thor-interactive 0.1.0.pre.1 → 0.1.0.pre.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 +4 -4
- data/README.md +45 -9
- data/examples/README.md +36 -29
- data/examples/sample_app.rb +3 -1
- data/lib/thor/interactive/command.rb +10 -0
- data/lib/thor/interactive/shell.rb +190 -32
- data/lib/thor/interactive/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04a0318e910fee2ac10a134b2a9514ceef72921c2b1525d29face4c46a306ad8
|
4
|
+
data.tar.gz: 1376543ed73e25721cb220893cb62fbe0fb6bc18d94ea38ecf00df60878fd4d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '05909f8f5c99a783831eec436ee94236b279b883c2cda0c62379081c28cc7258d8ab8317dfa4d3aba36c942f763767eed1dc08d45427ee05c00b7513f8488af9'
|
7
|
+
data.tar.gz: 16a55871f6754affc2450703f3de24ecfee977ca372566b93dc44537cd3599bf305ecd988ec7f06349c786df9f0f7ab2e5bfed7d608390e53fedc6bdeb05f1cb
|
data/README.md
CHANGED
@@ -55,10 +55,11 @@ Now your app supports both modes:
|
|
55
55
|
# Normal CLI usage (unchanged)
|
56
56
|
ruby myapp.rb hello World
|
57
57
|
|
58
|
-
# New interactive mode
|
58
|
+
# New interactive mode with slash commands
|
59
59
|
ruby myapp.rb interactive
|
60
|
-
myapp> hello Alice
|
60
|
+
myapp> /hello Alice
|
61
61
|
Hello Alice!
|
62
|
+
myapp> Natural language input goes to default handler
|
62
63
|
myapp> exit
|
63
64
|
```
|
64
65
|
|
@@ -116,19 +117,25 @@ In interactive mode:
|
|
116
117
|
```bash
|
117
118
|
ruby rag_app.rb interactive
|
118
119
|
|
119
|
-
rag> ask
|
120
|
-
# LLM initializes once
|
120
|
+
rag> /ask What is Ruby?
|
121
|
+
# LLM initializes once
|
121
122
|
Ruby is a programming language...
|
122
123
|
|
123
|
-
rag> ask
|
124
|
+
rag> /ask Tell me more
|
124
125
|
# LLM client reused, conversation context maintained
|
125
126
|
Based on our previous discussion about Ruby...
|
126
127
|
|
127
|
-
rag>
|
128
|
+
rag> What's the difference between Ruby and Python?
|
129
|
+
# Natural language goes directly to default handler (ask command)
|
130
|
+
Ruby and Python differ in several ways...
|
131
|
+
|
132
|
+
rag> /history
|
128
133
|
1. Q: What is Ruby?
|
129
134
|
A: Ruby is a programming language...
|
130
135
|
2. Q: Tell me more
|
131
136
|
A: Based on our previous discussion about Ruby...
|
137
|
+
3. Q: What's the difference between Ruby and Python?
|
138
|
+
A: Ruby and Python differ in several ways...
|
132
139
|
```
|
133
140
|
|
134
141
|
## Configuration
|
@@ -145,7 +152,10 @@ class MyApp < Thor
|
|
145
152
|
nested_prompt_format: "[L%d] %s", # Format for nested prompts (if allowed)
|
146
153
|
default_handler: proc do |input, thor_instance|
|
147
154
|
# Handle unrecognized input
|
148
|
-
|
155
|
+
# IMPORTANT: Use direct method calls, NOT invoke(), to avoid Thor's
|
156
|
+
# silent failure on repeated calls to the same method
|
157
|
+
thor_instance.search(input) # ✅ Works repeatedly
|
158
|
+
# thor_instance.invoke(:search, [input]) # ❌ Fails after first call
|
149
159
|
end
|
150
160
|
)
|
151
161
|
|
@@ -226,6 +236,28 @@ advanced> exit
|
|
226
236
|
Goodbye!
|
227
237
|
```
|
228
238
|
|
239
|
+
### ⚠️ Important: Default Handler Implementation
|
240
|
+
|
241
|
+
**Always use direct method calls in default handlers, NOT `invoke()`:**
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
# ✅ CORRECT - Works for repeated calls
|
245
|
+
configure_interactive(
|
246
|
+
default_handler: proc do |input, thor_instance|
|
247
|
+
thor_instance.ask(input) # Direct method call
|
248
|
+
end
|
249
|
+
)
|
250
|
+
|
251
|
+
# ❌ WRONG - Silent failure after first call
|
252
|
+
configure_interactive(
|
253
|
+
default_handler: proc do |input, thor_instance|
|
254
|
+
thor_instance.invoke(:ask, [input]) # Thor's invoke fails silently on repeat calls
|
255
|
+
end
|
256
|
+
)
|
257
|
+
```
|
258
|
+
|
259
|
+
**Why:** Thor's `invoke` method has internal deduplication that prevents repeated calls to the same method on the same instance. This causes silent failures in interactive mode where users expect to be able to repeat commands.
|
260
|
+
|
229
261
|
## Advanced Usage
|
230
262
|
|
231
263
|
### Custom Options
|
@@ -303,13 +335,14 @@ After checking out the repo:
|
|
303
335
|
|
304
336
|
```bash
|
305
337
|
bundle install # Install dependencies
|
306
|
-
bundle exec rspec # Run full test suite
|
338
|
+
bundle exec rspec # Run full test suite with coverage
|
307
339
|
bundle exec rake build # Build gem
|
340
|
+
open coverage/index.html # View coverage report (after running tests)
|
308
341
|
```
|
309
342
|
|
310
343
|
### Testing
|
311
344
|
|
312
|
-
The gem includes comprehensive tests organized into unit and integration test suites
|
345
|
+
The gem includes comprehensive tests organized into unit and integration test suites with **72%+ code coverage**:
|
313
346
|
|
314
347
|
```bash
|
315
348
|
# Run all tests
|
@@ -318,6 +351,9 @@ bundle exec rspec
|
|
318
351
|
# Run with detailed output
|
319
352
|
bundle exec rspec --format documentation
|
320
353
|
|
354
|
+
# View coverage report
|
355
|
+
open coverage/index.html # Detailed HTML coverage report
|
356
|
+
|
321
357
|
# Run specific test suites
|
322
358
|
bundle exec rspec spec/unit/ # Unit tests only
|
323
359
|
bundle exec rspec spec/integration/ # Integration tests only
|
data/examples/README.md
CHANGED
@@ -26,49 +26,55 @@ ruby sample_app.rb interactive
|
|
26
26
|
Once in interactive mode:
|
27
27
|
|
28
28
|
```
|
29
|
-
sample> hello Alice
|
29
|
+
sample> /hello Alice
|
30
30
|
Hello Alice!
|
31
31
|
|
32
|
-
sample> count
|
32
|
+
sample> /count
|
33
33
|
Count: 1
|
34
34
|
|
35
|
-
sample> count
|
35
|
+
sample> /count
|
36
36
|
Count: 2
|
37
37
|
|
38
|
-
sample> add
|
38
|
+
sample> /add First item
|
39
39
|
Added 'First item'. Total items: 1
|
40
40
|
|
41
|
-
sample> add
|
41
|
+
sample> /add Second item
|
42
42
|
Added 'Second item'. Total items: 2
|
43
43
|
|
44
|
-
sample> list
|
44
|
+
sample> /list
|
45
45
|
Items:
|
46
46
|
1. First item
|
47
47
|
2. Second item
|
48
48
|
|
49
|
-
sample> status
|
49
|
+
sample> /status
|
50
50
|
Application Status:
|
51
51
|
Counter: 2
|
52
52
|
Items in list: 2
|
53
53
|
Memory usage: 15234 KB
|
54
54
|
|
55
|
-
sample> This is unrecognized text
|
56
|
-
Echo: This is unrecognized text
|
55
|
+
sample> This is unrecognized text that doesn't need quotes
|
56
|
+
Echo: This is unrecognized text that doesn't need quotes
|
57
57
|
|
58
|
-
sample>
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
58
|
+
sample> What about text with "quotes" and apostrophes?
|
59
|
+
Echo: What about text with "quotes" and apostrophes?
|
60
|
+
|
61
|
+
sample> /help
|
62
|
+
Available commands (prefix with /):
|
63
|
+
/hello Say hello to NAME
|
64
|
+
/count Show and increment counter (demonstrates state persistence)
|
65
|
+
/add Add item to list (demonstrates state persistence)
|
66
|
+
/list Show all items
|
67
|
+
/clear Clear all items
|
68
|
+
/echo Echo the text back (used as default handler)
|
69
|
+
/status Show application status
|
70
|
+
/interactive Start an interactive REPL for this application
|
68
71
|
|
69
72
|
Special commands:
|
70
|
-
help [COMMAND]
|
71
|
-
exit/quit/q
|
73
|
+
/help [COMMAND] Show help for command
|
74
|
+
/exit, /quit, /q Exit the REPL
|
75
|
+
|
76
|
+
Natural language mode:
|
77
|
+
Type anything without / to use default handler
|
72
78
|
|
73
79
|
sample> exit
|
74
80
|
Goodbye!
|
@@ -81,16 +87,17 @@ Goodbye!
|
|
81
87
|
- In normal CLI mode, each command starts fresh
|
82
88
|
|
83
89
|
### 2. Auto-completion
|
84
|
-
- Tab completion works for command names
|
85
|
-
- Try typing
|
90
|
+
- Tab completion works for command names with slash prefix
|
91
|
+
- Try typing `/h<TAB>` or `/co<TAB>` to see completions
|
86
92
|
|
87
|
-
### 3.
|
88
|
-
- Text
|
89
|
-
-
|
93
|
+
### 3. Natural Language Mode
|
94
|
+
- Text without `/` prefix gets sent to the configured default handler
|
95
|
+
- No need to worry about quoting or escaping in natural language
|
96
|
+
- Perfect for LLM interfaces and conversational commands
|
90
97
|
|
91
98
|
### 4. Built-in Help
|
92
|
-
-
|
93
|
-
-
|
99
|
+
- `/help` shows all available commands
|
100
|
+
- `/help COMMAND` shows help for a specific command
|
94
101
|
|
95
102
|
### 5. History
|
96
103
|
- Up/down arrows navigate command history
|
@@ -98,7 +105,7 @@ Goodbye!
|
|
98
105
|
|
99
106
|
### 6. Graceful Exit
|
100
107
|
- Ctrl+C interrupts current operation
|
101
|
-
- Ctrl+D
|
108
|
+
- Ctrl+D, `exit`, `quit`, `q`, `/exit`, `/quit`, or `/q` exits the REPL
|
102
109
|
|
103
110
|
## Integration Patterns
|
104
111
|
|
data/examples/sample_app.rb
CHANGED
@@ -15,7 +15,9 @@ class SampleApp < Thor
|
|
15
15
|
allow_nested: false, # Prevent nested interactive sessions by default
|
16
16
|
default_handler: proc do |input, thor_instance|
|
17
17
|
# Send unrecognized input to the 'echo' command
|
18
|
-
|
18
|
+
# IMPORTANT: Use direct method calls, NOT invoke(), to avoid Thor's
|
19
|
+
# silent deduplication that prevents repeated calls to the same method
|
20
|
+
thor_instance.echo(input)
|
19
21
|
end
|
20
22
|
)
|
21
23
|
|
@@ -38,6 +38,16 @@ class Thor
|
|
38
38
|
def configure_interactive(**options)
|
39
39
|
interactive_options.merge!(options)
|
40
40
|
end
|
41
|
+
|
42
|
+
# Check if currently running in interactive mode
|
43
|
+
def interactive?
|
44
|
+
ENV['THOR_INTERACTIVE_SESSION'] == 'true'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Instance method version for use in commands
|
49
|
+
def interactive?
|
50
|
+
self.class.interactive?
|
41
51
|
end
|
42
52
|
end
|
43
53
|
end
|
@@ -41,6 +41,8 @@ class Thor
|
|
41
41
|
ENV['THOR_INTERACTIVE_SESSION'] = 'true'
|
42
42
|
ENV['THOR_INTERACTIVE_LEVEL'] = (nesting_level + 1).to_s
|
43
43
|
|
44
|
+
puts "(Debug: Interactive session started, level #{nesting_level + 1})" if ENV["DEBUG"]
|
45
|
+
|
44
46
|
# Adjust prompt for nested sessions if configured
|
45
47
|
display_prompt = @prompt
|
46
48
|
if nesting_level > 0 && @merged_options[:nested_prompt_format]
|
@@ -51,20 +53,37 @@ class Thor
|
|
51
53
|
|
52
54
|
show_welcome(nesting_level)
|
53
55
|
|
56
|
+
puts "(Debug: Entering main loop)" if ENV["DEBUG"]
|
57
|
+
|
54
58
|
loop do
|
55
59
|
line = Reline.readline(display_prompt, true)
|
56
|
-
|
60
|
+
puts "(Debug: Got input: #{line.inspect})" if ENV["DEBUG"]
|
61
|
+
|
62
|
+
if should_exit?(line)
|
63
|
+
puts "(Debug: Exit condition met)" if ENV["DEBUG"]
|
64
|
+
break
|
65
|
+
end
|
57
66
|
|
58
67
|
next if line.nil? || line.strip.empty?
|
59
68
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
69
|
+
begin
|
70
|
+
puts "(Debug: Processing input: #{line.strip})" if ENV["DEBUG"]
|
71
|
+
process_input(line.strip)
|
72
|
+
puts "(Debug: Input processed successfully)" if ENV["DEBUG"]
|
73
|
+
rescue Interrupt
|
74
|
+
puts "\n(Interrupted - press Ctrl+D or type 'exit' to quit)"
|
75
|
+
rescue SystemExit => e
|
76
|
+
puts "A command tried to exit with code #{e.status}. Staying in interactive mode."
|
77
|
+
puts "(Debug: SystemExit caught in main loop)" if ENV["DEBUG"]
|
78
|
+
rescue => e
|
79
|
+
puts "Error in main loop: #{e.message}"
|
80
|
+
puts e.backtrace.first(5) if ENV["DEBUG"]
|
81
|
+
puts "(Debug: Error handled, continuing loop)" if ENV["DEBUG"]
|
82
|
+
# Continue the loop - don't let errors break the session
|
83
|
+
end
|
66
84
|
end
|
67
85
|
|
86
|
+
puts "(Debug: Exited main loop)" if ENV["DEBUG"]
|
68
87
|
save_history
|
69
88
|
puts nesting_level > 0 ? "Exiting nested session..." : "Goodbye!"
|
70
89
|
|
@@ -88,12 +107,22 @@ class Thor
|
|
88
107
|
end
|
89
108
|
|
90
109
|
def complete_input(text, preposing)
|
91
|
-
#
|
92
|
-
|
93
|
-
|
110
|
+
# Handle completion for slash commands
|
111
|
+
full_line = preposing + text
|
112
|
+
|
113
|
+
if full_line.start_with?('/')
|
114
|
+
# Command completion mode
|
115
|
+
if preposing.strip == '/' || preposing.strip.empty?
|
116
|
+
# Complete command names with / prefix
|
117
|
+
command_completions = complete_commands(text.sub(/^\//, ''))
|
118
|
+
command_completions.map { |cmd| "/#{cmd}" }
|
119
|
+
else
|
120
|
+
# Complete command arguments (basic implementation)
|
121
|
+
complete_command_options(text, preposing)
|
122
|
+
end
|
94
123
|
else
|
95
|
-
#
|
96
|
-
|
124
|
+
# Natural language mode - no completion for now
|
125
|
+
[]
|
97
126
|
end
|
98
127
|
end
|
99
128
|
|
@@ -114,25 +143,130 @@ class Thor
|
|
114
143
|
# Handle completely empty input
|
115
144
|
return if input.nil? || input.strip.empty?
|
116
145
|
|
117
|
-
|
118
|
-
|
146
|
+
# Check if input starts with / for explicit command mode
|
147
|
+
if input.strip.start_with?('/')
|
148
|
+
# Explicit command mode: /command args
|
149
|
+
handle_slash_command(input.strip[1..-1])
|
150
|
+
elsif is_help_request?(input)
|
151
|
+
# Special case: treat bare "help" as /help for convenience
|
152
|
+
if input.strip.split.length == 1
|
153
|
+
show_help
|
154
|
+
else
|
155
|
+
command_part = input.strip.split[1]
|
156
|
+
show_help(command_part)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
# Determine if this looks like a command or natural language
|
160
|
+
command_word = input.strip.split(/\s+/, 2).first
|
161
|
+
|
162
|
+
if thor_command?(command_word)
|
163
|
+
# Looks like a command - handle it as a command (backward compatibility)
|
164
|
+
handle_command(input.strip)
|
165
|
+
elsif @default_handler
|
166
|
+
# Natural language mode: send whole input to default handler
|
167
|
+
begin
|
168
|
+
@default_handler.call(input, @thor_instance)
|
169
|
+
rescue => e
|
170
|
+
puts "Error in default handler: #{e.message}"
|
171
|
+
puts "Input was: #{input}"
|
172
|
+
puts "Try using /commands or type '/help' for available commands."
|
173
|
+
end
|
174
|
+
else
|
175
|
+
# No default handler, suggest using command mode
|
176
|
+
puts "No default handler configured. Use /command for commands, or type '/help' for available commands."
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
119
180
|
|
120
|
-
|
181
|
+
def handle_slash_command(command_input)
|
182
|
+
return if command_input.empty?
|
183
|
+
handle_command(command_input)
|
184
|
+
end
|
121
185
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
186
|
+
def handle_command(command_input)
|
187
|
+
# Extract command and check if it's a single-text command
|
188
|
+
command_word = command_input.split(/\s+/, 2).first
|
189
|
+
|
190
|
+
if thor_command?(command_word)
|
191
|
+
task = @thor_class.tasks[command_word]
|
192
|
+
|
193
|
+
if task && single_text_command?(task)
|
194
|
+
# Single text command - pass everything after command as one argument
|
195
|
+
text_part = command_input.sub(/^#{Regexp.escape(command_word)}\s*/, '')
|
196
|
+
if text_part.empty?
|
197
|
+
invoke_thor_command(command_word, [])
|
198
|
+
else
|
199
|
+
invoke_thor_command(command_word, [text_part])
|
200
|
+
end
|
201
|
+
else
|
202
|
+
# Multi-argument command, use proper parsing
|
203
|
+
args = safe_parse_input(command_input)
|
204
|
+
if args && !args.empty?
|
205
|
+
command = args.shift
|
206
|
+
invoke_thor_command(command, args)
|
207
|
+
else
|
208
|
+
# Parsing failed, try simple split
|
209
|
+
parts = command_input.split(/\s+/)
|
210
|
+
command = parts.shift
|
211
|
+
invoke_thor_command(command, parts)
|
212
|
+
end
|
213
|
+
end
|
126
214
|
else
|
127
|
-
puts "Unknown command: '#{
|
215
|
+
puts "Unknown command: '#{command_word}'. Type '/help' for available commands."
|
128
216
|
end
|
129
217
|
end
|
130
218
|
|
131
|
-
def
|
219
|
+
def safe_parse_input(input)
|
220
|
+
# Try proper shell parsing first
|
132
221
|
Shellwords.split(input)
|
133
|
-
rescue ArgumentError
|
134
|
-
|
135
|
-
|
222
|
+
rescue ArgumentError
|
223
|
+
# If parsing fails, return nil so caller can handle it
|
224
|
+
nil
|
225
|
+
end
|
226
|
+
|
227
|
+
def parse_input(input)
|
228
|
+
# Legacy method - kept for backward compatibility
|
229
|
+
safe_parse_input(input) || []
|
230
|
+
end
|
231
|
+
|
232
|
+
def handle_unparseable_command(input, command_word)
|
233
|
+
# For commands that failed shell parsing, try intelligent handling
|
234
|
+
task = @thor_class.tasks[command_word]
|
235
|
+
|
236
|
+
# Always try single text approach first for better natural language support
|
237
|
+
text_part = input.strip.sub(/^#{Regexp.escape(command_word)}\s*/, '')
|
238
|
+
if text_part.empty?
|
239
|
+
invoke_thor_command(command_word, [])
|
240
|
+
else
|
241
|
+
invoke_thor_command(command_word, [text_part])
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def single_text_command?(task)
|
246
|
+
# Heuristic: determine if this is likely a single text command
|
247
|
+
return false unless task
|
248
|
+
|
249
|
+
# Check the method signature to see how many parameters it expects
|
250
|
+
method_name = task.name.to_sym
|
251
|
+
if @thor_instance.respond_to?(method_name)
|
252
|
+
method_obj = @thor_instance.method(method_name)
|
253
|
+
param_count = method_obj.parameters.count { |type, _| type == :req }
|
254
|
+
|
255
|
+
# Only single required parameter = likely text command
|
256
|
+
param_count == 1
|
257
|
+
else
|
258
|
+
# Fallback for introspection issues
|
259
|
+
false
|
260
|
+
end
|
261
|
+
rescue
|
262
|
+
# If introspection fails, default to false (safer)
|
263
|
+
false
|
264
|
+
end
|
265
|
+
|
266
|
+
def is_help_request?(input)
|
267
|
+
# Check if input is a help request (help, ?, etc.)
|
268
|
+
stripped = input.strip.downcase
|
269
|
+
stripped == "help" || stripped.start_with?("help ")
|
136
270
|
end
|
137
271
|
|
138
272
|
def thor_command?(command)
|
@@ -146,14 +280,22 @@ class Thor
|
|
146
280
|
if command == "help"
|
147
281
|
show_help(args.first)
|
148
282
|
else
|
149
|
-
#
|
150
|
-
#
|
283
|
+
# Always use direct method calls to avoid Thor's invoke deduplication
|
284
|
+
# Thor's invoke method silently fails on subsequent calls to the same method
|
151
285
|
if @thor_instance.respond_to?(command)
|
152
286
|
@thor_instance.send(command, *args)
|
153
287
|
else
|
154
|
-
|
288
|
+
# If method doesn't exist, this will raise a proper error
|
289
|
+
@thor_instance.send(command, *args)
|
155
290
|
end
|
156
291
|
end
|
292
|
+
rescue SystemExit => e
|
293
|
+
if e.status == 0
|
294
|
+
puts "Command completed successfully (would have exited with code 0 in CLI mode)"
|
295
|
+
else
|
296
|
+
puts "Command failed with exit code #{e.status}"
|
297
|
+
end
|
298
|
+
puts "(Use 'exit' or Ctrl+D to exit the interactive session)" if ENV["DEBUG"]
|
157
299
|
rescue Thor::Error => e
|
158
300
|
puts "Thor Error: #{e.message}"
|
159
301
|
rescue ArgumentError => e
|
@@ -161,21 +303,36 @@ class Thor
|
|
161
303
|
puts "Try: help #{command}" if thor_command?(command)
|
162
304
|
rescue StandardError => e
|
163
305
|
puts "Error: #{e.message}"
|
306
|
+
puts "Command: #{command}, Args: #{args.inspect}" if ENV["DEBUG"]
|
164
307
|
end
|
165
308
|
|
166
309
|
def show_help(command = nil)
|
167
310
|
if command && @thor_class.tasks.key?(command)
|
168
311
|
@thor_class.command_help(Thor::Base.shell.new, command)
|
169
312
|
else
|
170
|
-
puts "Available commands:"
|
313
|
+
puts "Available commands (prefix with /):"
|
171
314
|
@thor_class.tasks.each do |name, task|
|
172
|
-
puts "
|
315
|
+
puts " /#{name.ljust(19)} #{task.description}"
|
173
316
|
end
|
174
317
|
puts
|
175
318
|
puts "Special commands:"
|
176
|
-
puts " help [COMMAND]
|
177
|
-
puts " exit/quit/q
|
319
|
+
puts " /help [COMMAND] Show help for command"
|
320
|
+
puts " /exit, /quit, /q Exit the REPL"
|
178
321
|
puts
|
322
|
+
if @default_handler
|
323
|
+
puts "Natural language mode:"
|
324
|
+
puts " Type anything without / to use default handler"
|
325
|
+
else
|
326
|
+
puts "Use /command syntax for all commands"
|
327
|
+
end
|
328
|
+
puts
|
329
|
+
if ENV["DEBUG"]
|
330
|
+
puts "Debug info:"
|
331
|
+
puts " Thor class: #{@thor_class.name}"
|
332
|
+
puts " Available tasks: #{@thor_class.tasks.keys.sort}"
|
333
|
+
puts " Instance methods: #{@thor_instance.methods.grep(/^[a-z]/).sort}" if @thor_instance
|
334
|
+
puts
|
335
|
+
end
|
179
336
|
end
|
180
337
|
end
|
181
338
|
|
@@ -183,7 +340,8 @@ class Thor
|
|
183
340
|
return true if line.nil? # Ctrl+D
|
184
341
|
|
185
342
|
stripped = line.strip.downcase
|
186
|
-
|
343
|
+
# Handle both /exit and exit for convenience
|
344
|
+
EXIT_COMMANDS.include?(stripped) || EXIT_COMMANDS.include?(stripped.sub(/^\//, ''))
|
187
345
|
end
|
188
346
|
|
189
347
|
def show_welcome(nesting_level = 0)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thor-interactive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.pre.
|
4
|
+
version: 0.1.0.pre.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Petersen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-09-
|
11
|
+
date: 2025-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|