inquirex-tty 0.5.0 → 0.9.6

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.
@@ -0,0 +1,93 @@
1
+ Inquirex.define do
2
+ start :do_you_have_a_minute
3
+
4
+ confirm :do_you_have_a_minute do
5
+ type :boolean
6
+ question "Do you you have a few minutes to help us understand how you use the site, what's working and what needs fixing?"
7
+ widget type: :toggle
8
+ widget target: :mobile, type: :yes_no_buttons
9
+ transition to: :name, if_rule: equals(:do_you_have_a_minute, true)
10
+ transition to: :no_problem
11
+ end
12
+
13
+ ask :name do
14
+ type :string
15
+ question "What is your name? However you'd like to be called."
16
+ widget type: :text_input
17
+ widget target: :mobile, type: :text_input
18
+ transition to: :email
19
+ end
20
+
21
+ ask :email do
22
+ type :email
23
+ question "Please share with us your email address so that we can notify you when your ideas and feedback has been put to good use."
24
+ widget type: :email_input
25
+ widget target: :mobile, type: :email_input
26
+ transition to: :other_tools
27
+ end
28
+
29
+ say :no_problem do
30
+ text "Not a problem. If you ever change your mind, just hit that round circle and answer the quesitons,. Adios!"
31
+ end
32
+
33
+ ask :other_tools do
34
+ type :boolean
35
+ question "Do you use any other online tools for generating laser cuts or engraving?"
36
+ default false
37
+ transition to: :uses_other_tools, if_rule: equals(:other_tools, true)
38
+ transition to: :how_long_you_used_makeabox
39
+ end
40
+
41
+ ask :uses_other_tools do
42
+ type :multi_enum
43
+ question "May we ask which tools do you use to building enclosures cut with a laser cutter? If the tool you use is not listed, choose Unlisted."
44
+ options i_dont: "I don't use Any of Them",
45
+ makeabox: "MakeABox.io (this site)",
46
+ makercase: "Makercase",
47
+ boxes_py: "Boxes.py",
48
+ www_atomm_com: "Atomm.com",
49
+ vector_painter: "Vector Painter.com",
50
+ laserbiz_ru: "Laserbiz.ru",
51
+ hessmer_org_gears: "hessmer.org/gears",
52
+ unlisted: "Unlisted (Other)"
53
+ transition to: :makeabox_only, if_rule: contains(:uses_other_tools, "makeabox")
54
+ transition to: :other, if_rule: contains(:uses_other_tools, "unlisted")
55
+ transition to: :feedback
56
+ end
57
+
58
+ say :makeabox_only do
59
+ text "Thanks for using MakeABox only :) We hope to improve this application futher and we appreciate your choice, considering there are a few out there :)"
60
+ transition to: :how_long_you_used_makeabox
61
+ end
62
+
63
+ ask :how_long_you_used_makeabox do
64
+ type :enum
65
+ question "Give us a sense about how long have you been used MakeABox.io..."
66
+ options first_time: "My First TIme",
67
+ about_a_month: "About a Month",
68
+ a_few_months: "A few Months",
69
+ at_least_a_year: "At least a Year",
70
+ over_a_year: "Over a Year",
71
+ over_two_years: "Over two Years"
72
+ default :first_time
73
+ transition to: :other, if_rule: contains(:uses_other_tools, "unlisted")
74
+ transition to: :feedback
75
+ end
76
+
77
+ ask :other do
78
+ type :text
79
+ widget type: :text_input
80
+ question "Can you list the tools not listed in the previous question?"
81
+ transition to: :feedback
82
+ end
83
+
84
+ ask :feedback do
85
+ question "Pelase provide any feedback or bug reports that will make MakeABox.io better."
86
+ type :text
87
+ transition to: :thanks
88
+ end
89
+
90
+ say :thanks do
91
+ text "We appreciate you taking the time to answer some of the questions! See you around!"
92
+ end
93
+ end
data/exe/inqt ADDED
@@ -0,0 +1 @@
1
+ inquirex-tty
data/justfile CHANGED
@@ -4,7 +4,26 @@ set shell := ["bash", "-c"]
4
4
 
5
5
  repo := 'git@github.com:inquirex/inquirex-tty.git'
6
6
  version := `grep VERSION lib/inquirex/tty/version.rb | awk '{print $3}' | tr -d '"' | tr -d '\n'`
7
- rbenv := 'eval "$(rbenv init bash)"; bundle exec '
7
+ # The `-` matters: `rbenv init bash` prints human instructions ("skipping
8
+ # ~/.bash_login: already configured"), which eval then tries to run and the
9
+ # recipe dies with "skipping: command not found". `rbenv init - bash` prints
10
+ # the shell code that is meant to be eval'd.
11
+ rbenv := 'eval "$(rbenv init - bash 2>/dev/null || true)"; bundle exec '
12
+
13
+ # 1Password secret reference for the RubyGems TOTP, and the full path to `op`
14
+ # because a recipe does not inherit an interactive shell's PATH.
15
+ # The account is NOT named here: it is a private 1Password address and this
16
+ # file is public. `op` reads it from OP_ACCOUNT, exported by the ecosystem
17
+ # root's .envrc, which lives outside every repo. Two accounts are
18
+ # registered and only one holds the open-source-repos vault, so without
19
+ # OP_ACCOUNT the read can resolve against the wrong one, return nothing,
20
+ # and publish silently falls back to prompting mid-release.
21
+ op := '/opt/homebrew/bin/op'
22
+ otp_ref := 'op://open-source-repos/ruby-gems/one-time password?attribute=otp'
23
+
24
+ gem_name := 'inquirex-tty'
25
+ gem_file := 'pkg/' + gem_name + '-' + version + '.gem'
26
+ gem_url := 'https://rubygems.org/gems/' + gem_name
8
27
 
9
28
  [no-exit-message]
10
29
  recipes:
@@ -29,15 +48,15 @@ format:
29
48
 
30
49
  # Run a flow interactively
31
50
  run flow_file:
32
- {{ rbenv }} exe/inquirex-tty run {{flow_file}}
51
+ {{ rbenv }} exe/inqt run {{flow_file}}
33
52
 
34
53
  # Validate a flow definition
35
54
  validate flow_file:
36
- {{ rbenv }} exe/inquirex-tty validate {{flow_file}}
55
+ {{ rbenv }} exe/inqt validate {{flow_file}}
37
56
 
38
57
  # Export a flow as a Mermaid diagram (stdout)
39
58
  graph flow_file:
40
- {{ rbenv }} exe/inquirex-tty graph {{flow_file}}
59
+ {{ rbenv }} exe/inqt graph {{flow_file}}
41
60
 
42
61
  # Validate all examples
43
62
  examples:
@@ -45,7 +64,7 @@ examples:
45
64
  set -e
46
65
  for f in examples/*.rb; do
47
66
  echo "=== $f ==="
48
- {{ rbenv }} exe/inquirex validate "$f"
67
+ {{ rbenv }} exe/inqt validate "$f"
49
68
  echo ""
50
69
  done
51
70
 
@@ -59,12 +78,45 @@ clean:
59
78
  /usr/bin/find . -name .DS_Store -delete -print || true
60
79
  rm -rf tmp/*
61
80
 
62
- # Create
63
- publish: build
64
- {{ rbenv }} rake release[remote]
65
-
66
- version:
67
- @echo "{{ version }}"
81
+ # `gem push` rather than `rake release`: release also guards the tree, tags and
82
+ # pushes git — which `just release` does deliberately and separately — and it
83
+ # gives no way to pass a 2FA code, so it always stopped to prompt.
84
+ #
85
+ # The code comes from 1Password unless one is passed in:
86
+ #
87
+ # just publish # read the code from 1Password
88
+ # just publish 123456 # use this code
89
+ #
90
+ # `just publish-all` in inquirex-tools passes one, because a TOTP is single-use:
91
+ # four gems reading the same 30-second window would have the second push
92
+ # rejected as a replay.
93
+ #
94
+ # Build the .gem and push it to RubyGems, non-interactively
95
+ publish otp="": build
96
+ #!/usr/bin/env bash
97
+ set -euo pipefail
98
+ eval "$(rbenv init - bash 2>/dev/null || true)"
99
+
100
+ mkdir -p pkg
101
+ gem build {{ gem_name }}.gemspec --output "{{ gem_file }}"
102
+
103
+ # `|| true` is load-bearing: under `set -e` a failed `op read` — not signed
104
+ # in to 1Password, item renamed, op not installed — would abort the recipe
105
+ # before the prompting fallback below could run.
106
+ otp="{{ otp }}"
107
+ [[ -n "${otp}" ]] || otp=$({{ op }} read "{{ otp_ref }}" 2>/dev/null || true)
108
+
109
+ if [[ -n "${otp}" ]]; then
110
+ gem push "{{ gem_file }}" --otp "${otp}"
111
+ else
112
+ echo "rubygems: no OTP available — gem push will prompt if 2FA is required."
113
+ gem push "{{ gem_file }}"
114
+ fi
115
+
116
+ # Only reachable when the push succeeded: `set -e` aborts the recipe on a
117
+ # non-zero `gem push`, so the page never opens for a release that failed.
118
+ echo "published {{ gem_name }} {{ version }} → {{ gem_url }}"
119
+ open "{{ gem_url }}" 2>/dev/null || xdg-open "{{ gem_url }}" 2>/dev/null || true
68
120
 
69
121
  # Tag v{{ version }}, publish the GH release, & refresh the Homebrew tap.
70
122
  release:
@@ -12,9 +12,11 @@ module Inquirex
12
12
  # inquirex export examples/08_tax_preparer.rb -f yml -o . # write 08_tax_preparer.yml to cwd
13
13
  # inquirex export examples/08_tax_preparer.rb -o out.json # write to out.json
14
14
  class Export < Dry::CLI::Command
15
+ # Multi-line help text shown by `inquirex export --help`.
15
16
  LONG_DESCRIPTION = "Export a flow definition as JSON or YAML.\n\n" \
16
17
  "Example:\n inquirex export examples/08_tax_preparer.rb -f yml -o ."
17
18
 
19
+ # One-line summary shown in the top-level command listing.
18
20
  SHORT_DESCRIPTION = "Export a flow definition as JSON or YAML ."
19
21
 
20
22
  if ARGV[0] == "export"
@@ -44,7 +46,7 @@ module Inquirex
44
46
  content = serialize(definition, format)
45
47
  write(content, flow_file, options[:output], extension_for(format))
46
48
  rescue Inquirex::TTY::Error => e
47
- warn "Error: #{e.message}"
49
+ $stderr.puts "Error: #{e.message}"
48
50
  exit 1
49
51
  end
50
52
 
@@ -73,7 +75,7 @@ module Inquirex
73
75
  end
74
76
 
75
77
  File.write(path, content)
76
- warn "Exported to #{path}"
78
+ $stderr.puts "Exported to #{path}"
77
79
  end
78
80
  end
79
81
  end
@@ -5,11 +5,13 @@ module Inquirex
5
5
  module Commands
6
6
  # Exports a flow definition as a Mermaid flowchart (stdout or file).
7
7
  class Graph < Dry::CLI::Command
8
+ # Multi-line help text shown by `inquirex graph --help`.
8
9
  LONG_DESCRIPTION = "Export a flow definition as a Mermaid diagram source, an image, or both.\n " \
9
10
  "Image generation requires mermaid-cli (npm install -g @mermaid-js/mermaid-cli)\n " \
10
11
  "which this gem will attempt to install for you if mmdc command is not available.\n\n" \
11
12
  "Example:\n inquirex graph qualify_dsl.rb --format both --output ~/Desktop --open"
12
13
 
14
+ # One-line summary shown in the top-level command listing.
13
15
  SHORT_DESCRIPTION = "Export a flow definition as a Mermaid diagram source, an image, or both."
14
16
 
15
17
  if ARGV[0] == "graph"
@@ -54,7 +56,7 @@ module Inquirex
54
56
  write_image(source, OutputPath.resolve_with_default(flow_file, output, ".png"), options[:open])
55
57
  end
56
58
  rescue Inquirex::TTY::Error => e
57
- warn "Error: #{e.message}"
59
+ $stderr.puts "Error: #{e.message}"
58
60
  exit 1
59
61
  end
60
62
 
@@ -66,7 +68,7 @@ module Inquirex
66
68
  return
67
69
  end
68
70
  File.write(output_path, source)
69
- warn "Diagram written to #{output_path}"
71
+ $stderr.puts "Diagram written to #{output_path}"
70
72
  end
71
73
 
72
74
  def write_image(source, output_path, open_file)
@@ -81,14 +83,14 @@ module Inquirex
81
83
  )
82
84
  end
83
85
 
84
- warn "Diagram written to #{output_path}"
86
+ $stderr.puts "Diagram written to #{output_path}"
85
87
  open_image_file(output_path) if open_file
86
88
  end
87
89
 
88
90
  def ensure_mermaid_cli_installed!
89
91
  return if command_available?("mmdc")
90
92
 
91
- warn "Installing @mermaid-js/mermaid-cli..."
93
+ $stderr.puts "Installing @mermaid-js/mermaid-cli..."
92
94
  installed = system("npm install -g @mermaid-js/mermaid-cli")
93
95
  return if installed && command_available?("mmdc")
94
96
 
@@ -22,7 +22,7 @@ module Inquirex
22
22
  File.write(options[:output], json)
23
23
  puts "\nResults saved to #{options[:output]}"
24
24
  else
25
- $stderr.puts json # rubocop:disable Style/StderrPuts
25
+ $stderr.puts json
26
26
  end
27
27
  rescue Inquirex::TTY::Error => e
28
28
  error(e.message)
@@ -43,6 +43,8 @@ module Inquirex
43
43
  renderer = Renderer.new
44
44
  adapter = build_llm_adapter
45
45
 
46
+ engine.after_completion { |eng| eng.completion_metadata = build_completion_metadata }
47
+
46
48
  show_banner(definition)
47
49
 
48
50
  until engine.finished?
@@ -55,7 +57,8 @@ module Inquirex
55
57
  renderer.render(step)
56
58
  engine.advance
57
59
  else
58
- engine.answer(renderer.render(step))
60
+ suggestion = engine.suggestion_for(engine.current_step_id)
61
+ engine.answer(renderer.render(step, suggestion:))
59
62
  end
60
63
  end
61
64
 
@@ -183,11 +186,56 @@ module Inquirex
183
186
  {
184
187
  flow_file: flow_file,
185
188
  path_taken: engine.history,
186
- answers: engine.answers,
189
+ answers: engine.answers_with_metadata,
187
190
  steps_completed: engine.history.length,
188
191
  completed_at: Time.now.iso8601
189
192
  }
190
193
  end
194
+
195
+ # Rich completion metadata for the answers. Only :engine and
196
+ # :engine_version are guaranteed — the environment probes are
197
+ # best-effort: a failing one (offline host, no login name, blocked
198
+ # egress) yields nil and its member is dropped.
199
+ #
200
+ # @return [Inquirex::CompletionMetadata]
201
+ def build_completion_metadata
202
+ optional = {
203
+ uname: best_effort { OpenStruct.new(Etc.uname) },
204
+ user: best_effort { Etc.getlogin },
205
+ local_ip: best_effort { local_ip },
206
+ public_ip: best_effort { public_ip },
207
+ terminal: ENV["LC_TERMINAL"] || ENV["TERM_PROGRAM"] || "Unknown"
208
+ }.compact
209
+ Inquirex::CompletionMetadata.new(
210
+ engine: "inquirex-tty",
211
+ engine_version: Inquirex::TTY::VERSION,
212
+ **optional
213
+ )
214
+ end
215
+
216
+ def best_effort
217
+ yield
218
+ rescue StandardError
219
+ nil
220
+ end
221
+
222
+ # The local address the OS would route out through — a UDP "connect"
223
+ # picks the outbound interface without sending a single packet.
224
+ def local_ip
225
+ UDPSocket.open do |socket|
226
+ socket.connect("8.8.8.8", 1)
227
+ socket.addr.last
228
+ end
229
+ end
230
+
231
+ # Short timeouts so a slow or airgapped network can never stall the
232
+ # CLI at completion time.
233
+ def public_ip
234
+ Net::HTTP.start("api.ipify.org", 443, use_ssl: true, open_timeout: 2, read_timeout: 2) do |http|
235
+ response = http.get("/")
236
+ response.is_a?(Net::HTTPSuccess) ? response.body.to_s.strip : nil
237
+ end
238
+ end
191
239
  end
192
240
  end
193
241
  end
@@ -23,10 +23,10 @@ module Inquirex
23
23
  exit 1
24
24
  end
25
25
  rescue Inquirex::TTY::Error => e
26
- warn "Error: #{e.message}"
26
+ $stderr.puts "Error: #{e.message}"
27
27
  exit 1
28
28
  rescue Inquirex::Errors::Error => e
29
- warn "Definition error: #{e.message}"
29
+ $stderr.puts "Definition error: #{e.message}"
30
30
  exit 1
31
31
  end
32
32
 
@@ -48,8 +48,8 @@ module Inquirex
48
48
  end
49
49
 
50
50
  def print_errors(errors)
51
- warn "Flow definition has #{errors.length} error(s):"
52
- errors.each { |e| warn " - #{e}" }
51
+ $stderr.puts "Flow definition has #{errors.length} error(s):"
52
+ errors.each { |e| $stderr.puts " - #{e}" }
53
53
  end
54
54
 
55
55
  def validate_definition(definition)
@@ -7,7 +7,9 @@ module Inquirex
7
7
  class Version < Dry::CLI::Command
8
8
  desc "Print version information"
9
9
 
10
- # @param **_ [Hash] ignored options
10
+ # Prints the version of this gem and of the core inquirex gem.
11
+ # Any CLI options are accepted and ignored.
12
+ #
11
13
  # @return [void]
12
14
  def call(**)
13
15
  puts "inquirex-tty #{Inquirex::TTY::VERSION}"
@@ -6,6 +6,12 @@ module Inquirex
6
6
  # binding. The file is expected to call +Inquirex.define+ and return an
7
7
  # +Inquirex::Definition+.
8
8
  class FlowLoader
9
+ # Loads and evaluates a flow definition file in one call.
10
+ #
11
+ # @example Load a flow definition from disk
12
+ # definition = Inquirex::TTY::FlowLoader.load("examples/08_tax_preparer.rb")
13
+ # definition.step_ids # => [:filing_status, :dependents, ...]
14
+ #
9
15
  # @param path [String] path to a .rb flow definition file
10
16
  # @return [Inquirex::Definition]
11
17
  # @raise [Inquirex::TTY::Error] if file is missing, not .rb, or has syntax errors
@@ -18,6 +18,11 @@ module Inquirex
18
18
 
19
19
  # Resolves an output path, returning nil when the caller should use stdout.
20
20
  #
21
+ # @example Resolve the --output option for a JSON export
22
+ # OutputPath.resolve("flows/intake.rb", nil, ".json") # => nil (stdout)
23
+ # OutputPath.resolve("flows/intake.rb", "out", ".json") # => "out.json"
24
+ # OutputPath.resolve("flows/intake.rb", "/tmp", ".json") # => "/tmp/intake.json"
25
+ #
21
26
  # @param flow_file [String] source .rb flow file (basename used for defaults)
22
27
  # @param output [String, nil] value of the --output option
23
28
  # @param extension [String] including leading dot, e.g. ".json"
@@ -63,15 +63,28 @@ module Inquirex
63
63
  end
64
64
 
65
65
  # Renders a node. Returns the collected answer, or +nil+ for display verbs.
66
+ #
67
+ # @example Collect an answer for the current step, pre-checking LLM suggestions
68
+ # renderer = Inquirex::TTY::Renderer.new
69
+ # step = engine.current_step
70
+ # answer = renderer.render(step, suggestion: engine.suggestion_for(engine.current_step_id))
71
+ # engine.answer(answer) unless step.display?
72
+ #
66
73
  # @param node [Inquirex::Node]
74
+ # @param suggestion [Array, nil] prefill suggestion for the step (option
75
+ # form values, e.g. from Engine#suggestion_for) — multi-select prompts
76
+ # render these choices pre-checked so the user confirms or extends
67
77
  # @return [Object, nil]
68
- def render(node)
78
+ def render(node, suggestion: nil)
79
+ @current_suggestion = suggestion
69
80
  if node.display?
70
81
  render_display_verb(node)
71
82
  nil
72
83
  else
73
84
  render_collecting(node)
74
85
  end
86
+ ensure
87
+ @current_suggestion = nil
75
88
  end
76
89
 
77
90
  private
@@ -177,12 +190,51 @@ module Inquirex
177
190
  # Integer or float depending on the node's data type.
178
191
  def render_number_input(node)
179
192
  convert = node.type == :integer ? :int : :float
180
- prompt.ask(node.question, convert:)
193
+ ask_bounded(node, convert:)
181
194
  end
182
195
 
183
196
  # Float for currency types.
184
197
  def render_currency_input(node)
185
- prompt.ask(node.question, convert: :float)
198
+ ask_bounded(node, convert: :float)
199
+ end
200
+
201
+ # Asks for a number, enforcing the node's declared bounds.
202
+ #
203
+ # tty-prompt's `in:` re-asks until the answer falls inside the range,
204
+ # which is the terminal's equivalent of the widget's stepper bounds — the
205
+ # user is corrected before the value is ever stored, rather than having it
206
+ # silently clamped afterwards. An unbounded node keeps the plain ask so
207
+ # the prompt text is unchanged for the overwhelmingly common case.
208
+ #
209
+ # @param node [Inquirex::Node] the step being rendered
210
+ # @param convert [Symbol] :int or :float
211
+ # @return [Numeric, nil]
212
+ def ask_bounded(node, convert:)
213
+ return prompt.ask(node.question, convert:) unless node.bounded?
214
+
215
+ prompt.ask(node.question, convert:, in: bounds_range(node)) do |q|
216
+ q.messages[:range?] = "Enter a value #{describe_bounds(node)}."
217
+ end
218
+ end
219
+
220
+ # The node's bounds as a Range tty-prompt understands. An open end is
221
+ # spelled with infinity rather than omitted, since `in:` wants a Range.
222
+ #
223
+ # @param node [Inquirex::Node]
224
+ # @return [Range]
225
+ def bounds_range(node)
226
+ low = node.min || -Float::INFINITY
227
+ high = node.max || Float::INFINITY
228
+ low..high
229
+ end
230
+
231
+ # @param node [Inquirex::Node]
232
+ # @return [String] human phrasing of whichever bounds exist
233
+ def describe_bounds(node)
234
+ return "between #{node.min} and #{node.max}" if node.min && node.max
235
+ return "of #{node.min} or more" if node.min
236
+
237
+ "of #{node.max} or less"
186
238
  end
187
239
 
188
240
  # Boolean — tty-prompt yes?.
@@ -195,9 +247,26 @@ module Inquirex
195
247
  prompt.select(node.question, select_options(node))
196
248
  end
197
249
 
198
- # Multiple-choice list (space to toggle, min 1 selection).
250
+ # Multiple-choice list (space to toggle, min 1 selection). Suggested
251
+ # choices (an LLM extraction the user should confirm) render pre-checked.
199
252
  def render_multi_select(node)
200
- prompt.multi_select(node.question, select_options(node), min: 1)
253
+ prompt.multi_select(node.question, select_options(node), min: 1, **multi_select_defaults(node))
254
+ end
255
+
256
+ # tty-prompt pre-checks multi_select choices via default: choice NAMES —
257
+ # the labels, not the return values. Suggestions arrive as option form
258
+ # values (that is the wire contract), so translate value → label here;
259
+ # a value with no label entry falls back to itself.
260
+ #
261
+ # @param node [Inquirex::Node]
262
+ # @return [Hash] {} or { default: Array<String> }
263
+ def multi_select_defaults(node)
264
+ suggested = Array(@current_suggestion)
265
+ return {} if suggested.empty?
266
+
267
+ labels = node.respond_to?(:option_labels) ? node.option_labels : nil
268
+ names = suggested.map { |value| labels&.fetch(value.to_s, nil) || value.to_s }
269
+ { default: names }
201
270
  end
202
271
 
203
272
  # Numbered choice menu.
@@ -12,6 +12,13 @@ module Inquirex
12
12
  @pastel ||= Pastel.new
13
13
  end
14
14
 
15
+ # Module hook that installs the UI helpers on the including class:
16
+ # +tty_box+, +tty_screen+, +pastel+, the TTY::Box wrappers (+frame+,
17
+ # +info+, +success+, +error+, +warning+), the +width+ delegator, and
18
+ # the +box+ / +next_step+ / +sep+ convenience methods.
19
+ #
20
+ # @param base [Class, Module] the class or module including UIHelper
21
+ # @return [void]
15
22
  def included(base)
16
23
  base.extend(Forwardable)
17
24
  base.define_method(:tty_box) { ::TTY::Box }
@@ -27,6 +34,12 @@ module Inquirex
27
34
 
28
35
  base.class_eval do
29
36
  # Draw a bordered box with optional title.
37
+ #
38
+ # @param text [String] text to display inside the box
39
+ # @param title [String, nil] optional title on the top border
40
+ # @param bg [Symbol] background color name
41
+ # @param fg [Symbol] foreground color name
42
+ # @return [void]
30
43
  def box(text, title: nil, bg: :green, fg: :white) # rubocop:disable Naming/MethodParameterName
31
44
  w = [width, 80].min
32
45
  args = {
@@ -41,12 +54,21 @@ module Inquirex
41
54
  end
42
55
 
43
56
  # Print step progress and separator.
57
+ #
58
+ # @param step_id [Symbol, String] id of the step about to run
59
+ # @param step_number [Integer] 1-based position in the flow
60
+ # @return [void]
44
61
  def next_step(step_id, step_number)
62
+ puts
45
63
  puts pastel.yellow("Step #{step_number}: #{step_id}")
46
64
  sep(:yellow, "━")
47
65
  end
48
66
 
49
67
  # Print a full-width separator in the given color.
68
+ #
69
+ # @param color [Symbol] Pastel color name
70
+ # @param char [String] character repeated across the line
71
+ # @return [void]
50
72
  def sep(color = :yellow, char = "━")
51
73
  puts pastel.send(color, char * 80)
52
74
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Inquirex
4
4
  module TTY
5
- VERSION = "0.5.0"
5
+ VERSION = "0.9.6"
6
6
  end
7
7
  end
data/lib/inquirex/tty.rb CHANGED
@@ -14,7 +14,15 @@ require "yaml"
14
14
  require "time"
15
15
  require "shellwords"
16
16
  require "tempfile"
17
+ require "etc"
18
+ require "socket"
19
+ require "net/http"
20
+ require "uri"
21
+ require "ostruct"
17
22
 
23
+ # Top-level namespace shared by every gem in the Inquirex family.
24
+ # The core gem defines the DSL, rules, and engine; this gem adds the
25
+ # terminal adapter under {Inquirex::TTY}.
18
26
  module Inquirex
19
27
  # Terminal adapter for Inquirex flows. Renders questions as interactive
20
28
  # TTY prompts via tty-prompt, mapping each data type to the appropriate
data/sig/inquirex/tty.rbs CHANGED
@@ -1,4 +1,5 @@
1
1
  module Inquirex
2
+ # RBS scaffold namespace for the inquirex-tty gem's type signatures.
2
3
  module Tty
3
4
  VERSION: String
4
5
  # See the writing guide of rbs: https://github.com/ruby/rbs#guides