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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +38 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +16 -14
- data/examples/grading/rb_test_runner +1 -1
- data/lib/roast/errors.rb +3 -0
- data/lib/roast/tools/coding_agent.rb +1 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/command_executor.rb +3 -1
- data/lib/roast/workflow/configuration_parser.rb +2 -0
- data/lib/roast/workflow/input_step.rb +2 -0
- data/lib/roast/workflow/interpolator.rb +23 -1
- data/lib/roast/workflow/sqlite_state_repository.rb +17 -17
- data/lib/roast/workflow/workflow_initializer.rb +80 -0
- data/lib/roast.rb +1 -0
- data/roast.gemspec +2 -0
- metadata +29 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1822868d0357ade29fdbdd3feab6a1beff23ef97621de9e1a9cacee366d5897
|
4
|
+
data.tar.gz: 00b9aff47853dc8f1f4ffa89bf571975fbe414632909b682fe6caab4e64bee51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95c32a5e010460dfe2e584c7b0ba8b7d617dc965210fc00343bd2c1f647c847f3be41be0bb07a951a41b5aa1447c770b36f4eba97985543f510938044e85453b
|
7
|
+
data.tar.gz: d221ec8fba42b7187ae30fa4171e2700a7b81b1092693f9fdf1f168a37b6318482f5be8cf3f1be4e8650fe7a56d87ef2a0f93fea13f633e24c95734c285fd75b
|
data/.gitignore
CHANGED
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
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
roast-ai (0.4.
|
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.
|
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.
|
90
|
+
faraday-multipart (1.1.1)
|
87
91
|
multipart-post (~> 2.0)
|
88
|
-
faraday-net_http (3.4.
|
92
|
+
faraday-net_http (3.4.1)
|
89
93
|
net-http (>= 0.5.0)
|
90
|
-
faraday-retry (2.3.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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 (
|
206
|
-
|
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
|
|
data/lib/roast/errors.rb
CHANGED
@@ -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
@@ -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]
|
@@ -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,
|
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
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.
|
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
|