gloo-cli 1.1 → 1.2

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: fb5afa063ddf131c135fc6c78b1af9af2f866e683522ef7d0565bbd7d95ac060
4
- data.tar.gz: 26afa7e343e9539e942bbf2c78ee8218be70dc3a7a04e542574955452e9a5d84
3
+ metadata.gz: e85b61ea757d2e59ded7f9271a24394b66bc4ed6da70687257e0a4b8d6f2fcd6
4
+ data.tar.gz: faf2dcf643d24652a1a11be58232f235308878deb5c99b521d495031a40f2f8f
5
5
  SHA512:
6
- metadata.gz: 847b73db8362c4bf1e2f153de7a3b6f255dab12bf21e9e5e9bc2bcb930d82a923bfccc2b929ae3344feead48ae2b4c5f333cbc0b336088127a70103549f6e2a6
7
- data.tar.gz: b12d891f6255aa0f7ec36780af4af39a0ed1161673b641b5583dcca323530fdbb56424d088db29606f5c01a1ac9afd5e7e5baefd9a3f5937a5c232cf13590761
6
+ metadata.gz: cc7764a58cd890fe2100f4bd45ebf188d41726d583e0f86f5596ca1e0e958a9153de83a268d6fd14ad6ab78163e953a84bcfbe39aea2a829b2d54079ed3c4423
7
+ data.tar.gz: cc93483adce46c83815080802cab6fe1cc471a30f22fb52ef6d5a77b6153de27871717a71882c83a7d5ab4eb90cbf7cb3396419ee643b0fc76bd209eb0c47b02
data/lib/command.rb ADDED
@@ -0,0 +1,217 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
3
+ #
4
+ # A CLI command.
5
+ #
6
+
7
+ class Command < Gloo::Core::Obj
8
+
9
+ KEYWORD = 'command'.freeze
10
+ KEYWORD_SHORT = 'command'.freeze
11
+ DESCRIPTION = 'description'.freeze
12
+ ACTION = 'action'.freeze
13
+ DYNAMIC = 'dynamic'.freeze
14
+ NODES = 'nodes'.freeze
15
+
16
+ CONTEXT = 'context'.freeze
17
+ OPTIONS = 'options'.freeze
18
+ OPTIONS_KEY = 'options_key'.freeze
19
+
20
+ #
21
+ # The name of the object type.
22
+ #
23
+ def self.typename
24
+ return KEYWORD
25
+ end
26
+
27
+ #
28
+ # The short name of the object type.
29
+ #
30
+ def self.short_typename
31
+ return KEYWORD_SHORT
32
+ end
33
+
34
+ #
35
+ # Get the description of the command.
36
+ #
37
+ def description
38
+ o = find_child DESCRIPTION
39
+ return '' unless o
40
+
41
+ return o.value
42
+ end
43
+
44
+ #
45
+ # Is this a dynamic command?
46
+ # It is dynamic if there is a dynamic child.
47
+ # If so, it has contextual data.
48
+ #
49
+ def dynamic?
50
+ o = find_child DYNAMIC
51
+ return true if o
52
+ return false
53
+ end
54
+
55
+ #
56
+ # Get the dynamic key for this command.
57
+ #
58
+ def dynamic_key
59
+ o = find_child DYNAMIC
60
+ return o ? o.value : nil
61
+ end
62
+
63
+ def context
64
+ o = find_child CONTEXT
65
+ return o ? o : nil
66
+ end
67
+
68
+ #
69
+ # Get the options key for this command.
70
+ #
71
+ def options_key
72
+ o = find_child OPTIONS_KEY
73
+ return o ? o.value : nil
74
+ end
75
+
76
+ #
77
+ # Get the options for this command.
78
+ #
79
+ def options
80
+ arr = []
81
+ o = find_child OPTIONS
82
+
83
+ if o
84
+ arr = o.children.map { |child| child.value }
85
+ end
86
+
87
+ return arr
88
+ end
89
+
90
+ #
91
+ # Get the child nodes for this command.
92
+ #
93
+ def nodes
94
+ o = find_child NODES
95
+ return o ? o : nil
96
+ end
97
+
98
+ #
99
+ # Run the action script.
100
+ #
101
+ def run_action
102
+ o = find_child ACTION
103
+ return unless o
104
+
105
+ Gloo::Exec::Dispatch.message( @engine, 'run', o )
106
+ end
107
+
108
+ def run_action_with_context( context )
109
+ o = find_child ACTION
110
+ return unless o
111
+
112
+ ctx = find_child CONTEXT
113
+ ctx.set_value( context ) if ctx
114
+
115
+ Gloo::Exec::Dispatch.message( @engine, 'run', o )
116
+ end
117
+
118
+
119
+ # ---------------------------------------------------------------------
120
+ # Children
121
+ # ---------------------------------------------------------------------
122
+
123
+ #
124
+ # Does this object have children to add when an object
125
+ # is created in interactive mode?
126
+ # This does not apply during obj load, etc.
127
+ #
128
+ def add_children_on_create?
129
+ return true
130
+ end
131
+
132
+ #
133
+ # Add children to this object.
134
+ # This is used by containers to add children needed
135
+ # for default configurations.
136
+ #
137
+ def add_default_children
138
+ fac = @engine.factory
139
+ fac.create_string NAME, '', self
140
+ fac.create_string DESCRIPTION, '', self
141
+ fac.create_script ACTION, '', self
142
+ end
143
+
144
+
145
+ # ---------------------------------------------------------------------
146
+ # Messages
147
+ # ---------------------------------------------------------------------
148
+
149
+ #
150
+ # Get a list of message names that this object receives.
151
+ #
152
+ def self.messages
153
+ return super + [ 'register' ]
154
+ end
155
+
156
+ #
157
+ # Register the command with the shell.
158
+ #
159
+ def msg_register
160
+ if @params&.token_count&.positive?
161
+ pn = Gloo::Core::Pn.new( @engine, @params.first )
162
+ shell = pn.resolve
163
+ shell.add_command( self, get_command_data )
164
+
165
+ # Are there options to add?
166
+ if options_key
167
+ shell.set_context( options_key, options )
168
+ end
169
+ end
170
+ end
171
+
172
+
173
+ # ---------------------------------------------------------------------
174
+ # Helpers
175
+ # ---------------------------------------------------------------------
176
+
177
+ #
178
+ # Get the child nodes for this command.
179
+ #
180
+ def get_child_nodes
181
+ data = nodes.children.map do |child|
182
+ child.get_command_data
183
+ end
184
+
185
+ return data
186
+ end
187
+
188
+ #
189
+ # Get the command data for this command.
190
+ #
191
+ def get_command_data
192
+ if dynamic?
193
+ return {
194
+ name: name,
195
+ description: description,
196
+ dynamic: true,
197
+ obj: pn,
198
+ method: "cmd_obj_action_with_context",
199
+ source: dynamic_key
200
+ }
201
+ elsif nodes
202
+ return {
203
+ name: name,
204
+ description: description,
205
+ children: get_child_nodes
206
+ }
207
+ else
208
+ return {
209
+ name: name,
210
+ description: description,
211
+ method: "cmd_obj_action",
212
+ obj: pn
213
+ }
214
+ end
215
+ end
216
+
217
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
3
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
4
+ #
5
+ # A command node.
6
+ # Represents a command in the command tree.
7
+ #
8
+ class CommandNode
9
+
10
+ attr_reader :name, :description, :method, :obj
11
+
12
+ def initialize( name, description: "", method: nil, obj: nil, &children_block )
13
+ @name = name
14
+ @description = description
15
+ @method = method
16
+ @obj = obj
17
+ @children_block = children_block
18
+ end
19
+
20
+ def children( context )
21
+ return [] unless @children_block
22
+ @children_block.call( context )
23
+ end
24
+ end
data/lib/gloo-cli.rb CHANGED
@@ -9,6 +9,11 @@ require 'menu'
9
9
  require 'menu_item'
10
10
  require 'prompt'
11
11
  require 'select'
12
+ require 'shell'
13
+ require 'shell_runner'
14
+ require 'shell_context'
15
+ require 'command_node'
16
+ require 'command'
12
17
 
13
18
  #
14
19
  # Registers the extension.
@@ -25,6 +30,8 @@ class CliInit < Gloo::Plugin::Base
25
30
  callback.register_obj( MenuItem )
26
31
  callback.register_obj( Prompt )
27
32
  callback.register_obj( Select )
33
+ callback.register_obj( Shell )
34
+ callback.register_obj( Command )
28
35
  end
29
36
 
30
37
  end
data/lib/shell.rb ADDED
@@ -0,0 +1,192 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
3
+ #
4
+ # A CLI shell.
5
+ #
6
+
7
+ class Shell < Gloo::Core::Obj
8
+
9
+ KEYWORD = 'shell'.freeze
10
+ KEYWORD_SHORT = 'shell'.freeze
11
+ PROMPT = 'prompt'.freeze
12
+ DEFAULT_ACTION = 'default_action'.freeze
13
+ INCLUDE_QUIT = 'include_quit'.freeze
14
+
15
+ #
16
+ # The name of the object type.
17
+ #
18
+ def self.typename
19
+ return KEYWORD
20
+ end
21
+
22
+ #
23
+ # The short name of the object type.
24
+ #
25
+ def self.short_typename
26
+ return KEYWORD_SHORT
27
+ end
28
+
29
+ #
30
+ # Get the value of the prompt child object.
31
+ # Returns nil if there is none.
32
+ #
33
+ def prompt
34
+ o = find_child PROMPT
35
+ return '' unless o
36
+
37
+ return o.value
38
+ end
39
+
40
+ #
41
+ # Get the value of the include_quit child object.
42
+ # Returns false if there is none.
43
+ #
44
+ def include_quit?
45
+ o = find_child INCLUDE_QUIT
46
+ return o.value if o
47
+ return false
48
+ end
49
+
50
+
51
+ # ---------------------------------------------------------------------
52
+ # Children
53
+ # ---------------------------------------------------------------------
54
+
55
+ #
56
+ # Does this object have children to add when an object
57
+ # is created in interactive mode?
58
+ # This does not apply during obj load, etc.
59
+ #
60
+ def add_children_on_create?
61
+ return true
62
+ end
63
+
64
+ #
65
+ # Add children to this object.
66
+ # This is used by containers to add children needed
67
+ # for default configurations.
68
+ #
69
+ def add_default_children
70
+ fac = @engine.factory
71
+ fac.create_string PROMPT, '> ', self
72
+ fac.create_script DEFAULT_ACTION, '', self
73
+ end
74
+
75
+
76
+ # ---------------------------------------------------------------------
77
+ # Messages
78
+ # ---------------------------------------------------------------------
79
+
80
+ #
81
+ # Get a list of message names that this object receives.
82
+ #
83
+ def self.messages
84
+ return super + [ 'start', 'stop' ]
85
+ end
86
+
87
+ #
88
+ # Get the shell runner or initialize it if it doesn't exist.
89
+ #
90
+ def get_runner
91
+ return @runner ||= ShellRunner.new( @engine, self )
92
+ end
93
+
94
+ #
95
+ # Start the shell.
96
+ #
97
+ def msg_start
98
+ runner = get_runner
99
+
100
+ # add_test_commands
101
+ add_quit_command
102
+
103
+ runner.start
104
+ end
105
+
106
+ #
107
+ # Stop the shell.
108
+ #
109
+ def msg_stop
110
+ @runner.stop if @runner
111
+ end
112
+
113
+ #
114
+ # Add a command to the shell.
115
+ #
116
+ def add_command obj, command_data
117
+ runner = get_runner
118
+ runner.add_command_node( command_data )
119
+ end
120
+
121
+
122
+ # ---------------------------------------------------------------------
123
+ # Context
124
+ # ---------------------------------------------------------------------
125
+
126
+ def set_context key, value
127
+ @runner.set_context( key, value )
128
+ end
129
+
130
+
131
+ # ---------------------------------------------------------------------
132
+ # Commands
133
+ # ---------------------------------------------------------------------
134
+
135
+ #
136
+ # Quit the shell.
137
+ #
138
+ def add_quit_command
139
+ return unless include_quit?
140
+
141
+ @runner.add_command_node({
142
+ name: "quit",
143
+ description: "Quit the application",
144
+ method: "cmd_quit"
145
+ })
146
+ end
147
+
148
+ # def add_test_commands
149
+ # @runner.add_command_node({
150
+ # name: "add",
151
+ # description: "add a project",
152
+ # method: "cmd_add"
153
+ # })
154
+
155
+ # @runner.add_command_node({
156
+ # name: "show",
157
+ # description: "show a resource",
158
+ # children: [
159
+ # {
160
+ # name: "project",
161
+ # description: "show a project",
162
+ # dynamic: true,
163
+ # source: "projects"
164
+ # },
165
+ # {
166
+ # name: "task",
167
+ # description: "show a task",
168
+ # dynamic: true,
169
+ # source: "tasks"
170
+ # }
171
+ # ]
172
+ # })
173
+
174
+ # @runner.add_command_node({
175
+ # name: "list",
176
+ # description: "list resources",
177
+ # children: [
178
+ # {
179
+ # name: "projects",
180
+ # description: "list projects",
181
+ # method: "cmd_projects"
182
+ # },
183
+ # {
184
+ # name: "tasks",
185
+ # description: "list tasks",
186
+ # method: "cmd_tasks"
187
+ # }
188
+ # ]
189
+ # })
190
+ # end
191
+
192
+ end
@@ -0,0 +1,108 @@
1
+ #
2
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
3
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
4
+ #
5
+ # A shell context.
6
+ # Data associated with the shell session.
7
+ #
8
+ class ShellContext
9
+
10
+ attr_accessor :done
11
+
12
+ #
13
+ # Initialize the shell context
14
+ #
15
+ def initialize
16
+ @done = false
17
+ @properties = {}
18
+ end
19
+
20
+ #
21
+ # Get a property value
22
+ #
23
+ # @param key [Symbol] The property key
24
+ # @return [Object] The property value
25
+ #
26
+ def get( key )
27
+ key = key.to_sym
28
+ return @properties[key] if @properties.key?(key)
29
+
30
+ # If the property doesn't exist, return an empty array
31
+ return []
32
+ end
33
+
34
+ #
35
+ # Set a property value
36
+ #
37
+ # @param key [Symbol] The property key
38
+ # @param value [Object] The property value
39
+ #
40
+ def set( key, value )
41
+ @properties[key.to_sym] = value
42
+ end
43
+
44
+ #
45
+ # Add an item to a property list
46
+ #
47
+ # @param key [Symbol] The property key
48
+ # @param item [Object] The item to add
49
+ #
50
+ def add_to_list( key, item )
51
+ key = key.to_sym
52
+ list = get(key)
53
+
54
+ # Ensure we have an array to work with
55
+ list = [] unless list.is_a?(Array)
56
+
57
+ # Add the item and store the updated list
58
+ list << item
59
+ set(key, list)
60
+
61
+ list
62
+ end
63
+
64
+ #
65
+ # Check if a property exists
66
+ #
67
+ # @param key [Symbol] The property key
68
+ # @return [Boolean] True if property exists
69
+ #
70
+ def has?( key )
71
+ @properties.key?( key.to_sym )
72
+ end
73
+
74
+ #
75
+ # Get all property keys
76
+ #
77
+ # @return [Array<Symbol>] All property keys
78
+ #
79
+ def keys
80
+ @properties.keys
81
+ end
82
+
83
+ #
84
+ # Dynamic method access to properties
85
+ #
86
+ def method_missing( method_name, *args, &block )
87
+ if method_name.to_s.end_with?('=')
88
+ # Setter method: projects=, tasks=, etc.
89
+ key = method_name.to_s.chomp('=').to_sym
90
+ set(key, args.first)
91
+ else
92
+ # Getter method: projects, tasks, etc.
93
+ key = method_name.to_sym
94
+ get(key)
95
+ end
96
+ end
97
+
98
+ #
99
+ # Respond to missing methods for property access
100
+ #
101
+ def respond_to_missing?( method_name, include_private = false )
102
+ key = method_name.to_s.chomp('=').to_sym
103
+ has?(key) || super
104
+ end
105
+
106
+ private
107
+
108
+ end
@@ -0,0 +1,243 @@
1
+ #
2
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
3
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
4
+ #
5
+ # A shell runner.
6
+ #
7
+ require "readline"
8
+
9
+ class ShellRunner
10
+
11
+ DEFAULT_PROMPT = " -> "
12
+ #
13
+ # Initialize the shell runner
14
+ #
15
+ # @param obj [Object] The shell obj.
16
+ #
17
+ def initialize( engine, obj )
18
+ @engine = engine
19
+ @obj = obj
20
+ @context = ShellContext.new
21
+ @root = CommandNode.new( nil )
22
+ end
23
+
24
+
25
+ # ---------------------------------------------------------------------
26
+ # Shell, control, start and stop
27
+ # ---------------------------------------------------------------------
28
+
29
+ #
30
+ # Start the shell.
31
+ #
32
+ def start
33
+ repl
34
+ end
35
+
36
+ #
37
+ # Flag the shell as done, next time through the loop it will stop
38
+ #
39
+ def stop
40
+ @context.done = true
41
+ end
42
+
43
+ #
44
+ # Get the prompt string
45
+ #
46
+ def prompt
47
+ p = @obj.prompt
48
+ return p ? p + ' ' : DEFAULT_PROMPT
49
+ end
50
+
51
+ #
52
+ # Quit the shell.
53
+ #
54
+ def cmd_quit( obj, context )
55
+ puts "Quitting…"
56
+ context.done = true
57
+ end
58
+
59
+ #
60
+ # Run an action on an object.
61
+ #
62
+ def cmd_obj_action( obj, context )
63
+ pn = Gloo::Core::Pn.new( @engine, obj )
64
+ command = pn.resolve
65
+ if command
66
+ command.run_action
67
+ end
68
+ end
69
+
70
+ def cmd_obj_action_with_context( cmd_node, parent_node = nil )
71
+ if parent_node
72
+ pn = Gloo::Core::Pn.new( @engine, parent_node.obj )
73
+ command = pn.resolve
74
+ if command
75
+ command.run_action_with_context( cmd_node.name )
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+
82
+ # ---------------------------------------------------------------------
83
+ # Context
84
+ # ---------------------------------------------------------------------
85
+
86
+ #
87
+ # Set a context list.
88
+ #
89
+ def set_context key, value_list
90
+ @context.set( key, value_list )
91
+ end
92
+
93
+
94
+ # ---------------------------------------------------------------------
95
+ # Tree building
96
+ # ---------------------------------------------------------------------
97
+
98
+ #
99
+ # Execute a command.
100
+ #
101
+ def execute_command( command_node, args, parent_node = nil )
102
+ if command_node.respond_to?( :method ) && command_node.method
103
+ send( command_node.method, command_node.obj, @context )
104
+ else
105
+ if command_node.name && command_node.name != "" && !command_node.description.empty?
106
+ puts "#{command_node.description}: #{command_node.name}"
107
+ elsif command_node.name
108
+ # puts "Showing: #{command_node.name}"
109
+ cmd_obj_action_with_context( command_node, parent_node )
110
+ end
111
+ end
112
+ end
113
+
114
+
115
+ #
116
+ # Build a command node from data.
117
+ #
118
+ def build_node_from_data( data )
119
+ if data[:dynamic]
120
+ CommandNode.new(data[:name], description: data[:description], obj: data[:obj]) do |ctx|
121
+ ctx.send(data[:source]).map do |item|
122
+ CommandNode.new(item)
123
+ end
124
+ end
125
+ elsif data[:children]
126
+ CommandNode.new(data[:name], description: data[:description], method: data[:method], obj: data[:obj]) do |ctx|
127
+ data[:children].map { |child_data| build_node_from_data(child_data) }
128
+ end
129
+ else
130
+ CommandNode.new(data[:name], description: data[:description], method: data[:method], obj: data[:obj])
131
+ end
132
+ end
133
+
134
+ #
135
+ # Add a command node to the root dynamically
136
+ #
137
+ # @param command_data [Hash] The command data hash
138
+ #
139
+ # Add a single command dynamically
140
+ #
141
+ def add_command_node( command_data)
142
+ node = build_node_from_data(command_data)
143
+
144
+ # Get existing children block or create new one
145
+ existing_block = @root.instance_variable_get(:@children_block)
146
+
147
+ if existing_block
148
+ # Store existing nodes and add new one
149
+ existing_nodes = existing_block.call(@context)
150
+ all_nodes = existing_nodes + [node]
151
+ @root.instance_variable_set(:@children_block, proc { |ctx| all_nodes })
152
+ else
153
+ # Create new children block with just this node
154
+ @root.instance_variable_set(:@children_block, proc { |ctx| [node] })
155
+ end
156
+
157
+ node
158
+ end
159
+
160
+
161
+ # ---------------------------------------------------------------------
162
+ # REPL
163
+ # ---------------------------------------------------------------------
164
+
165
+ #
166
+ # Traverse the command tree to find the matching node
167
+ #
168
+ # @param node [CommandNode] The current node
169
+ # @param tokens [Array<String>] The tokens to traverse
170
+ #
171
+ # @return [Hash] Hash with :node and :parent keys
172
+ #
173
+ def traverse( node, tokens )
174
+ current = node
175
+ parent = nil
176
+
177
+ tokens.each do |token|
178
+ children = current.children( @context )
179
+ match = children.find { |c| c.name == token }
180
+ return { node: nil, parent: nil } unless match
181
+
182
+ parent = current
183
+ current = match
184
+ end
185
+
186
+ { node: current, parent: parent }
187
+ end
188
+
189
+ #
190
+ # Setup readline completion
191
+ #
192
+ def setup_completion
193
+ Readline.completion_append_character = " "
194
+ Readline.basic_word_break_characters = " \t\n\"\\'`@$><=;|&{("
195
+
196
+ Readline.completion_proc = proc do |input|
197
+ buffer = Readline.line_buffer
198
+ tokens = buffer.split(" ")
199
+
200
+ tokens << "" if buffer.end_with?(" ")
201
+
202
+ result = traverse( @root, tokens[0..-2] )
203
+ current = result[:node]
204
+ options = current.children( @context ).map( &:name )
205
+
206
+ matches = options.grep(/^#{Regexp.escape(input)}/)
207
+
208
+ if matches.length > 1
209
+ puts
210
+ current.children( @context ).each do |child|
211
+ if matches.include?( child.name )
212
+ puts "#{child.name.ljust(15)} #{child.description}"
213
+ end
214
+ end
215
+ print "#{prompt}#{buffer}"
216
+ end
217
+
218
+ matches
219
+ end
220
+ end
221
+
222
+
223
+ #
224
+ # Run the REPL loop.
225
+ #
226
+ def repl
227
+ setup_completion
228
+
229
+ while ( ! @context.done && (line = Readline.readline(prompt, true)) )
230
+ tokens = line.strip.split(" ")
231
+ next if tokens.empty?
232
+
233
+ result = traverse( @root, tokens )
234
+
235
+ if result[:node]
236
+ execute_command( result[:node], tokens, result[:parent] )
237
+ else
238
+ puts "Unknown command"
239
+ end
240
+ end
241
+ end
242
+
243
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloo-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Crane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-22 00:00:00.000000000 Z
11
+ date: 2026-04-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Adds CLI support to Gloo.
14
14
  email:
@@ -19,11 +19,16 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - lib/cli_colorize.rb
21
21
  - lib/cli_confirm.rb
22
+ - lib/command.rb
23
+ - lib/command_node.rb
22
24
  - lib/gloo-cli.rb
23
25
  - lib/menu.rb
24
26
  - lib/menu_item.rb
25
27
  - lib/prompt.rb
26
28
  - lib/select.rb
29
+ - lib/shell.rb
30
+ - lib/shell_context.rb
31
+ - lib/shell_runner.rb
27
32
  homepage: https://gloo.ecrane.us/
28
33
  licenses:
29
34
  - MIT