inquirex-tty 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d2a11293b1ac438c2ccd3b25cf1b18782e9b49e9b3e1d4d344e84216403cca9
4
- data.tar.gz: d4001771880c748982dbaa765a390f910d4d0e71772259981bb317af0c2696c7
3
+ metadata.gz: a2984ed233d95ddd841ed94f02795d8489c2d93897a317c846985cc8b88b807b
4
+ data.tar.gz: e082b1ac879a6a8384ad1f9aa89de557ccb9f483933fad7d474116e1670b3a36
5
5
  SHA512:
6
- metadata.gz: '085bb14fd6cd73b341108763e61989b882ad4936f7b70ffd2a45089bd5bc930887ab7e7d85b8e2c15e682c77bdb8ba7384971a102199ae74038f2696c0af71dd'
7
- data.tar.gz: c6c92ee9cd25e7b23b1a0d6a839f7df0f92bc6a6a36c385a33ee2be4bd01004261c476bb4cf2a5689a2eca10afca1cfd23334546c4c12da7c4e89e42602ebc8f
6
+ metadata.gz: e84d7ae1c21cd3113399327024b9020b81e4c69323605b98d29fc6c20002884902a8396047f2628598e16e4d4bbc87aa35943b3e4a51e9816200390b218882b7
7
+ data.tar.gz: 060a278fd838977a0ab5885e7d5e11000e25e004f0d6173729c7194a059d57c061afcc34a0d05929270bdecee01bc72cdf8dd042de9ce4204a7cc2053000001b
@@ -15,7 +15,7 @@
15
15
  <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
16
16
  <text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
17
17
  <text x="31.5" y="14">coverage</text>
18
- <text x="80" y="15" fill="#010101" fill-opacity=".3">99%</text>
19
- <text x="80" y="14">99%</text>
18
+ <text x="80" y="15" fill="#010101" fill-opacity=".3">98%</text>
19
+ <text x="80" y="14">98%</text>
20
20
  </g>
21
21
  </svg>
@@ -49,6 +49,38 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
49
49
  "Please do NOT enter any personal information: no names,\n" \
50
50
  "SSN, ITIN, addresses, or account numbers. We only need\n" \
51
51
  "enough to quote a fee."
52
+ transition to: :describe
53
+ end
54
+
55
+ # -----------------------------------------------------------------------
56
+ # Opening
57
+ # -----------------------------------------------------------------------
58
+
59
+ ask :describe do
60
+ type :text
61
+ question "Describe your last year's tax situation, your residency/citizenship status, your filing status, any dependents, various income types, which state(s) you are filing in,and whether you have your prior year returns available. If you also participate in businesses, please describe their tax status (S-Corp, LLC, etc), and if you have any foreign investments or property."
62
+ transition to: :summary
63
+ end
64
+
65
+ extract :summary do
66
+ from :describe
67
+ prompt "Extract the filer's tax intake details for scoping: filing_status, " \
68
+ "number of dependents, income_types, state_filing (which states they " \
69
+ "file in), residency_status, prior_return_available, and any " \
70
+ "business_entities they own or partner in."
71
+ schema filing_status: { type: :enum, values: %w[single married_filing_jointly married_filing_separately
72
+ head_of_household widowed] },
73
+ dependents: { type: :enum, values: %w[0 1 2 3 4+] },
74
+ income_types: { type: :multi_enum, values: %w[W2 1099_nec 1099_k business investment crypto rental
75
+ retirement social_sec gambling foreign home_sale
76
+ inheritance none] },
77
+ state_filing: { type: :multi_enum, values: US_STATES },
78
+ residency_status: { type: :enum, values: %w[us_person resident non_resident dual_status] },
79
+ prior_return_available: { type: :enum, values: %w[yes_last_year yes_older no first_time] },
80
+ business_entities: { type: :multi_enum, values: %w[sole_prop single_llc multi_llc s_corp c_corp
81
+ partnership trust nonprofit] }
82
+
83
+ model :claude_sonnet
52
84
  transition to: :residency_status
53
85
  end
54
86
 
@@ -59,6 +91,7 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
59
91
  ask :residency_status do
60
92
  type :enum
61
93
  question "Which best describes your US tax residency for 2025?"
94
+ skip_if not_empty(:residency_status)
62
95
  options({
63
96
  "us_person" => "US citizen or permanent resident",
64
97
  "resident" => "Resident alien (substantial presence)",
@@ -74,6 +107,7 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
74
107
  ask :prior_return_available do
75
108
  type :enum
76
109
  question "Do you have a copy of your most recent tax return?"
110
+ skip_if not_empty(:prior_return_available)
77
111
  options({
78
112
  "yes_last_year" => "Yes, last year's return",
79
113
  "yes_older" => "Yes, but older than last year",
@@ -88,6 +122,7 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
88
122
  ask :filing_status do
89
123
  type :enum
90
124
  question "What is your filing status for 2025?"
125
+ skip_if not_empty(:filing_status)
91
126
  options({
92
127
  "single" => "Single",
93
128
  "married_filing_jointly" => "Married Filing Jointly",
@@ -98,12 +133,13 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
98
133
  widget target: :tty, type: :select
99
134
  widget target: :desktop, type: :radio_group, columns: 1
100
135
  widget target: :mobile, type: :dropdown
101
- transition to: :dependents_band
136
+ transition to: :dependents
102
137
  end
103
138
 
104
- ask :dependents_band do
139
+ ask :dependents do
105
140
  type :enum
106
141
  question "How many dependents will you claim?"
142
+ skip_if not_empty(:dependents)
107
143
  options %w[0 1 2 3 4+]
108
144
  widget target: :tty, type: :select
109
145
  widget target: :desktop, type: :radio_group, columns: 5
@@ -118,6 +154,7 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
118
154
  ask :income_types do
119
155
  type :multi_enum
120
156
  question "Select every type of income you had in 2025."
157
+ skip_if not_empty(:income_types)
121
158
  options({
122
159
  "W2" => "W-2 wages",
123
160
  "1099_nec" => "1099-NEC (contractor)",
@@ -166,6 +203,7 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
166
203
  ask :business_entities do
167
204
  type :multi_enum
168
205
  question "Which business entity types do you own or partner in?"
206
+ skip_if not_empty(:business_entities)
169
207
  options({
170
208
  "sole_prop" => "Sole Proprietor / Schedule C",
171
209
  "single_llc" => "Single-member LLC",
@@ -435,6 +473,7 @@ Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
435
473
  ask :state_filing do
436
474
  type :multi_enum
437
475
  question "Select every state (and DC) you need to file a return in."
476
+ skip_if not_empty(:state_filing)
438
477
  options US_STATES
439
478
  widget target: :tty, type: :multi_select
440
479
  widget target: :desktop, type: :checkbox_group, columns: 10, layout: :grid
@@ -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?
@@ -183,11 +185,56 @@ module Inquirex
183
185
  {
184
186
  flow_file: flow_file,
185
187
  path_taken: engine.history,
186
- answers: engine.answers,
188
+ answers: engine.answers_with_metadata,
187
189
  steps_completed: engine.history.length,
188
190
  completed_at: Time.now.iso8601
189
191
  }
190
192
  end
193
+
194
+ # Rich completion metadata for the answers. Only :engine and
195
+ # :engine_version are guaranteed — the environment probes are
196
+ # best-effort: a failing one (offline host, no login name, blocked
197
+ # egress) yields nil and its member is dropped.
198
+ #
199
+ # @return [Inquirex::CompletionMetadata]
200
+ def build_completion_metadata
201
+ optional = {
202
+ uname: best_effort { OpenStruct.new(Etc.uname) },
203
+ user: best_effort { Etc.getlogin },
204
+ local_ip: best_effort { local_ip },
205
+ public_ip: best_effort { public_ip },
206
+ terminal: ENV["LC_TERMINAL"] || ENV["TERM_PROGRAM"] || "Unknown"
207
+ }.compact
208
+ Inquirex::CompletionMetadata.new(
209
+ engine: "inquirex-tty",
210
+ engine_version: Inquirex::TTY::VERSION,
211
+ **optional
212
+ )
213
+ end
214
+
215
+ def best_effort
216
+ yield
217
+ rescue StandardError
218
+ nil
219
+ end
220
+
221
+ # The local address the OS would route out through — a UDP "connect"
222
+ # picks the outbound interface without sending a single packet.
223
+ def local_ip
224
+ UDPSocket.open do |socket|
225
+ socket.connect("8.8.8.8", 1)
226
+ socket.addr.last
227
+ end
228
+ end
229
+
230
+ # Short timeouts so a slow or airgapped network can never stall the
231
+ # CLI at completion time.
232
+ def public_ip
233
+ Net::HTTP.start("api.ipify.org", 443, use_ssl: true, open_timeout: 2, read_timeout: 2) do |http|
234
+ response = http.get("/")
235
+ response.is_a?(Net::HTTPSuccess) ? response.body.to_s.strip : nil
236
+ end
237
+ end
191
238
  end
192
239
  end
193
240
  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,6 +63,13 @@ 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
68
+ # renderer = Inquirex::TTY::Renderer.new
69
+ # step = engine.current_step
70
+ # answer = renderer.render(step) # nil for display verbs
71
+ # engine.answer(answer) unless step.display?
72
+ #
66
73
  # @param node [Inquirex::Node]
67
74
  # @return [Object, nil]
68
75
  def render(node)
@@ -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.6.0"
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inquirex-tty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
@@ -29,28 +29,42 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.5'
32
+ version: '0.6'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.5'
39
+ version: '0.6'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: inquirex-llm
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.5'
46
+ version: '0.6'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.5'
53
+ version: '0.6'
54
+ - !ruby/object:Gem::Dependency
55
+ name: ostruct
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.6'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.6'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: pastel
56
70
  requirement: !ruby/object:Gem::Requirement