philiprehberger-cli_kit 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +71 -10
- data/lib/philiprehberger/cli_kit/menu.rb +41 -0
- data/lib/philiprehberger/cli_kit/parser.rb +18 -6
- data/lib/philiprehberger/cli_kit/prompt.rb +60 -0
- data/lib/philiprehberger/cli_kit/version.rb +1 -1
- data/lib/philiprehberger/cli_kit.rb +35 -0
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 15240e9c9eb6ad374dafd6457c91e3d1514b34031ccb57f00b4283b266bf3575
|
|
4
|
+
data.tar.gz: 77fd549d9211ab095433cebe4938db8fd1188e1cffb124c19c91a77e5ce428a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b18bf81377d13116b703b5938d8e0b6c84e18272570d1f0eb2b598f125d80d0f603298735445873445882e073d504c25694313c7604157b907817c908b71417f
|
|
7
|
+
data.tar.gz: 636957466367093c8cdd87305abcffb8523633182e513223f50898d876e3c93e2d3dab4c0a1290350c344d8f6d04d53f66cce5b097b609990e4907f10a54d931
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-04-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Repeatable options via `option :name, multi: true` — collects each occurrence into an array
|
|
14
|
+
- `CliKit.password(message)` — prompt that reads input without echoing when stdin is a TTY (falls back to plain `gets` otherwise)
|
|
15
|
+
- `CliKit.ask(message)` — prompt that re-asks until a validator block returns truthy (defaults to any non-empty answer)
|
|
16
|
+
- `CliKit.multi_select(message, choices, defaults:)` — numbered menu supporting comma- or space-separated multi-selection
|
|
17
|
+
- Help text now shows `VALUE (repeatable)` for `multi: true` options
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- VERSION spec no longer hardcodes the version string; asserts semver format instead
|
|
21
|
+
|
|
22
|
+
## [0.2.1] - 2026-03-31
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Standardize README badges, support section, and license format
|
|
26
|
+
|
|
10
27
|
## [0.2.0] - 2026-03-30
|
|
11
28
|
|
|
12
29
|
### Added
|
|
@@ -40,3 +57,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
40
57
|
- Yes/no confirmation prompt
|
|
41
58
|
- Animated spinner for long-running operations
|
|
42
59
|
- Positional argument collection
|
|
60
|
+
|
|
61
|
+
[Unreleased]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.3.0...HEAD
|
|
62
|
+
[0.3.0]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.2.1...v0.3.0
|
|
63
|
+
[0.2.1]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.2.0...v0.2.1
|
|
64
|
+
[0.2.0]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.1.2...v0.2.0
|
|
65
|
+
[0.1.2]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.1.1...v0.1.2
|
|
66
|
+
[0.1.1]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.1.0...v0.1.1
|
|
67
|
+
[0.1.0]: https://github.com/philiprehberger/rb-cli-kit/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/philiprehberger/rb-cli-kit/actions/workflows/ci.yml)
|
|
4
4
|
[](https://rubygems.org/gems/philiprehberger-cli_kit)
|
|
5
|
-
[](https://github.com/philiprehberger/rb-cli-kit/releases)
|
|
6
5
|
[](https://github.com/philiprehberger/rb-cli-kit/commits/main)
|
|
7
|
-
[](LICENSE)
|
|
8
|
-
[](https://github.com/philiprehberger/rb-cli-kit/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
|
9
|
-
[](https://github.com/philiprehberger/rb-cli-kit/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
|
10
|
-
[](https://github.com/sponsors/philiprehberger)
|
|
11
6
|
|
|
12
7
|
All-in-one CLI toolkit with argument parsing, prompts, and spinners
|
|
13
8
|
|
|
@@ -90,6 +85,37 @@ confirmed = Philiprehberger::CliKit.confirm('Continue?')
|
|
|
90
85
|
# Continue? [y/n] _
|
|
91
86
|
```
|
|
92
87
|
|
|
88
|
+
### Password Prompt
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
secret = Philiprehberger::CliKit.password('Enter password:')
|
|
92
|
+
# Enter password: _
|
|
93
|
+
# Input is read without echoing to the terminal when stdin is a TTY.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Validated Ask
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Without a block, any non-empty answer is accepted.
|
|
100
|
+
name = Philiprehberger::CliKit.ask('Name:')
|
|
101
|
+
|
|
102
|
+
# With a block, the prompt repeats until the block returns a truthy value.
|
|
103
|
+
port = Philiprehberger::CliKit.ask('Port:', error: 'Must be a number') do |answer|
|
|
104
|
+
answer.match?(/\A\d+\z/)
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Repeatable Options
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
result = Philiprehberger::CliKit.parse(ARGV) do
|
|
112
|
+
option :tag, short: :t, multi: true, desc: 'Add a tag (repeatable)'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Invoked as: mycli --tag ruby --tag cli -t kit
|
|
116
|
+
result.options[:tag] # => ["ruby", "cli", "kit"]
|
|
117
|
+
```
|
|
118
|
+
|
|
93
119
|
### Menu Selection
|
|
94
120
|
|
|
95
121
|
```ruby
|
|
@@ -108,6 +134,26 @@ env = Philiprehberger::CliKit.select('Choose env:', %w[dev staging prod], defaul
|
|
|
108
134
|
# Choose [2]: _
|
|
109
135
|
```
|
|
110
136
|
|
|
137
|
+
### Multi-Select Menu
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
tags = Philiprehberger::CliKit.multi_select('Pick tags:', %w[ruby cli dsl testing])
|
|
141
|
+
# Pick tags:
|
|
142
|
+
# 1) ruby
|
|
143
|
+
# 2) cli
|
|
144
|
+
# 3) dsl
|
|
145
|
+
# 4) testing
|
|
146
|
+
# Choose (comma-separated): 1,3
|
|
147
|
+
# => ["ruby", "dsl"]
|
|
148
|
+
|
|
149
|
+
tags = Philiprehberger::CliKit.multi_select('Pick tags:', %w[ruby cli dsl], defaults: %w[ruby dsl])
|
|
150
|
+
# Pick tags:
|
|
151
|
+
# * 1) ruby
|
|
152
|
+
# 2) cli
|
|
153
|
+
# * 3) dsl
|
|
154
|
+
# Choose (comma-separated): _ # empty answer => defaults
|
|
155
|
+
```
|
|
156
|
+
|
|
111
157
|
### Spinners
|
|
112
158
|
|
|
113
159
|
```ruby
|
|
@@ -124,10 +170,14 @@ end
|
|
|
124
170
|
| `.parse(args) { ... }` | Parse arguments with flag/option/command DSL |
|
|
125
171
|
| `.prompt(message)` | Display prompt and read input |
|
|
126
172
|
| `.confirm(message)` | Display yes/no confirmation |
|
|
127
|
-
| `.
|
|
173
|
+
| `.password(message)` | Read input without echoing to the terminal |
|
|
174
|
+
| `.ask(message) { \|answer\| ... }` | Prompt until block returns truthy (defaults to non-empty) |
|
|
175
|
+
| `.select(message, choices)` | Present numbered menu and return one selection |
|
|
176
|
+
| `.multi_select(message, choices, defaults:)` | Present numbered menu and return multiple selections |
|
|
128
177
|
| `.spinner(message) { ... }` | Show spinner during block execution |
|
|
178
|
+
| `Parser#option(name, multi: true)` | Collect repeated option values into an array |
|
|
129
179
|
| `Parser#flags` | Hash of boolean flag values |
|
|
130
|
-
| `Parser#options` | Hash of option values |
|
|
180
|
+
| `Parser#options` | Hash of option values (arrays when `multi: true`) |
|
|
131
181
|
| `Parser#arguments` | Array of positional arguments |
|
|
132
182
|
| `Parser#command` | Matched subcommand name or nil |
|
|
133
183
|
| `Parser#help_text` | Formatted help string |
|
|
@@ -143,10 +193,21 @@ bundle exec rubocop
|
|
|
143
193
|
|
|
144
194
|
## Support
|
|
145
195
|
|
|
146
|
-
If you find this
|
|
196
|
+
If you find this project useful:
|
|
197
|
+
|
|
198
|
+
⭐ [Star the repo](https://github.com/philiprehberger/rb-cli-kit)
|
|
199
|
+
|
|
200
|
+
🐛 [Report issues](https://github.com/philiprehberger/rb-cli-kit/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
|
201
|
+
|
|
202
|
+
💡 [Suggest features](https://github.com/philiprehberger/rb-cli-kit/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
|
203
|
+
|
|
204
|
+
❤️ [Sponsor development](https://github.com/sponsors/philiprehberger)
|
|
205
|
+
|
|
206
|
+
🌐 [All Open Source Projects](https://philiprehberger.com/open-source-packages)
|
|
207
|
+
|
|
208
|
+
💻 [GitHub Profile](https://github.com/philiprehberger)
|
|
147
209
|
|
|
148
|
-
[
|
|
149
|
-
[](https://philiprehberger.com/open-source-packages)
|
|
210
|
+
🔗 [LinkedIn Profile](https://www.linkedin.com/in/philiprehberger)
|
|
150
211
|
|
|
151
212
|
## License
|
|
152
213
|
|
|
@@ -43,6 +43,47 @@ module Philiprehberger
|
|
|
43
43
|
choices.first
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
# Present a numbered menu and allow multiple selections.
|
|
48
|
+
#
|
|
49
|
+
# The user enters a comma- or space-separated list of numbers (e.g.
|
|
50
|
+
# "1,3" or "1 3"). Unknown or out-of-range entries are ignored, and
|
|
51
|
+
# duplicates are collapsed. An empty answer returns +defaults+ or an
|
|
52
|
+
# empty array if no defaults are given.
|
|
53
|
+
#
|
|
54
|
+
# @param message [String] the prompt message
|
|
55
|
+
# @param choices [Array<String>] the list of choices
|
|
56
|
+
# @param defaults [Array<String>] choices pre-selected by default
|
|
57
|
+
# @param input [IO] input stream (default: $stdin)
|
|
58
|
+
# @param output [IO] output stream (default: $stdout)
|
|
59
|
+
# @return [Array<String>] the selected values in choice order
|
|
60
|
+
# @raise [ArgumentError] if choices is empty
|
|
61
|
+
def self.multi_select(message, choices, defaults: [], input: $stdin, output: $stdout)
|
|
62
|
+
raise ArgumentError, 'choices must not be empty' if choices.empty?
|
|
63
|
+
|
|
64
|
+
default_indexes = defaults.map { |d| choices.index(d) }.compact
|
|
65
|
+
|
|
66
|
+
output.puts message
|
|
67
|
+
choices.each_with_index do |choice, idx|
|
|
68
|
+
marker = default_indexes.include?(idx) ? '*' : ' '
|
|
69
|
+
output.puts " #{marker} #{idx + 1}) #{choice}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
output.print 'Choose (comma-separated): '
|
|
73
|
+
output.flush
|
|
74
|
+
|
|
75
|
+
answer = input.gets&.strip || ''
|
|
76
|
+
return defaults.dup if answer.empty?
|
|
77
|
+
|
|
78
|
+
picks = answer.split(/[,\s]+/).filter_map do |token|
|
|
79
|
+
idx = Integer(token, 10) - 1
|
|
80
|
+
idx if idx >= 0 && idx < choices.length
|
|
81
|
+
rescue ArgumentError
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
picks.uniq.sort.map { |i| choices[i] }
|
|
86
|
+
end
|
|
46
87
|
end
|
|
47
88
|
end
|
|
48
89
|
end
|
|
@@ -34,10 +34,11 @@ module Philiprehberger
|
|
|
34
34
|
# @param short [Symbol, nil] short alias (single character)
|
|
35
35
|
# @param default [Object, nil] default value
|
|
36
36
|
# @param desc [String, nil] description for help text
|
|
37
|
+
# @param multi [Boolean] when true, collect repeated values into an array
|
|
37
38
|
# @return [void]
|
|
38
|
-
def option(name, short: nil, default: nil, desc: nil)
|
|
39
|
-
@option_definitions[name] = { short: short, default: default, desc: desc }
|
|
40
|
-
@options[name] = default
|
|
39
|
+
def option(name, short: nil, default: nil, desc: nil, multi: false)
|
|
40
|
+
@option_definitions[name] = { short: short, default: default, desc: desc, multi: multi }
|
|
41
|
+
@options[name] = multi ? [] : default
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
# Define a subcommand or return the matched command name.
|
|
@@ -147,7 +148,7 @@ module Philiprehberger
|
|
|
147
148
|
if @flag_definitions.key?(name)
|
|
148
149
|
@flags[name] = true
|
|
149
150
|
elsif @option_definitions.key?(name)
|
|
150
|
-
|
|
151
|
+
assign_option(name, args.shift)
|
|
151
152
|
end
|
|
152
153
|
end
|
|
153
154
|
|
|
@@ -163,12 +164,22 @@ module Philiprehberger
|
|
|
163
164
|
|
|
164
165
|
@option_definitions.each do |name, defn|
|
|
165
166
|
if defn[:short] == char
|
|
166
|
-
|
|
167
|
+
assign_option(name, args.shift)
|
|
167
168
|
return
|
|
168
169
|
end
|
|
169
170
|
end
|
|
170
171
|
end
|
|
171
172
|
|
|
173
|
+
def assign_option(name, value)
|
|
174
|
+
defn = @option_definitions[name]
|
|
175
|
+
if defn && defn[:multi]
|
|
176
|
+
@options[name] = [] unless @options[name].is_a?(Array)
|
|
177
|
+
@options[name] << value unless value.nil?
|
|
178
|
+
else
|
|
179
|
+
@options[name] = value
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
172
183
|
def format_flag_help(name, defn)
|
|
173
184
|
long = "--#{name.to_s.tr('_', '-')}"
|
|
174
185
|
if defn[:short]
|
|
@@ -185,7 +196,8 @@ module Philiprehberger
|
|
|
185
196
|
end
|
|
186
197
|
|
|
187
198
|
def format_option_help(name, defn)
|
|
188
|
-
|
|
199
|
+
placeholder = defn[:multi] ? 'VALUE (repeatable)' : 'VALUE'
|
|
200
|
+
long = "--#{name.to_s.tr('_', '-')} #{placeholder}"
|
|
189
201
|
if defn[:short]
|
|
190
202
|
short = "-#{defn[:short]}"
|
|
191
203
|
label = " #{short}, #{long}"
|
|
@@ -28,6 +28,66 @@ module Philiprehberger
|
|
|
28
28
|
answer = input.gets&.strip&.downcase || ''
|
|
29
29
|
%w[y yes].include?(answer)
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
# Display a prompt and read input without echoing characters to the terminal.
|
|
33
|
+
#
|
|
34
|
+
# When the input stream responds to +noecho+ (a real TTY), echo is disabled
|
|
35
|
+
# for the duration of the read. When it does not (e.g. a +StringIO+ during
|
|
36
|
+
# tests), input is read normally. A trailing newline is printed to the
|
|
37
|
+
# output after the read so the next prompt starts on its own line.
|
|
38
|
+
#
|
|
39
|
+
# @param message [String] the prompt message
|
|
40
|
+
# @param input [IO] input stream (default: $stdin)
|
|
41
|
+
# @param output [IO] output stream (default: $stdout)
|
|
42
|
+
# @return [String] the user's input, stripped of whitespace
|
|
43
|
+
def self.password(message, input: $stdin, output: $stdout)
|
|
44
|
+
output.print "#{message} "
|
|
45
|
+
output.flush
|
|
46
|
+
|
|
47
|
+
raw = if input.respond_to?(:noecho)
|
|
48
|
+
begin
|
|
49
|
+
input.noecho(&:gets)
|
|
50
|
+
rescue IOError, Errno::ENOTTY
|
|
51
|
+
input.gets
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
input.gets
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
output.print "\n"
|
|
58
|
+
output.flush
|
|
59
|
+
raw&.strip || ''
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Display a prompt and read input, repeating until it passes validation.
|
|
63
|
+
#
|
|
64
|
+
# The block is called with the stripped input; when it returns a truthy
|
|
65
|
+
# value the input is accepted and returned. When it returns a falsy value
|
|
66
|
+
# an optional +error+ message is printed and the user is prompted again.
|
|
67
|
+
# When no block is given, any non-empty input is accepted.
|
|
68
|
+
#
|
|
69
|
+
# @param message [String] the prompt message
|
|
70
|
+
# @param error [String] the message shown on invalid input
|
|
71
|
+
# @param input [IO] input stream (default: $stdin)
|
|
72
|
+
# @param output [IO] output stream (default: $stdout)
|
|
73
|
+
# @yieldparam answer [String] the stripped user input
|
|
74
|
+
# @yieldreturn [Boolean] whether the answer is acceptable
|
|
75
|
+
# @return [String] the accepted user input
|
|
76
|
+
def self.ask(message, error: 'Invalid input, please try again.', input: $stdin, output: $stdout, &block)
|
|
77
|
+
validator = block || ->(answer) { !answer.empty? }
|
|
78
|
+
|
|
79
|
+
loop do
|
|
80
|
+
output.print "#{message} "
|
|
81
|
+
output.flush
|
|
82
|
+
raw = input.gets
|
|
83
|
+
return '' if raw.nil?
|
|
84
|
+
|
|
85
|
+
answer = raw.strip
|
|
86
|
+
return answer if validator.call(answer)
|
|
87
|
+
|
|
88
|
+
output.puts error
|
|
89
|
+
end
|
|
90
|
+
end
|
|
31
91
|
end
|
|
32
92
|
end
|
|
33
93
|
end
|
|
@@ -49,6 +49,29 @@ module Philiprehberger
|
|
|
49
49
|
Prompt.confirm(message, input: input, output: output)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Display a prompt and read input without echoing to the terminal.
|
|
53
|
+
#
|
|
54
|
+
# @param message [String] the prompt message
|
|
55
|
+
# @param input [IO] input stream
|
|
56
|
+
# @param output [IO] output stream
|
|
57
|
+
# @return [String] the user's input
|
|
58
|
+
def self.password(message, input: $stdin, output: $stdout)
|
|
59
|
+
Prompt.password(message, input: input, output: output)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Display a prompt, re-asking until the answer satisfies the given block.
|
|
63
|
+
#
|
|
64
|
+
# @param message [String] the prompt message
|
|
65
|
+
# @param error [String] error message shown on invalid input
|
|
66
|
+
# @param input [IO] input stream
|
|
67
|
+
# @param output [IO] output stream
|
|
68
|
+
# @yieldparam answer [String] the stripped user input
|
|
69
|
+
# @yieldreturn [Boolean] whether the answer is acceptable
|
|
70
|
+
# @return [String] the accepted user input
|
|
71
|
+
def self.ask(message, error: 'Invalid input, please try again.', input: $stdin, output: $stdout, &block)
|
|
72
|
+
Prompt.ask(message, error: error, input: input, output: output, &block)
|
|
73
|
+
end
|
|
74
|
+
|
|
52
75
|
# Display a spinner while executing a block.
|
|
53
76
|
#
|
|
54
77
|
# @param message [String] the spinner message
|
|
@@ -70,5 +93,17 @@ module Philiprehberger
|
|
|
70
93
|
def self.select(message, choices, default: nil, input: $stdin, output: $stdout)
|
|
71
94
|
Menu.select(message, choices, default: default, input: input, output: output)
|
|
72
95
|
end
|
|
96
|
+
|
|
97
|
+
# Present a numbered menu allowing multiple selections and return the selected values.
|
|
98
|
+
#
|
|
99
|
+
# @param message [String] the prompt message
|
|
100
|
+
# @param choices [Array<String>] the list of choices
|
|
101
|
+
# @param defaults [Array<String>] pre-selected default choices
|
|
102
|
+
# @param input [IO] input stream
|
|
103
|
+
# @param output [IO] output stream
|
|
104
|
+
# @return [Array<String>] the selected values
|
|
105
|
+
def self.multi_select(message, choices, defaults: [], input: $stdin, output: $stdout)
|
|
106
|
+
Menu.multi_select(message, choices, defaults: defaults, input: input, output: output)
|
|
107
|
+
end
|
|
73
108
|
end
|
|
74
109
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-cli_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Lightweight CLI toolkit combining argument parsing with flags and options,
|
|
14
14
|
interactive prompts with confirmation, and animated spinners for long-running operations.
|
|
@@ -27,11 +27,11 @@ files:
|
|
|
27
27
|
- lib/philiprehberger/cli_kit/prompt.rb
|
|
28
28
|
- lib/philiprehberger/cli_kit/spinner.rb
|
|
29
29
|
- lib/philiprehberger/cli_kit/version.rb
|
|
30
|
-
homepage: https://
|
|
30
|
+
homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-cli_kit
|
|
31
31
|
licenses:
|
|
32
32
|
- MIT
|
|
33
33
|
metadata:
|
|
34
|
-
homepage_uri: https://
|
|
34
|
+
homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-cli_kit
|
|
35
35
|
source_code_uri: https://github.com/philiprehberger/rb-cli-kit
|
|
36
36
|
changelog_uri: https://github.com/philiprehberger/rb-cli-kit/blob/main/CHANGELOG.md
|
|
37
37
|
bug_tracker_uri: https://github.com/philiprehberger/rb-cli-kit/issues
|