roast-ai 0.4.1 → 0.4.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: b31e95a8d65d0b1fa798c0baed8bcbf9249cc67fca752154865d914d01ffbd8d
4
- data.tar.gz: 21205a0a50e2301dda0936e67b02d05af94085b0e383f859aec282827649ebb3
3
+ metadata.gz: d1822868d0357ade29fdbdd3feab6a1beff23ef97621de9e1a9cacee366d5897
4
+ data.tar.gz: 00b9aff47853dc8f1f4ffa89bf571975fbe414632909b682fe6caab4e64bee51
5
5
  SHA512:
6
- metadata.gz: 80b5ceada69faff53f5893e9378e628474b103c3d6b60a8701be80b1bfe11cb6e4bde252670d890a2ae7e460d90d956daed7813d251cf6d84399ab70d321b0d8
7
- data.tar.gz: 620e7d46fc6f7e9b10a71807f213ba101cb2bbd9a3bad073a153057bd67bc842cf74e4ffaa179596a2ed3655dc7a864c366034a37f4a720d2fd6faafa86f5541
6
+ metadata.gz: 95c32a5e010460dfe2e584c7b0ba8b7d617dc965210fc00343bd2c1f647c847f3be41be0bb07a951a41b5aa1447c770b36f4eba97985543f510938044e85453b
7
+ data.tar.gz: d221ec8fba42b7187ae30fa4171e2700a7b81b1092693f9fdf1f168a37b6318482f5be8cf3f1be4e8650fe7a56d87ef2a0f93fea13f633e24c95734c285fd75b
data/.gitignore CHANGED
@@ -41,3 +41,4 @@ bin/thor
41
41
 
42
42
  gemfiles/*.lock
43
43
  bin/claude-swarm
44
+ *.gem
data/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.2] - 2025-06-20
9
+
10
+ ### Added
11
+ - **Multiline bash command support** (#289)
12
+ - Enhanced CommandExecutor to properly handle commands spanning multiple lines
13
+ - Enables sophisticated bash scripts in workflow steps
14
+ - Maintains backward compatibility with single-line commands
15
+ - **Comprehensive shell security enhancements** (#289)
16
+ - Smart interpolation that detects shell commands and escapes dangerous characters
17
+ - Protection against shell injection for all major metacharacters:
18
+ - Backslashes (`\`) to prevent path injection
19
+ - Double quotes (`"`) to prevent breaking quoted contexts
20
+ - Dollar signs (`$`) to prevent variable expansion
21
+ - Backticks (`` ` ``) to prevent command substitution
22
+ - Context-aware escaping only in shell commands, preserving text elsewhere
23
+ - **Early detection for missing Raix configuration** (#292)
24
+ - Provides helpful error messages when Raix is not properly initialized
25
+ - Shows example configuration for both OpenAI and OpenRouter
26
+ - Prevents cryptic "undefined method 'chat' for nil" errors
27
+ - **Exit early feature for input steps** (#291)
28
+ - Pressing Ctrl-C during input steps now exits cleanly
29
+ - No more confusing stack traces when canceling input
30
+ - **Default --dangerously-skip-permissions flag for CodingAgent** (#290)
31
+ - Avoids permission prompts during automated workflows
32
+ - Improves workflow automation experience
33
+
34
+ ### Fixed
35
+ - Test isolation issue causing CI failures (#289)
36
+ - Flaky test in StepExecutorRegistryTest due to executor registration conflicts (#289)
37
+ - Shell command interpolation security vulnerabilities (#289)
38
+ - Missing dependency declarations (cli-kit, sqlite3) (#292)
39
+
40
+ ### Changed
41
+ - Updated cli-kit dependency to ~> 5.0 for better error handling
42
+ - Updated sqlite3 dependency to ~> 2.6 to resolve version conflicts
43
+
44
+ [0.4.2]: https://github.com/Shopify/roast/compare/v0.4.1...v0.4.2
45
+
8
46
  ## [0.4.1] - 2025-06-18
9
47
 
10
48
  ### Added
data/Gemfile CHANGED
@@ -18,6 +18,5 @@ gem "rubocop-shopify", require: false
18
18
  gem "vcr", require: false
19
19
  gem "webmock", require: false
20
20
  gem "minitest-rg"
21
- gem "sqlite3", "~> 1.7"
22
21
 
23
22
  gem "claude_swarm"
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- roast-ai (0.4.1)
4
+ roast-ai (0.4.2)
5
5
  activesupport (>= 7.0)
6
+ cli-kit (~> 5.0)
6
7
  cli-ui (= 2.3.0)
7
8
  diff-lcs (~> 1.5)
8
9
  faraday-retry
@@ -10,6 +11,7 @@ PATH
10
11
  open_router (~> 0.3)
11
12
  raix (~> 1.0)
12
13
  ruby-graphviz (~> 1.2)
14
+ sqlite3 (~> 2.6)
13
15
  thor (~> 1.3)
14
16
  zeitwerk (~> 2.6)
15
17
 
@@ -36,9 +38,11 @@ GEM
36
38
  benchmark (0.4.1)
37
39
  bigdecimal (3.2.2)
38
40
  cgi (0.5.0)
39
- claude_swarm (0.1.15)
41
+ claude_swarm (0.1.19)
40
42
  fast-mcp-annotations
41
43
  thor (~> 1.3)
44
+ cli-kit (5.0.1)
45
+ cli-ui (~> 2.0)
42
46
  cli-ui (2.3.0)
43
47
  coderay (1.1.3)
44
48
  concurrent-ruby (1.3.5)
@@ -83,11 +87,11 @@ GEM
83
87
  faraday-net_http (>= 2.0, < 3.5)
84
88
  json
85
89
  logger
86
- faraday-multipart (1.1.0)
90
+ faraday-multipart (1.1.1)
87
91
  multipart-post (~> 2.0)
88
- faraday-net_http (3.4.0)
92
+ faraday-net_http (3.4.1)
89
93
  net-http (>= 0.5.0)
90
- faraday-retry (2.3.1)
94
+ faraday-retry (2.3.2)
91
95
  faraday (~> 2.0)
92
96
  fast-mcp-annotations (1.5.2)
93
97
  addressable (~> 2.8)
@@ -132,8 +136,7 @@ GEM
132
136
  mime-types (3.7.0)
133
137
  logger
134
138
  mime-types-data (~> 3.2025, >= 3.2025.0507)
135
- mime-types-data (3.2025.0603)
136
- mini_portile2 (2.8.9)
139
+ mime-types-data (3.2025.0617)
137
140
  minitest (5.25.5)
138
141
  minitest-rg (5.3.0)
139
142
  minitest (~> 5.0)
@@ -151,7 +154,7 @@ GEM
151
154
  dotenv (>= 2)
152
155
  faraday (>= 1)
153
156
  faraday-multipart (>= 1)
154
- ostruct (0.6.1)
157
+ ostruct (0.6.2)
155
158
  parallel (1.27.0)
156
159
  parser (3.3.8.0)
157
160
  ast (~> 2.4.1)
@@ -176,7 +179,7 @@ GEM
176
179
  ffi (~> 1.0)
177
180
  regexp_parser (2.10.0)
178
181
  rexml (3.4.1)
179
- rubocop (1.76.0)
182
+ rubocop (1.77.0)
180
183
  json (~> 2.3)
181
184
  language_server-protocol (~> 3.17.0.2)
182
185
  lint_roller (~> 1.1.0)
@@ -184,10 +187,10 @@ GEM
184
187
  parser (>= 3.3.0.2)
185
188
  rainbow (>= 2.2.2, < 4.0)
186
189
  regexp_parser (>= 2.9.3, < 3.0)
187
- rubocop-ast (>= 1.45.0, < 2.0)
190
+ rubocop-ast (>= 1.45.1, < 2.0)
188
191
  ruby-progressbar (~> 1.7)
189
192
  unicode-display_width (>= 2.4.0, < 4.0)
190
- rubocop-ast (1.45.0)
193
+ rubocop-ast (1.45.1)
191
194
  parser (>= 3.3.7.2)
192
195
  prism (~> 1.4)
193
196
  rubocop-shopify (2.17.1)
@@ -202,8 +205,8 @@ GEM
202
205
  ruby2_keywords (0.0.5)
203
206
  securerandom (0.4.1)
204
207
  shellany (0.0.1)
205
- sqlite3 (1.7.3)
206
- mini_portile2 (~> 2.8.0)
208
+ sqlite3 (2.6.0-arm64-darwin)
209
+ sqlite3 (2.6.0-x86_64-linux-gnu)
207
210
  thor (1.3.2)
208
211
  tzinfo (2.0.6)
209
212
  concurrent-ruby (~> 1.0)
@@ -235,7 +238,6 @@ DEPENDENCIES
235
238
  rake
236
239
  roast-ai!
237
240
  rubocop-shopify
238
- sqlite3 (~> 1.7)
239
241
  vcr
240
242
  webmock
241
243
 
@@ -4,7 +4,7 @@
4
4
  require "rubygems"
5
5
  require "bundler/setup"
6
6
 
7
- require_relative "../../lib/roast/helpers/minitest_coverage_runner"
7
+ require "roast/helpers/minitest_coverage_runner"
8
8
 
9
9
  # Suppress fancy minitest reporting
10
10
  ENV["RM_INFO"] = "true"
data/lib/roast/errors.rb CHANGED
@@ -7,5 +7,8 @@ module Roast
7
7
 
8
8
  # Custom error for when API authentication fails
9
9
  class AuthenticationError < StandardError; end
10
+
11
+ # Exit the app, for instance via Ctrl-C during an InputStep
12
+ class ExitEarly < StandardError; end
10
13
  end
11
14
  end
@@ -147,7 +147,7 @@ module Roast
147
147
  end
148
148
 
149
149
  def claude_code_command
150
- CodingAgent.configured_command || ENV["CLAUDE_CODE_COMMAND"] || "claude -p --verbose --output-format stream-json"
150
+ CodingAgent.configured_command || ENV["CLAUDE_CODE_COMMAND"] || "claude -p --verbose --output-format stream-json --dangerously-skip-permissions"
151
151
  end
152
152
 
153
153
  def build_command(base_command, continue:)
data/lib/roast/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Roast
4
- VERSION = "0.4.1"
4
+ VERSION = "0.4.2"
5
5
  end
@@ -14,6 +14,8 @@ module Roast
14
14
  end
15
15
  end
16
16
 
17
+ BASH_COMMAND_REGEX = /^\$\((.*)\)$/m
18
+
17
19
  def initialize(logger: nil)
18
20
  @logger = logger || NullLogger.new
19
21
  end
@@ -44,7 +46,7 @@ module Roast
44
46
  private
45
47
 
46
48
  def extract_command(command_string)
47
- match = command_string.strip.match(/^\$\((.*)\)$/)
49
+ match = command_string.strip.match(BASH_COMMAND_REGEX)
48
50
  raise ArgumentError, "Invalid command format. Expected $(command), got: #{command_string}" unless match
49
51
 
50
52
  match[1]
@@ -38,6 +38,8 @@ module Roast
38
38
  else
39
39
  @workflow_runner.run_targetless
40
40
  end
41
+ rescue Roast::Errors::ExitEarly
42
+ $stderr.puts "Exiting workflow early."
41
43
  ensure
42
44
  execution_time = Time.now - start_time
43
45
 
@@ -29,6 +29,8 @@ module Roast
29
29
  store_in_state(result) if step_name
30
30
 
31
31
  result
32
+ rescue Interrupt
33
+ raise Roast::Errors::ExitEarly
32
34
  rescue Timeout::Error
33
35
  handle_timeout
34
36
  end
@@ -11,12 +11,22 @@ module Roast
11
11
  def interpolate(text)
12
12
  return text unless text.is_a?(String) && text.include?("{{") && text.include?("}}")
13
13
 
14
+ # Check if this is a shell command context
15
+ is_shell_command = text.strip.start_with?("$(") && text.strip.end_with?(")")
16
+
14
17
  # Replace all {{expression}} with their evaluated values
15
18
  text.gsub(/\{\{([^}]+)\}\}/) do |match|
16
19
  expression = Regexp.last_match(1).strip
17
20
  begin
18
21
  # Evaluate the expression in the context
19
- @context.instance_eval(expression).to_s
22
+ result = @context.instance_eval(expression).to_s
23
+
24
+ # Escape shell metacharacters if this is a shell command
25
+ if is_shell_command
26
+ escape_shell_metacharacters(result)
27
+ else
28
+ result
29
+ end
20
30
  rescue => e
21
31
  # Provide a detailed error message but preserve the original expression
22
32
  error_msg = "Error interpolating {{#{expression}}}: #{e.message}. This variable is not defined in the workflow context."
@@ -26,6 +36,18 @@ module Roast
26
36
  end
27
37
  end
28
38
 
39
+ private
40
+
41
+ # Escape shell metacharacters to prevent injection and command substitution
42
+ # Order matters: escape backslashes first to avoid double-escaping
43
+ def escape_shell_metacharacters(text)
44
+ text
45
+ .gsub("\\", "\\\\\\\\") # Escape backslashes first (4 backslashes become 2, then 1)
46
+ .gsub('"', '\\\\"') # Escape double quotes
47
+ .gsub("$", "\\\\$") # Escape dollar signs (variable expansion)
48
+ .gsub("`", "\\\\`") # Escape backticks (command substitution)
49
+ end
50
+
29
51
  class NullLogger
30
52
  def error(_message); end
31
53
  end
@@ -29,13 +29,13 @@ module Roast
29
29
 
30
30
  session_id = ensure_session(workflow)
31
31
 
32
- @db.execute(<<~SQL, session_id, state_data[:order], step_name, state_data.to_json)
32
+ @db.execute(<<~SQL, [session_id, state_data[:order], step_name, state_data.to_json])
33
33
  INSERT INTO session_states (session_id, step_index, step_name, state_data)
34
34
  VALUES (?, ?, ?, ?)
35
35
  SQL
36
36
 
37
37
  # Update session's current step
38
- @db.execute(<<~SQL, state_data[:order], session_id)
38
+ @db.execute(<<~SQL, [state_data[:order], session_id])
39
39
  UPDATE sessions#{" "}
40
40
  SET current_step_index = ?, updated_at = CURRENT_TIMESTAMP
41
41
  WHERE id = ?
@@ -49,7 +49,7 @@ module Roast
49
49
  return false unless session_id
50
50
 
51
51
  # Find the state before the target step
52
- result = @db.execute(<<~SQL, session_id, step_name)
52
+ result = @db.execute(<<~SQL, [session_id, step_name])
53
53
  SELECT state_data, step_name
54
54
  FROM session_states
55
55
  WHERE session_id = ?
@@ -64,7 +64,7 @@ module Roast
64
64
 
65
65
  if result.empty?
66
66
  # Try to find the latest state if target step doesn't exist
67
- result = @db.execute(<<~SQL, session_id)
67
+ result = @db.execute(<<~SQL, [session_id])
68
68
  SELECT state_data, step_name
69
69
  FROM session_states
70
70
  WHERE session_id = ?
@@ -95,7 +95,7 @@ module Roast
95
95
 
96
96
  session_id = ensure_session(workflow)
97
97
 
98
- @db.execute(<<~SQL, output_content, session_id)
98
+ @db.execute(<<~SQL, [output_content, session_id])
99
99
  UPDATE sessions#{" "}
100
100
  SET final_output = ?, status = 'completed', updated_at = CURRENT_TIMESTAMP
101
101
  WHERE id = ?
@@ -130,7 +130,7 @@ module Roast
130
130
 
131
131
  where_clause = conditions.empty? ? "" : "WHERE #{conditions.join(" AND ")}"
132
132
 
133
- @db.execute(<<~SQL, *params)
133
+ @db.execute(<<~SQL, params)
134
134
  SELECT id, workflow_name, workflow_path, status, current_step_index,#{" "}
135
135
  created_at, updated_at
136
136
  FROM sessions
@@ -141,20 +141,20 @@ module Roast
141
141
  end
142
142
 
143
143
  def get_session_details(session_id)
144
- session = @db.execute(<<~SQL, session_id).first
144
+ session = @db.execute(<<~SQL, [session_id]).first
145
145
  SELECT * FROM sessions WHERE id = ?
146
146
  SQL
147
147
 
148
148
  return unless session
149
149
 
150
- states = @db.execute(<<~SQL, session_id)
150
+ states = @db.execute(<<~SQL, [session_id])
151
151
  SELECT step_index, step_name, created_at
152
152
  FROM session_states
153
153
  WHERE session_id = ?
154
154
  ORDER BY step_index
155
155
  SQL
156
156
 
157
- events = @db.execute(<<~SQL, session_id)
157
+ events = @db.execute(<<~SQL, [session_id])
158
158
  SELECT event_name, event_data, received_at
159
159
  FROM session_events
160
160
  WHERE session_id = ?
@@ -170,7 +170,7 @@ module Roast
170
170
 
171
171
  def cleanup_old_sessions(older_than)
172
172
  count = @db.changes
173
- @db.execute(<<~SQL, "-#{older_than}")
173
+ @db.execute(<<~SQL, ["-#{older_than}"])
174
174
  DELETE FROM sessions
175
175
  WHERE created_at < datetime('now', ?)
176
176
  SQL
@@ -181,7 +181,7 @@ module Roast
181
181
  # Find the session if session_id not provided
182
182
  unless session_id
183
183
  workflow_name = File.basename(File.dirname(workflow_path))
184
- result = @db.execute(<<~SQL, workflow_name, "waiting")
184
+ result = @db.execute(<<~SQL, [workflow_name, "waiting"])
185
185
  SELECT id FROM sessions
186
186
  WHERE workflow_name = ? AND status = ?
187
187
  ORDER BY created_at DESC
@@ -194,13 +194,13 @@ module Roast
194
194
  end
195
195
 
196
196
  # Add the event
197
- @db.execute(<<~SQL, session_id, event_name, event_data&.to_json)
197
+ @db.execute(<<~SQL, [session_id, event_name, event_data&.to_json])
198
198
  INSERT INTO session_events (session_id, event_name, event_data)
199
199
  VALUES (?, ?, ?)
200
200
  SQL
201
201
 
202
202
  # Update session status
203
- @db.execute(<<~SQL, session_id)
203
+ @db.execute(<<~SQL, [session_id])
204
204
  UPDATE sessions#{" "}
205
205
  SET status = 'running', updated_at = CURRENT_TIMESTAMP
206
206
  WHERE id = ?
@@ -272,14 +272,14 @@ module Roast
272
272
  session_id = generate_session_id(workflow)
273
273
 
274
274
  # Check if session exists
275
- existing = @db.execute("SELECT id FROM sessions WHERE id = ?", session_id).first
275
+ existing = @db.execute("SELECT id FROM sessions WHERE id = ?", [session_id]).first
276
276
  return session_id if existing
277
277
 
278
278
  # Create new session
279
279
  workflow_name = workflow.session_name || "unnamed"
280
280
  workflow_path = workflow.file || "notarget"
281
281
 
282
- @db.execute(<<~SQL, session_id, workflow_name, workflow_path)
282
+ @db.execute(<<~SQL, [session_id, workflow_name, workflow_path])
283
283
  INSERT INTO sessions (id, workflow_name, workflow_path)
284
284
  VALUES (?, ?, ?)
285
285
  SQL
@@ -296,7 +296,7 @@ module Roast
296
296
  workflow_name = workflow.session_name || "unnamed"
297
297
  workflow_path = workflow.file || "notarget"
298
298
 
299
- result = @db.execute(<<~SQL, workflow_name, workflow_path)
299
+ result = @db.execute(<<~SQL, [workflow_name, workflow_path])
300
300
  SELECT id FROM sessions
301
301
  WHERE workflow_name = ? AND workflow_path = ?
302
302
  ORDER BY created_at DESC
@@ -324,7 +324,7 @@ module Roast
324
324
  new_session_id = ensure_session(workflow)
325
325
 
326
326
  # Copy states up to the target step
327
- @db.execute(<<~SQL, new_session_id, source_session_id, target_step_name, source_session_id)
327
+ @db.execute(<<~SQL, [new_session_id, source_session_id, target_step_name, source_session_id])
328
328
  INSERT INTO session_states (session_id, step_index, step_name, state_data)
329
329
  SELECT ?, step_index, step_name, state_data
330
330
  FROM session_states
@@ -10,6 +10,7 @@ module Roast
10
10
 
11
11
  def setup
12
12
  load_roast_initializers
13
+ check_raix_configuration
13
14
  include_tools
14
15
  configure_api_client
15
16
  end
@@ -20,6 +21,85 @@ module Roast
20
21
  Roast::Initializers.load_all
21
22
  end
22
23
 
24
+ def check_raix_configuration
25
+ # Skip check in test environment
26
+ return if ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test" || defined?(Minitest)
27
+
28
+ # Only check if the workflow has steps that would need API access
29
+ return if @configuration.steps.empty?
30
+
31
+ # Check if Raix has been configured with the appropriate client
32
+ case @configuration.api_provider
33
+ when :openai
34
+ if Raix.configuration.openai_client.nil?
35
+ warn_about_missing_raix_configuration(:openai)
36
+ end
37
+ when :openrouter
38
+ if Raix.configuration.openrouter_client.nil?
39
+ warn_about_missing_raix_configuration(:openrouter)
40
+ end
41
+ when nil
42
+ # If no api_provider is set but we have steps that might need API access,
43
+ # check if any client is configured
44
+ if Raix.configuration.openai_client.nil? && Raix.configuration.openrouter_client.nil?
45
+ warn_about_missing_raix_configuration(:any)
46
+ end
47
+ end
48
+ end
49
+
50
+ def warn_about_missing_raix_configuration(provider)
51
+ ::CLI::UI.frame_style = :box
52
+ ::CLI::UI::Frame.open("{{red:Raix Configuration Missing}}", color: :red) do
53
+ case provider
54
+ when :openai
55
+ puts ::CLI::UI.fmt("{{yellow:⚠️ Warning: Raix OpenAI client is not configured!}}")
56
+ when :openrouter
57
+ puts ::CLI::UI.fmt("{{yellow:⚠️ Warning: Raix OpenRouter client is not configured!}}")
58
+ else
59
+ puts ::CLI::UI.fmt("{{yellow:⚠️ Warning: Raix is not configured!}}")
60
+ end
61
+ puts
62
+ puts "Roast requires Raix to be properly initialized to make API calls."
63
+ puts ::CLI::UI.fmt("To fix this, create a file at {{cyan:.roast/initializers/raix.rb}} with:")
64
+ puts
65
+ puts ::CLI::UI.fmt("{{cyan:# frozen_string_literal: true}}")
66
+ puts
67
+ puts ::CLI::UI.fmt("{{cyan:require \"raix\"}}")
68
+
69
+ if provider == :openrouter
70
+ puts ::CLI::UI.fmt("{{cyan:require \"open_router\"}}")
71
+ puts
72
+ puts ::CLI::UI.fmt("{{cyan:Raix.configure do |config|}}")
73
+ puts ::CLI::UI.fmt("{{cyan: config.openrouter_client = OpenRouter::Client.new(}}")
74
+ puts ::CLI::UI.fmt("{{cyan: access_token: ENV.fetch(\"OPENROUTER_API_KEY\"),}}")
75
+ puts ::CLI::UI.fmt("{{cyan: uri_base: \"https://openrouter.ai/api/v1\",}}")
76
+ puts ::CLI::UI.fmt("{{cyan: )}}")
77
+ else
78
+ puts ::CLI::UI.fmt("{{cyan:require \"faraday\"}}")
79
+ puts ::CLI::UI.fmt("{{cyan:require \"faraday/retry\"}}")
80
+ puts
81
+ puts ::CLI::UI.fmt("{{cyan: Raix.configure do |config|}}")
82
+ puts ::CLI::UI.fmt("{{cyan: config.openai_client = OpenAI::Client.new(}}")
83
+ puts ::CLI::UI.fmt("{{cyan: access_token: ENV.fetch(\"OPENAI_API_KEY\"),}}")
84
+ puts ::CLI::UI.fmt("{{cyan: uri_base: \"https://api.openai.com/v1\",}}")
85
+ puts ::CLI::UI.fmt("{{cyan: ) do |f|}}")
86
+ puts ::CLI::UI.fmt("{{cyan: f.request(:retry, {}}")
87
+ puts ::CLI::UI.fmt("{{cyan: max: 2,}}")
88
+ puts ::CLI::UI.fmt("{{cyan: interval: 0.05,}}")
89
+ puts ::CLI::UI.fmt("{{cyan: interval_randomness: 0.5,}}")
90
+ puts ::CLI::UI.fmt("{{cyan: backoff_factor: 2,}}")
91
+ puts ::CLI::UI.fmt("{{cyan: })}}")
92
+ puts ::CLI::UI.fmt("{{cyan: end}}")
93
+ end
94
+ puts ::CLI::UI.fmt("{{cyan:end}}")
95
+ puts
96
+ puts "For Shopify users, you need to use the LLM gateway proxy instead."
97
+ puts "Check the #roast slack channel for more information."
98
+ puts
99
+ end
100
+ raise ::CLI::Kit::Abort, "Please configure Raix before running workflows."
101
+ end
102
+
23
103
  def include_tools
24
104
  return unless @configuration.tools.present? || @configuration.mcp_tools.present?
25
105
 
data/lib/roast.rb CHANGED
@@ -25,6 +25,7 @@ require "active_support/core_ext/string/inflections"
25
25
  require "active_support/isolated_execution_state"
26
26
  require "active_support/notifications"
27
27
  require "cli/ui"
28
+ require "cli/kit"
28
29
  require "diff/lcs"
29
30
  require "json-schema"
30
31
  require "raix"
data/roast.gemspec CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.require_paths = ["lib"]
38
38
 
39
39
  spec.add_dependency("activesupport", ">= 7.0")
40
+ spec.add_dependency("cli-kit", "~> 5.0")
40
41
  spec.add_dependency("cli-ui", "2.3.0")
41
42
  spec.add_dependency("diff-lcs", "~> 1.5")
42
43
  spec.add_dependency("faraday-retry")
@@ -44,6 +45,7 @@ Gem::Specification.new do |spec|
44
45
  spec.add_dependency("open_router", "~> 0.3")
45
46
  spec.add_dependency("raix", "~> 1.0")
46
47
  spec.add_dependency("ruby-graphviz", "~> 1.2")
48
+ spec.add_dependency("sqlite3", "~> 2.6")
47
49
  spec.add_dependency("thor", "~> 1.3")
48
50
  spec.add_dependency("zeitwerk", "~> 2.6")
49
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roast-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: cli-kit
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: cli-ui
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +135,20 @@ dependencies:
121
135
  - - "~>"
122
136
  - !ruby/object:Gem::Version
123
137
  version: '1.2'
138
+ - !ruby/object:Gem::Dependency
139
+ name: sqlite3
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '2.6'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '2.6'
124
152
  - !ruby/object:Gem::Dependency
125
153
  name: thor
126
154
  requirement: !ruby/object:Gem::Requirement