linear-cli 0.3.10 → 0.4.1

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: 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