linear-cli 0.3.10 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b42beb6ec055c0604219c54ecd861a08e2e5fc367531cdacc76d7fdb4bd86f0f
4
- data.tar.gz: 66b66a585dc37fc0f82dd31a9044d17dbc53b6b8d8f66288c62d9d7a05a8ddf3
3
+ metadata.gz: 78d3b6f808a137827fc21be0dd57daab6ef08fe4b54d6c2b428dec93e5801baa
4
+ data.tar.gz: 96e390ec3116a9cfd0ec844351ec0b9c86148f808ef9471a9bce37e95674c2b6
5
5
  SHA512:
6
- metadata.gz: 54c653e2f07ce9ebf5483ee3e13ec98db266b9a6e6a1775e768bfbff7f3036da4d73bc15c7fbc49e73939e560ae7ec970e6283d62875390578f658490faa1835
7
- data.tar.gz: f3caaa0bb9d299a2a753f899d416f20bd6a7de3a0b03a4496cc6a43810cb77cf31dd5d38a6676cb110e823e4f109daf90235f3c479f0106795adceb9d54ae320
6
+ metadata.gz: f9cfd202b19123a1c32b81995335f6947e40e204e50b588e9a059d508c495756de852f4cc3293f93590f14f1c9ad2a6c4978169d4a6e5a9dbd82c3d24f5e3478
7
+ data.tar.gz: b690c54cd0657929a45fea1bd68fe2621ea8afe4eefcf1af86f09575b04e8c864e9bc2a0a98523820406e6c6319b567aea98bfeba459a397a9de29efee6065ab
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyists
4
+ module Linear
5
+ module CLI
6
+ # This module is prepended to all commands to log their calls
7
+ module Caller
8
+ def self.prepended(mod) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
9
+ # Global options for all commands
10
+ mod.instance_eval do
11
+ option :output, type: :string, default: 'text', values: %w[text json], desc: 'Output format'
12
+ option :debug, type: :integer, default: 0, desc: 'Debug level'
13
+ end
14
+ Caller.class_eval do
15
+ # Wraps the :call method so the debug option is honored, and we can trace the call
16
+ # as well as handle any exceptions that are raised
17
+ define_method :call do |**method_args| # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
18
+ debug = method_args[:debug].to_i
19
+ Rubyists::Linear.verbosity = debug
20
+ logger.trace "Calling #{self.class} with #{method_args}"
21
+ super(**method_args)
22
+ rescue SmellsBad => e
23
+ logger.error e.message
24
+ exit 1
25
+ rescue NotFoundError => e
26
+ logger.error e.message
27
+ rescue StandardError => e
28
+ logger.error e.message
29
+ logger.error e.backtrace.join("\n") if Rubyists::Linear.verbosity.positive?
30
+ exit 5
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyists
4
+ module Linear
5
+ module CLI
6
+ # The CommonOptions module contains common options for all commands
7
+ module CommonOptions
8
+ def self.included(mod)
9
+ mod.instance_eval do
10
+ extend Rubyists::Linear::CLI::Watcher
11
+ end
12
+ end
13
+
14
+ def display(subject, options)
15
+ return puts(JSON.pretty_generate(subject)) if options[:output] == 'json'
16
+ return subject.each { |s| s.display(options) } if subject.respond_to?(:each)
17
+ unless subject.respond_to?(:display)
18
+ raise SmellsBad, "Cannot display #{subject}, there is no #display method and it is not a collection"
19
+ end
20
+
21
+ subject.display(options)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyists
4
+ module Linear
5
+ module CLI
6
+ # The SubCommands module should be included in all commands with subcommands
7
+ module SubCommands
8
+ def self.included(mod)
9
+ mod.instance_eval do
10
+ def const_added(const)
11
+ return unless const == :ALIASES
12
+
13
+ Rubyists::Linear::CLI.load_and_register! self
14
+ end
15
+ end
16
+ end
17
+
18
+ def ask_for_team # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
19
+ teams = Rubyists::Linear::Team.mine
20
+ if teams.size == 1
21
+ logger.info('Only one team found, using it', team: teams.first.name)
22
+ teams.first
23
+ elsif teams.empty?
24
+ logger.error('No teams found for you. Please join a team or pass an existing team name.')
25
+ raise SmellsBad, 'No team given and none found for you'
26
+ else
27
+ prompt.on(:keypress) do |event|
28
+ prompt.trigger(:keydown) if event.value == 'j'
29
+ prompt.trigger(:keyup) if event.value == 'k'
30
+ end
31
+ key = prompt.select('Choose a team', teams.to_h { |t| [t.name, t.key] })
32
+ Rubyists::Linear::Team.find key
33
+ end
34
+ end
35
+
36
+ def prompt
37
+ @prompt ||= CLI.prompt
38
+ end
39
+
40
+ def team_for(key = nil)
41
+ return Rubyists::Linear::Team.find(key) if key
42
+
43
+ ask_for_team
44
+ end
45
+
46
+ def description_for(description = nil)
47
+ return description if description
48
+
49
+ prompt.multiline('Description:').join(' ')
50
+ end
51
+
52
+ def title_for(title = nil)
53
+ return title if title
54
+
55
+ prompt.ask('Title:')
56
+ end
57
+
58
+ def labels_for(team, labels = nil)
59
+ return Rubyists::Linear::Label.find_all_by_name(labels.map(&:strip)) if labels
60
+
61
+ prompt.multi_select('Labels:', team.labels.to_h { |t| [t.name, t] })
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rubyists
4
4
  module Linear
5
- VERSION = '0.3.10'
5
+ VERSION = '0.4.1'
6
6
  end
7
7
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyists
4
+ module Linear
5
+ module CLI
6
+ # Watch for the call method to be added to a command
7
+ module Watcher
8
+ def self.extended(_mod)
9
+ define_method :method_added do |method_name|
10
+ return unless method_name == :call
11
+
12
+ prepend Rubyists::Linear::CLI::Caller
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/linear/cli.rb CHANGED
@@ -3,72 +3,55 @@
3
3
  require 'dry/cli'
4
4
  require 'dry/cli/completion/command'
5
5
  require_relative '../linear'
6
+ require 'semantic_logger'
6
7
  require 'tty-markdown'
8
+ require 'tty-prompt'
7
9
 
8
10
  # The Rubyists module is the top-level namespace for all Rubyists projects
9
11
  module Rubyists
10
12
  module Linear
11
13
  # The CLI module is a Dry::CLI::Registry that contains all the commands
12
14
  module CLI
15
+ include SemanticLogger::Loggable
13
16
  extend Dry::CLI::Registry
14
17
 
15
- # Watch for the call method to be added to a command
16
- module Watcher
17
- def self.extended(_mod)
18
- define_method :method_added do |method_name|
19
- return unless method_name == :call
20
-
21
- prepend Rubyists::Linear::CLI::Caller
22
- end
23
- end
18
+ def self.prompt
19
+ @prompt ||= TTY::Prompt.new
24
20
  end
25
21
 
26
- # The CommonOptions module contains common options for all commands
27
- module CommonOptions
28
- def self.included(mod)
29
- mod.instance_eval do
30
- extend Rubyists::Linear::CLI::Watcher
31
- option :output, type: :string, default: 'text', values: %w[text json], desc: 'Output format'
32
- option :debug, type: :integer, default: 0, desc: 'Debug level'
33
- end
22
+ def self.register_sub!(command, sub_file, klass)
23
+ # The filename is expected to define a class of the same name, but capitalized
24
+ name = sub_file.basename('.rb').to_s
25
+ subklass = klass.const_get(name.capitalize)
26
+ if (aliases = klass::ALIASES[name.to_sym])
27
+ command.register name, subklass, aliases: Array(aliases)
28
+ else
29
+ command.register name, subklass
34
30
  end
31
+ end
35
32
 
36
- def display(subject, options)
37
- return puts(JSON.pretty_generate(subject)) if options[:output] == 'json'
38
- return subject.each { |s| s.display(options) } if subject.respond_to?(:each)
39
- unless subject.respond_to?(:display)
40
- raise SmellsBad, "Cannot display #{subject}, there is no #display method and it is not a collection"
41
- end
42
-
43
- subject.display(options)
33
+ def self.register_subcommands!(command, name, klass)
34
+ Pathname.new(__FILE__).dirname.join("commands/#{name}").glob('*.rb').each do |file|
35
+ require file.expand_path
36
+ register_sub! command, file, klass
44
37
  end
45
38
  end
46
39
 
47
- # This module is prepended to all commands to log their calls
48
- module Caller
49
- def self.prepended(_mod) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
50
- Caller.class_eval do
51
- define_method :call do |**method_args| # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
52
- debug = method_args[:debug].to_i
53
- Rubyists::Linear.verbosity = debug
54
- logger.trace "Calling #{self.class} with #{method_args}"
55
- super(**method_args)
56
- rescue SmellsBad => e
57
- logger.error e.message
58
- exit 1
59
- rescue NotFoundError => e
60
- logger.error e.message
61
- rescue StandardError => e
62
- logger.error e.message
63
- logger.error e.backtrace.join("\n") if Rubyists::Linear.verbosity.positive?
64
- exit 5
65
- end
66
- end
40
+ def self.load_and_register!(command)
41
+ logger.debug "Registering #{command}"
42
+ name = command.name.split('::').last.downcase
43
+ command_aliases = command::ALIASES[name.to_sym] || []
44
+ register name, aliases: Array(command_aliases) do |cmd|
45
+ register_subcommands! cmd, name, command
67
46
  end
68
47
  end
69
48
  end
70
49
  end
71
50
 
51
+ Pathname.new(__FILE__).dirname.join('cli').glob('*.rb').each do |file|
52
+ require file.expand_path
53
+ end
54
+
72
55
  # Load all our commands
73
56
  Pathname.new(__FILE__).dirname.join('commands').glob('*.rb').each do |file|
74
57
  require file.expand_path
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'semantic_logger'
4
+
5
+ module Rubyists
6
+ # Namespace for Linear
7
+ module Linear
8
+ M :issue, :user, :label
9
+ # Namespace for CLI
10
+ module CLI
11
+ module Issue
12
+ Create = Class.new Dry::CLI::Command
13
+ # The Create class is a Dry::CLI::Command to create a new issue
14
+ class Create
15
+ include SemanticLogger::Loggable
16
+ include Rubyists::Linear::CLI::CommonOptions
17
+ include Rubyists::Linear::CLI::Issue # for #gimme_da_issue and other methods
18
+ desc 'Create a new issue'
19
+ option :title, type: :string, aliases: ['-t'], desc: 'Issue Title'
20
+ option :team, type: :string, aliases: ['-T'], desc: 'Team Identifier'
21
+ option :description, type: :string, aliases: ['-d'], desc: 'Issue Description'
22
+ option :labels, type: :array, aliases: ['-l'], desc: 'Labels for the issue (Comma separated list)'
23
+
24
+ def call(**options)
25
+ logger.debug('Creating issue', options:)
26
+ issue = make_da_issue!(**options)
27
+ logger.debug('Issue created', issue:)
28
+ prompt.yes?('Do you want to take this issue?') && gimme_da_issue!(issue.id, User.me)
29
+ display issue, options
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -38,7 +38,7 @@ module Rubyists
38
38
 
39
39
  def issues_for(options)
40
40
  logger.debug('Fetching issues', options:)
41
- return options[:ids].map { |id| Rubyists::Linear::Issue.find(id) } if options[:ids]
41
+ return options[:ids].map { |id| Rubyists::Linear::Issue.find(id.upcase) } if options[:ids]
42
42
  return Rubyists::Linear::Issue.all(filter: { assignee: { null: true } }) if options[:unassigned]
43
43
  return Rubyists::Linear::User.me.issues if options[:mine]
44
44
 
@@ -17,18 +17,10 @@ module Rubyists
17
17
  desc 'Assign one or more issues to yourself'
18
18
  argument :issue_ids, type: :array, required: true, desc: 'Issue Identifiers'
19
19
 
20
- def gimme_da_issue(issue_id, me) # rubocop:disable Naming/MethodParameterName
21
- issue = Rubyists::Linear::Issue.find(issue_id)
22
- logger.debug 'Taking issue', issue:, assignee: me
23
- updated = issue.assign! me
24
- logger.debug 'Issue taken', issue: updated
25
- updated
26
- end
27
-
28
20
  def call(issue_ids:, **options)
29
21
  me = Rubyists::Linear::User.me
30
22
  updates = issue_ids.map do |issue_id|
31
- gimme_da_issue issue_id, me
23
+ gimme_da_issue! issue_id, me # gimme_da_issue! is defined in Rubyists::Linear::CLI::Issue
32
24
  rescue NotFoundError => e
33
25
  logger.warn e.message
34
26
  next
@@ -1,21 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../cli/sub_commands'
4
+
3
5
  module Rubyists
4
6
  module Linear
5
7
  # The Cli module is defined in cli.rb and is the top-level namespace for all CLI commands
6
8
  module CLI
7
- # This ALIASES hash will return the key as the value if the key is not found,
8
- # otherwise it will return the value of the existing key
9
- ALIASES = Hash.new { |h, k| h[k] = k }.merge(
10
- 'list' => 'ls'
11
- )
9
+ # The Issue module is the namespace for all issue-related commands, and
10
+ # should be included in any command that deals with issues
11
+ module Issue
12
+ include CLI::SubCommands
13
+ # Aliases for Issue commands
14
+ ALIASES = {
15
+ create: %w[c new add], # aliases for the create command
16
+ list: %w[l ls], # aliases for the list command
17
+ show: %w[s view v display d], # aliases for the show command
18
+ issue: %w[i issues] # aliases for the main issue command itself
19
+ }.freeze
20
+
21
+ def make_da_issue!(**options)
22
+ # These *_for methods are defined in Rubyists::Linear::CLI::SubCommands
23
+ title = title_for options[:title]
24
+ description = description_for options[:description]
25
+ team = team_for options[:team]
26
+ labels = labels_for team, options[:labels]
27
+ Rubyists::Linear::Issue.create(title:, description:, team:, labels:)
28
+ end
12
29
 
13
- Pathname.new(__FILE__).dirname.join('issue').glob('*.rb').each do |file|
14
- require file.expand_path
15
- register 'issue', aliases: %w[i] do |issue|
16
- basename = File.basename(file, '.rb')
17
- # The filename is expected to define a class of the same name, but capitalized
18
- issue.register ALIASES[basename], Issue.const_get(basename.capitalize)
30
+ def gimme_da_issue!(issue_id, me) # rubocop:disable Naming/MethodParameterName
31
+ issue = Rubyists::Linear::Issue.find(issue_id)
32
+ logger.debug 'Taking issue', issue:, assignee: me
33
+ updated = issue.assign! me
34
+ logger.debug 'Issue taken', issue: updated
35
+ updated
19
36
  end
20
37
  end
21
38
  end
@@ -31,10 +31,6 @@ module Rubyists
31
31
  prepend Rubyists::Linear::CLI::Caller
32
32
  end
33
33
  end
34
-
35
- register 'team', aliases: %w[t] do |team|
36
- team.register 'ls', Team::List
37
- end
38
34
  end
39
35
  end
40
36
  end
@@ -1,5 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Pathname.new(__FILE__).dirname.join('team').glob('*.rb').each do |file|
4
- require file.expand_path
3
+ module Rubyists
4
+ module Linear
5
+ # The Cli module is defined in cli.rb and is the top-level namespace for all CLI commands
6
+ module CLI
7
+ # The Team module is the namespace for all team-related commands
8
+ module Team
9
+ include CLI::SubCommands
10
+ # Aliases for Team commands.
11
+ ALIASES = {
12
+ list: %w[ls l], # aliases for the list command
13
+ team: %w[t teams] # aliases for the main team command itself
14
+ }.freeze
15
+ end
16
+ end
17
+ end
5
18
  end
@@ -14,7 +14,9 @@ module Rubyists
14
14
  include SemanticLogger::Loggable
15
15
  include Rubyists::Linear::CLI::CommonOptions
16
16
 
17
- option :teams, type: :boolean, default: false, desc: 'Show teams'
17
+ desc 'Get your own user info'
18
+
19
+ option :teams, aliases: ['-t'], type: :boolean, default: false, desc: 'Show teams'
18
20
 
19
21
  def call(**options)
20
22
  logger.debug 'Getting user info'
@@ -23,7 +25,7 @@ module Rubyists
23
25
 
24
26
  prepend Rubyists::Linear::CLI::Caller
25
27
  end
26
- register 'whoami', WhoAmI
28
+ register 'whoami', WhoAmI, aliases: %w[me w who whodat]
27
29
  end
28
30
  end
29
31
  end
@@ -34,6 +34,16 @@ module Rubyists
34
34
 
35
35
  new(data[:issue])
36
36
  end
37
+
38
+ def create(title:, description:, team:, labels: [])
39
+ team_id = team.id
40
+ label_ids = labels.map(&:id)
41
+ input = { title:, description:, teamId: team_id }
42
+ input.merge!(labelIds: label_ids) unless label_ids.empty?
43
+ m = mutation { issueCreate(input:) { issue { ___ Base } } }
44
+ data = Api.query(m)
45
+ new(data[:issueCreate][:issue])
46
+ end
37
47
  end
38
48
 
39
49
  def assign!(user)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gqli'
4
+
5
+ module Rubyists
6
+ # Namespace for Linear
7
+ module Linear
8
+ L :api, :fragments
9
+ M :base_model, :issue, :user, :team
10
+ Label = Class.new(BaseModel)
11
+ # The Label class represents a Linear issue label.
12
+ class Label
13
+ include SemanticLogger::Loggable
14
+
15
+ Base = fragment('BaseLabel', 'IssueLabel') do
16
+ id
17
+ description
18
+ name
19
+ createdAt
20
+ updatedAt
21
+ end
22
+
23
+ def self.find_all_by_name(names)
24
+ q = query do
25
+ issueLabels(filter: { name: { in: names } }) do
26
+ edges { node { ___ Base } }
27
+ end
28
+ end
29
+ data = Api.query(q)
30
+ edges = data.dig(:issueLabels, :edges)
31
+ raise NotFoundError, "No labels found: #{names}" unless edges
32
+
33
+ edges.map { |edge| new edge[:node] }
34
+ end
35
+
36
+ def self.find_by_name(name)
37
+ them = find_all_by_name([name])
38
+ if them.size > 1
39
+ logger.warn('Found multiple matches for label name, using the first one returned', labels: them)
40
+ end
41
+ them.first
42
+ end
43
+
44
+ def to_s
45
+ format('%<name>s', name:)
46
+ end
47
+
48
+ def full
49
+ format('%<to_s>-10s %<description>s', description: , to_s:)
50
+ end
51
+
52
+ def display(_options)
53
+ printf "%s\n", full
54
+ end
55
+ end
56
+ end
57
+ end
@@ -12,6 +12,9 @@ module Rubyists
12
12
  class Team
13
13
  include SemanticLogger::Loggable
14
14
 
15
+ # TODO: Make this configurable
16
+ IgnoredLabels = [/-ios$/, /-android$/].freeze # rubocop:disable Naming/ConstantName
17
+
15
18
  Base = fragment('BaseTeam', 'Team') do
16
19
  description
17
20
  id
@@ -21,6 +24,17 @@ module Rubyists
21
24
  updatedAt
22
25
  end
23
26
 
27
+ def self.find(key)
28
+ q = query do
29
+ team(id: key) { ___ Base }
30
+ end
31
+ data = Api.query(q)
32
+ hash = data[:team]
33
+ raise NotFoundError, "Team not found: #{key}" unless hash
34
+
35
+ new hash
36
+ end
37
+
24
38
  def self.mine
25
39
  User.me.teams
26
40
  end
@@ -33,8 +47,34 @@ module Rubyists
33
47
  format('%<key>-6s %<to_s>s', key:, to_s:)
34
48
  end
35
49
 
50
+ def label_query
51
+ team_id = id
52
+ query do
53
+ team(id: team_id) do
54
+ labels do
55
+ nodes { ___ Label::Base }
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def label_filter(labels = nil)
62
+ return [] unless labels
63
+
64
+ labels.reject { |label| IgnoredLabels.detect { |i| label.name.match? i } }
65
+ end
66
+
67
+ def labels
68
+ return @labels if @labels && !@labels.empty?
69
+
70
+ all = Api.query(label_query).dig(:team, :labels, :nodes)&.map do |label|
71
+ Label.new label
72
+ end
73
+ @labels = label_filter(all)
74
+ end
75
+
36
76
  def members
37
- return @members unless @members.empty?
77
+ return @members if @members && !@members.empty?
38
78
 
39
79
  q = query do
40
80
  team(id:) do
@@ -18,15 +18,17 @@ module Rubyists
18
18
  email
19
19
  end
20
20
 
21
- WithTeams = fragment('UserWithTeams', 'User') do
22
- ___ Base
23
- teams do
24
- nodes { ___ Team::Base }
21
+ def self.with_teams
22
+ @with_teams = fragment('UserWithTeams', 'User') do
23
+ ___ Base
24
+ teams do
25
+ nodes { ___ Team::Base }
26
+ end
25
27
  end
26
28
  end
27
29
 
28
30
  def self.me(teams: false)
29
- fragment = teams ? WithTeams : Base
31
+ fragment = teams ? with_teams : Base
30
32
  q = query do
31
33
  viewer do
32
34
  ___ fragment
data/linear-cli.gemspec CHANGED
@@ -42,6 +42,7 @@ Gem::Specification.new do |spec|
42
42
  spec.add_dependency 'sequel', '~> 5.0'
43
43
  spec.add_dependency 'sqlite3', '~> 1.7'
44
44
  spec.add_dependency 'tty-markdown', '~> 0.7'
45
+ spec.add_dependency 'tty-prompt', '~> 0.23'
45
46
 
46
47
  # For more information and examples about making a new gem, check out our
47
48
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linear-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.10
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tj (bougyman) Vanderpoel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-01 00:00:00.000000000 Z
11
+ date: 2024-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.7'
139
+ - !ruby/object:Gem::Dependency
140
+ name: tty-prompt
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.23'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.23'
139
153
  description: A CLI for interacting with Linear.app. Loosely based on the GitHub CLI
140
154
  email:
141
155
  - tj@rubyists.com
@@ -156,8 +170,13 @@ files:
156
170
  - lib/linear.rb
157
171
  - lib/linear/api.rb
158
172
  - lib/linear/cli.rb
173
+ - lib/linear/cli/caller.rb
174
+ - lib/linear/cli/common_options.rb
175
+ - lib/linear/cli/sub_commands.rb
159
176
  - lib/linear/cli/version.rb
177
+ - lib/linear/cli/watcher.rb
160
178
  - lib/linear/commands/issue.rb
179
+ - lib/linear/commands/issue/create.rb
161
180
  - lib/linear/commands/issue/list.rb
162
181
  - lib/linear/commands/issue/take.rb
163
182
  - lib/linear/commands/team.rb
@@ -167,6 +186,7 @@ files:
167
186
  - lib/linear/fragments.rb
168
187
  - lib/linear/models/base_model.rb
169
188
  - lib/linear/models/issue.rb
189
+ - lib/linear/models/label.rb
170
190
  - lib/linear/models/team.rb
171
191
  - lib/linear/models/user.rb
172
192
  - lib/linear/version.rb