roast-ai 0.4.6 → 0.4.7
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/.github/workflows/ci.yaml +3 -1
- data/.gitignore +7 -0
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +9 -1
- data/Rakefile +14 -4
- data/examples/available_tools_demo/workflow.yml +2 -2
- data/examples/cmd/basic_workflow.yml +0 -1
- data/examples/grading/js_test_runner +1 -1
- data/examples/grading/run_coverage.rb +1 -1
- data/examples/user_input/funny_name/workflow.yml +3 -4
- data/lib/roast/dsl/executor.rb +2 -1
- data/lib/roast/helpers/cmd_runner.rb +199 -0
- data/lib/roast/initializers.rb +1 -1
- data/lib/roast/tools/apply_diff.rb +1 -1
- data/lib/roast/tools/bash.rb +4 -4
- data/lib/roast/tools/cmd.rb +3 -5
- data/lib/roast/tools/coding_agent.rb +1 -1
- data/lib/roast/tools/grep.rb +6 -2
- data/lib/roast/tools/read_file.rb +2 -1
- data/lib/roast/tools/swarm.rb +2 -7
- data/lib/roast/tools.rb +10 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/command_executor.rb +3 -3
- data/lib/roast/workflow/resource_resolver.rb +1 -1
- data/lib/roast/workflow/shell_script_step.rb +1 -1
- data/lib/roast.rb +1 -0
- data/rubocop/cop/roast/use_cmd_runner.rb +93 -0
- data/rubocop/cop/roast.rb +4 -0
- data/sorbet/rbi/gems/docile@1.4.1.rbi +377 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +233 -2
- data/sorbet/rbi/gems/racc@1.8.1.rbi +6 -4
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +396 -2
- data/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3788 -2
- data/sorbet/rbi/gems/rubocop-ast@1.45.1.rbi +7747 -2
- data/sorbet/rbi/gems/rubocop-sorbet@0.10.5.rbi +2386 -0
- data/sorbet/rbi/gems/rubocop@1.77.0.rbi +62813 -2
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1311 -2
- data/sorbet/rbi/gems/simplecov-html@0.13.2.rbi +225 -0
- data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2259 -0
- data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +9 -0
- data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +125 -2
- data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +244 -2
- data/sorbet/tapioca/require.rb +2 -1
- metadata +9 -2
- data/lib/roast/helpers/timeout_handler.rb +0 -89
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1d2d1b26f4618a6036bc890e0426b5fe6c10dfd530e1c391d68b46680d2c2bc
|
4
|
+
data.tar.gz: e6b5670d414a2349c639b45a9b9d7b45b74a6dafc48cd706fedf8b55af88b6d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 447c20f3a2b170ce7ced6c14c5a1f62117299a984f513a6fb83f9c53934bbb6239c3dd11a791fe0437475af127a315120140b6e1918c124c25d774e3cc1cf2d3
|
7
|
+
data.tar.gz: cabe9380d1d1859c49d71d79547a1cc13f0d15f550aa373d82523384a0a8a8dda1f21bf1a6a2bfb2196bf3e4d6f1161ff047cbd498ca790c90e86006629e972b
|
data/.github/workflows/ci.yaml
CHANGED
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
inherit_from: .rubocop_todo.yml
|
2
2
|
|
3
|
+
require:
|
4
|
+
- ./rubocop/cop/roast
|
5
|
+
|
3
6
|
plugins:
|
4
7
|
- rubocop-sorbet
|
5
8
|
|
@@ -23,3 +26,14 @@ Sorbet/FalseSigil:
|
|
23
26
|
Exclude:
|
24
27
|
- "test/**/*"
|
25
28
|
- "examples/**/*"
|
29
|
+
|
30
|
+
Roast/UseCmdRunner:
|
31
|
+
Enabled: true
|
32
|
+
Exclude:
|
33
|
+
- 'lib/roast/helpers/cmd_runner.rb'
|
34
|
+
- 'roast.gemspec'
|
35
|
+
|
36
|
+
Style/MethodCallWithArgsParentheses:
|
37
|
+
Enabled: true
|
38
|
+
Exclude:
|
39
|
+
- 'test/**/*.rb'
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,11 @@ 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.7]
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- Infinite loop if `.roast` directory can't be found. (#382)
|
12
|
+
|
8
13
|
## [0.4.6]
|
9
14
|
|
10
15
|
### Added
|
data/Gemfile
CHANGED
@@ -16,9 +16,10 @@ gem "mocha"
|
|
16
16
|
gem "rake", require: false
|
17
17
|
gem "rubocop-shopify", require: false
|
18
18
|
gem "rubocop-sorbet", require: false
|
19
|
+
gem "simplecov", require: false
|
20
|
+
gem "minitest-rg"
|
19
21
|
gem "vcr", require: false
|
20
22
|
gem "webmock", require: false
|
21
|
-
gem "minitest-rg"
|
22
23
|
|
23
24
|
gem "sorbet", require: false
|
24
25
|
gem "tapioca", require: false
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
roast-ai (0.4.
|
4
|
+
roast-ai (0.4.7)
|
5
5
|
activesupport (>= 7.0)
|
6
6
|
cli-kit (~> 5.0)
|
7
7
|
cli-ui (= 2.3.0)
|
@@ -50,6 +50,7 @@ GEM
|
|
50
50
|
bigdecimal
|
51
51
|
rexml
|
52
52
|
diff-lcs (1.6.2)
|
53
|
+
docile (1.4.1)
|
53
54
|
dotenv (3.1.8)
|
54
55
|
drb (2.2.3)
|
55
56
|
dry-configurable (1.3.0)
|
@@ -214,6 +215,12 @@ GEM
|
|
214
215
|
ruby2_keywords (0.0.5)
|
215
216
|
securerandom (0.4.1)
|
216
217
|
shellany (0.0.1)
|
218
|
+
simplecov (0.22.0)
|
219
|
+
docile (~> 1.1)
|
220
|
+
simplecov-html (~> 0.11)
|
221
|
+
simplecov_json_formatter (~> 0.1)
|
222
|
+
simplecov-html (0.13.2)
|
223
|
+
simplecov_json_formatter (0.1.4)
|
217
224
|
sorbet (0.5.12414)
|
218
225
|
sorbet-static (= 0.5.12414)
|
219
226
|
sorbet-runtime (0.5.12414)
|
@@ -277,6 +284,7 @@ DEPENDENCIES
|
|
277
284
|
roast-ai!
|
278
285
|
rubocop-shopify
|
279
286
|
rubocop-sorbet
|
287
|
+
simplecov
|
280
288
|
sorbet
|
281
289
|
tapioca
|
282
290
|
vcr
|
data/Rakefile
CHANGED
@@ -4,17 +4,27 @@ require "bundler/gem_tasks"
|
|
4
4
|
require "rubocop/rake_task"
|
5
5
|
require "rake/testtask"
|
6
6
|
|
7
|
-
Rake::TestTask.new(:
|
7
|
+
Rake::TestTask.new(:minitest_all) do |t|
|
8
8
|
t.libs << "test"
|
9
9
|
t.libs << "lib"
|
10
10
|
t.test_files = FileList["test/**/*_test.rb"]
|
11
11
|
end
|
12
12
|
|
13
|
-
|
13
|
+
Rake::TestTask.new(:minitest_functional) do |t|
|
14
|
+
t.libs << "test"
|
15
|
+
t.libs << "lib"
|
16
|
+
t.test_files = FileList["test/functional/**/*_test.rb"]
|
17
|
+
end
|
14
18
|
|
15
|
-
|
19
|
+
Rake::TestTask.new(:minitest_old) do |t|
|
20
|
+
t.libs << "test"
|
21
|
+
t.libs << "lib"
|
22
|
+
t.test_files = FileList["test/functional/**/*_test.rb"]
|
23
|
+
end
|
16
24
|
|
17
|
-
task
|
25
|
+
task test: [:minitest_all]
|
26
|
+
|
27
|
+
RuboCop::RakeTask.new(:rubocop_ci)
|
18
28
|
|
19
29
|
RuboCop::RakeTask.new(:rubocop) do |task|
|
20
30
|
task.options = ["--autocorrect"]
|
@@ -41,7 +41,7 @@ class RunCoverage < Roast::Workflow::BaseStep
|
|
41
41
|
|
42
42
|
# Run the test_runner using shadowenv for environment consistency
|
43
43
|
command = "shadowenv exec --dir . -- #{test_runner_path} #{resolved_test_file} #{resolved_subject_file}"
|
44
|
-
output, status =
|
44
|
+
output, status = Roast::Helpers::CmdRunner.capture2(command)
|
45
45
|
|
46
46
|
unless status.success?
|
47
47
|
Roast::Helpers::Logger.error("Test runner exited with non-zero status: #{status.exitstatus}")
|
@@ -1,6 +1,5 @@
|
|
1
1
|
name: funny_name_backstory
|
2
2
|
description: Create a humorous backstory based on your name
|
3
|
-
model: anthropic:claude-3-5-sonnet
|
4
3
|
|
5
4
|
steps:
|
6
5
|
# Collect user's name
|
@@ -8,7 +7,7 @@ steps:
|
|
8
7
|
prompt: "What's your name?"
|
9
8
|
name: user_name
|
10
9
|
required: true
|
11
|
-
|
10
|
+
|
12
11
|
# Ask for preferences
|
13
12
|
- input:
|
14
13
|
prompt: "Pick a genre for your backstory:"
|
@@ -21,6 +20,6 @@ steps:
|
|
21
20
|
- "Victorian-Era Vampire Hunter"
|
22
21
|
- "Professional Cat Whisperer"
|
23
22
|
name: genre
|
24
|
-
|
23
|
+
|
25
24
|
# Generate the backstory
|
26
|
-
- create_backstory
|
25
|
+
- create_backstory
|
data/lib/roast/dsl/executor.rb
CHANGED
@@ -0,0 +1,199 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Roast
|
5
|
+
module Helpers
|
6
|
+
class CmdRunner
|
7
|
+
DEFAULT_TIMEOUT = 30
|
8
|
+
MAX_TIMEOUT = 3600 # 1 hour
|
9
|
+
|
10
|
+
@child_processes = {}
|
11
|
+
@child_processes_mutex = Mutex.new
|
12
|
+
|
13
|
+
class << self
|
14
|
+
#: (*untyped, **untyped) -> [String, Process::Status]
|
15
|
+
def capture2(*args, **options)
|
16
|
+
args = args #: as untyped
|
17
|
+
stdout, _stderr, status = capture3(*args, **options)
|
18
|
+
[stdout, status]
|
19
|
+
end
|
20
|
+
|
21
|
+
#: (*untyped, **untyped) -> [String, Process::Status]
|
22
|
+
def capture2e(*args, **options)
|
23
|
+
args = args #: as untyped
|
24
|
+
stdout, stderr, status = capture3(*args, **options)
|
25
|
+
combined_output = stdout + stderr
|
26
|
+
[combined_output, status]
|
27
|
+
end
|
28
|
+
|
29
|
+
#: (*untyped, **untyped) -> [String?, String?, Process::Status?]
|
30
|
+
def capture3(*args, **options)
|
31
|
+
args = args #: as untyped
|
32
|
+
popen3(*args, **options) do |stdin, stdout, stderr, wait_thr|
|
33
|
+
stdin.close # Prevent hanging on stdin-waiting commands
|
34
|
+
|
35
|
+
stdout_thread = threaded_read(stdout)
|
36
|
+
stderr_thread = threaded_read(stderr)
|
37
|
+
|
38
|
+
[stdout_thread.value, stderr_thread.value, wait_thr.value]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#: (*untyped, **untyped) -> bool
|
43
|
+
def system(*args, **options)
|
44
|
+
args = args #: as untyped
|
45
|
+
popen3(*args, **options) do |stdin, stdout, stderr, wait_thr|
|
46
|
+
stdin.close # Prevent hanging on stdin-waiting commands
|
47
|
+
|
48
|
+
stdout_thread = threaded_stream(from: stdout, to: $stdout)
|
49
|
+
stderr_thread = threaded_stream(from: stderr, to: $stderr)
|
50
|
+
|
51
|
+
stdout_thread.join
|
52
|
+
stderr_thread.join
|
53
|
+
|
54
|
+
wait_thr.value.success?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#: (*untyped, **untyped) ?{ (IO, IO, IO, Thread) -> untyped } -> [IO, IO, IO, Thread] | untyped
|
59
|
+
def popen3(*args, **options, &block)
|
60
|
+
args = args #: as untyped
|
61
|
+
|
62
|
+
timeout = options.delete(:timeout)
|
63
|
+
validate_timeout(timeout) unless timeout.nil?
|
64
|
+
|
65
|
+
raise ArgumentError, "Timeout provided but no block given" if !timeout.nil? && !block_given?
|
66
|
+
|
67
|
+
# Mirror Open3.popen3 behavior - if no block, return the IO objects and thread
|
68
|
+
unless block_given?
|
69
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(*args, **options)
|
70
|
+
track_child_process(wait_thr.pid, presentable_command(args))
|
71
|
+
return [stdin, stdout, stderr, wait_thr]
|
72
|
+
end
|
73
|
+
|
74
|
+
Open3.popen3(*args, **options) do |stdin, stdout, stderr, wait_thr|
|
75
|
+
track_child_process(wait_thr.pid, presentable_command(args))
|
76
|
+
|
77
|
+
runnable = proc { yield(stdin, stdout, stderr, wait_thr) } #: Proc
|
78
|
+
timeout.nil? ? runnable.call : Timeout.timeout(timeout, &runnable)
|
79
|
+
rescue Timeout::Error => e
|
80
|
+
raise e.class, "Command '#{presentable_command(args)}' timed out after #{timeout} seconds: #{e.message}"
|
81
|
+
ensure
|
82
|
+
cleanup_child_process(wait_thr.pid) unless wait_thr.nil?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#: -> void
|
87
|
+
def cleanup_all_children
|
88
|
+
Thread.new do # Thread to avoid issues with calling a mutex in a signal handler
|
89
|
+
child_processes = all_child_processes
|
90
|
+
Thread.current.exit if child_processes.empty?
|
91
|
+
|
92
|
+
child_processes.each do |pid, info|
|
93
|
+
Roast::Helpers::Logger.info("Cleaning up PID #{pid}: #{info[:command]}")
|
94
|
+
cleanup_child_process(pid)
|
95
|
+
end
|
96
|
+
end.join
|
97
|
+
end
|
98
|
+
|
99
|
+
#: (Integer?) -> Integer
|
100
|
+
def normalize_timeout(timeout)
|
101
|
+
return DEFAULT_TIMEOUT if timeout.nil? || timeout <= 0
|
102
|
+
|
103
|
+
[timeout, MAX_TIMEOUT].min
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
#: (Integer) -> void
|
109
|
+
def validate_timeout(timeout)
|
110
|
+
if timeout <= 0 || timeout > MAX_TIMEOUT
|
111
|
+
raise ArgumentError, "Invalid timeout value: #{timeout.inspect}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
#: (Array) -> String
|
116
|
+
def presentable_command(args)
|
117
|
+
args.flatten.map(&:to_s).join(" ")
|
118
|
+
end
|
119
|
+
|
120
|
+
#: (IO) -> Thread
|
121
|
+
def threaded_read(stream)
|
122
|
+
Thread.new do
|
123
|
+
buffer = ""
|
124
|
+
stream.each_line do |line|
|
125
|
+
buffer += line
|
126
|
+
end
|
127
|
+
buffer
|
128
|
+
rescue IOError => e
|
129
|
+
Roast::Helpers::Logger.debug("IOError while capturing output: #{e.message}")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
#: (from: IO, to: IO) -> Thread
|
134
|
+
def threaded_stream(from:, to:)
|
135
|
+
Thread.new do
|
136
|
+
from.each_line do |line|
|
137
|
+
to.puts(line)
|
138
|
+
end
|
139
|
+
rescue IOError => e
|
140
|
+
Roast::Helpers::Logger.debug("IOError while streaming output: #{e.message}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
#: (Integer, String) -> void
|
145
|
+
def track_child_process(pid, command)
|
146
|
+
@child_processes_mutex.synchronize do
|
147
|
+
@child_processes[pid] = {
|
148
|
+
command: command,
|
149
|
+
started_at: Time.now,
|
150
|
+
}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#: (Integer) -> void
|
155
|
+
def untrack_child_process(pid)
|
156
|
+
@child_processes_mutex.synchronize { @child_processes.delete(pid) }
|
157
|
+
end
|
158
|
+
|
159
|
+
#: -> Hash[Integer, { command: String, started_at: Time }]
|
160
|
+
def all_child_processes
|
161
|
+
@child_processes_mutex.synchronize { @child_processes.dup }
|
162
|
+
end
|
163
|
+
|
164
|
+
#: (Integer) -> void
|
165
|
+
def cleanup_child_process(pid)
|
166
|
+
untrack_child_process(pid)
|
167
|
+
|
168
|
+
return unless process_running?(pid)
|
169
|
+
|
170
|
+
[0.1, 0.2, 0.5].each do |sleep_time|
|
171
|
+
Process.kill("TERM", pid)
|
172
|
+
break unless process_running?(pid)
|
173
|
+
|
174
|
+
sleep(sleep_time) # Grace period to let the process terminate
|
175
|
+
end
|
176
|
+
|
177
|
+
# Force kill if still alive
|
178
|
+
Process.kill("KILL", pid) if process_running?(pid)
|
179
|
+
rescue Errno::ESRCH
|
180
|
+
# Process already terminated, which is fine
|
181
|
+
rescue Errno::EPERM
|
182
|
+
# Permission denied - process may be owned by different user
|
183
|
+
Roast::Helpers::Logger.debug("Could not kill process #{pid}: Permission denied")
|
184
|
+
rescue => e
|
185
|
+
# Catch any other unexpected errors during cleanup
|
186
|
+
Roast::Helpers::Logger.debug("Unexpected error during process cleanup: #{e.message}")
|
187
|
+
end
|
188
|
+
|
189
|
+
#: (Integer) -> bool
|
190
|
+
def process_running?(pid)
|
191
|
+
Process.getpgid(pid)
|
192
|
+
true
|
193
|
+
rescue Errno::ESRCH
|
194
|
+
false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
data/lib/roast/initializers.rb
CHANGED
@@ -7,7 +7,7 @@ module Roast
|
|
7
7
|
def config_root(starting_path = Dir.pwd, ending_path = File.dirname(Dir.home))
|
8
8
|
paths = []
|
9
9
|
candidate = starting_path
|
10
|
-
while candidate != ending_path
|
10
|
+
while candidate != ending_path && candidate != "/"
|
11
11
|
paths << File.join(candidate, ".roast")
|
12
12
|
candidate = File.dirname(candidate)
|
13
13
|
end
|
@@ -93,7 +93,7 @@ module Roast
|
|
93
93
|
File.write(temp_file, updated_content)
|
94
94
|
|
95
95
|
# Run git diff
|
96
|
-
diff_output =
|
96
|
+
diff_output, _status = Roast::Helpers::CmdRunner.capture2e("git", "diff", "--no-index", "--no-prefix", file_path, temp_file)
|
97
97
|
|
98
98
|
if diff_output.empty?
|
99
99
|
Roast::Helpers::Logger.info("No differences found (files are identical)\n")
|
data/lib/roast/tools/bash.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
require "English"
|
5
5
|
require "roast/helpers/logger"
|
6
|
-
require "roast/helpers/timeout_handler"
|
7
6
|
|
8
7
|
module Roast
|
9
8
|
module Tools
|
@@ -33,13 +32,14 @@ module Roast
|
|
33
32
|
Roast::Helpers::Logger.warn("⚠️ WARNING: Unrestricted bash execution - use with caution!\n")
|
34
33
|
end
|
35
34
|
|
36
|
-
|
35
|
+
timeout = Roast::Helpers::CmdRunner.normalize_timeout(timeout)
|
36
|
+
|
37
|
+
result, status = Roast::Helpers::CmdRunner.capture2e(
|
37
38
|
"#{command} 2>&1",
|
38
39
|
timeout: timeout,
|
39
|
-
working_directory: Dir.pwd,
|
40
40
|
)
|
41
41
|
|
42
|
-
format_output(command, result,
|
42
|
+
format_output(command, result, status.exitstatus)
|
43
43
|
rescue Timeout::Error => e
|
44
44
|
Roast::Helpers::Logger.error(e.message + "\n")
|
45
45
|
e.message
|
data/lib/roast/tools/cmd.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
require "English"
|
5
5
|
require "roast/helpers/logger"
|
6
|
-
require "roast/helpers/timeout_handler"
|
7
6
|
|
8
7
|
module Roast
|
9
8
|
module Tools
|
@@ -140,7 +139,7 @@ module Roast
|
|
140
139
|
end
|
141
140
|
|
142
141
|
def execute_command(command, command_prefix, timeout)
|
143
|
-
timeout = Roast::Helpers::
|
142
|
+
timeout = Roast::Helpers::CmdRunner.normalize_timeout(timeout)
|
144
143
|
|
145
144
|
full_command = if command_prefix == "dev"
|
146
145
|
"bash -l -c '#{command.gsub("'", "\\'")}'"
|
@@ -148,13 +147,12 @@ module Roast
|
|
148
147
|
command
|
149
148
|
end
|
150
149
|
|
151
|
-
result,
|
150
|
+
result, status = Roast::Helpers::CmdRunner.capture2e(
|
152
151
|
full_command,
|
153
152
|
timeout: timeout,
|
154
|
-
working_directory: Dir.pwd,
|
155
153
|
)
|
156
154
|
|
157
|
-
format_output(command, result,
|
155
|
+
format_output(command, result, status.exitstatus)
|
158
156
|
rescue Timeout::Error => e
|
159
157
|
Roast::Helpers::Logger.error(e.message + "\n")
|
160
158
|
e.message
|
@@ -105,7 +105,7 @@ module Roast
|
|
105
105
|
command = "cat #{temp_file.path} | #{command_to_run}"
|
106
106
|
result = ""
|
107
107
|
|
108
|
-
|
108
|
+
Roast::Helpers::CmdRunner.popen3(command) do |stdin, stdout, stderr, wait_thread|
|
109
109
|
stdin.close
|
110
110
|
if expect_json_output
|
111
111
|
stdout.each_line do |line|
|
data/lib/roast/tools/grep.rb
CHANGED
@@ -28,13 +28,17 @@ module Roast
|
|
28
28
|
def call(string)
|
29
29
|
Roast::Helpers::Logger.info("🔍 Grepping for string: #{string}\n")
|
30
30
|
|
31
|
-
|
31
|
+
# Check if ripgrep is available by trying to run it with --version
|
32
|
+
unless Roast::Helpers::CmdRunner.system("rg --version > /dev/null 2>&1")
|
32
33
|
raise "ripgrep is not available. Please install it using your package manager (e.g., brew install rg) and make sure it's on your PATH."
|
33
34
|
end
|
34
35
|
|
35
36
|
# Use Open3 to safely pass the string as an argument, avoiding shell injection
|
36
37
|
cmd = ["rg", "-C", "4", "--trim", "--color=never", "--heading", "-F", "--", string, "."]
|
37
|
-
stdout,
|
38
|
+
stdout, stderr, status = Roast::Helpers::CmdRunner.capture3(*cmd)
|
39
|
+
unless status.success?
|
40
|
+
return "Error grepping for string: Command failed: #{stderr}"
|
41
|
+
end
|
38
42
|
|
39
43
|
# Limit output to MAX_RESULT_LINES
|
40
44
|
lines = stdout.lines
|
@@ -34,7 +34,8 @@ module Roast
|
|
34
34
|
path = File.expand_path(path)
|
35
35
|
Roast::Helpers::Logger.info("📖 Reading file: #{path}\n")
|
36
36
|
if File.directory?(path)
|
37
|
-
|
37
|
+
output, _status = Roast::Helpers::CmdRunner.capture2e("ls", "-la", path)
|
38
|
+
output
|
38
39
|
else
|
39
40
|
File.read(path)
|
40
41
|
end
|
data/lib/roast/tools/swarm.rb
CHANGED
@@ -85,14 +85,9 @@ module Roast
|
|
85
85
|
# Build the swarm command with proper escaping
|
86
86
|
command = build_swarm_command(prompt, config_path)
|
87
87
|
|
88
|
-
result = ""
|
89
|
-
|
90
88
|
# Execute the command directly with the prompt included
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
exit_status = $CHILD_STATUS.exitstatus
|
89
|
+
result, status = Roast::Helpers::CmdRunner.capture2e(command)
|
90
|
+
exit_status = status.exitstatus
|
96
91
|
|
97
92
|
format_output(command, result, exit_status)
|
98
93
|
end
|
data/lib/roast/tools.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "roast/helpers/cmd_runner"
|
5
|
+
|
4
6
|
module Roast
|
5
7
|
# @requires_ancestor: Kernel
|
6
8
|
module Tools
|
@@ -33,7 +35,14 @@ module Roast
|
|
33
35
|
Signal.trap("INT") do
|
34
36
|
puts "\n\nCaught CTRL-C! Printing before exiting:\n"
|
35
37
|
puts JSON.pretty_generate(object_to_inspect)
|
36
|
-
|
38
|
+
|
39
|
+
begin
|
40
|
+
Roast::Helpers::CmdRunner.cleanup_all_children
|
41
|
+
rescue => e
|
42
|
+
puts "Error interrupting tracked child processes: #{e.message}"
|
43
|
+
end
|
44
|
+
|
45
|
+
exit(130)
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
data/lib/roast/version.rb
CHANGED
@@ -24,14 +24,14 @@ module Roast
|
|
24
24
|
def execute(command_string, exit_on_error: true)
|
25
25
|
command = extract_command(command_string)
|
26
26
|
|
27
|
-
output =
|
28
|
-
exit_status =
|
27
|
+
output, status = Roast::Helpers::CmdRunner.capture2e(command)
|
28
|
+
exit_status = status.exitstatus
|
29
29
|
|
30
30
|
handle_execution_result(
|
31
31
|
command: command,
|
32
32
|
output: output,
|
33
33
|
exit_status: exit_status,
|
34
|
-
success:
|
34
|
+
success: status.success?,
|
35
35
|
exit_on_error: exit_on_error,
|
36
36
|
)
|
37
37
|
rescue ArgumentError, CommandExecutionError
|
@@ -52,7 +52,7 @@ module Roast
|
|
52
52
|
def process_shell_command(command)
|
53
53
|
# If it's a bash command with the $(command) syntax
|
54
54
|
if command =~ /^\$\((.*)\)$/
|
55
|
-
return
|
55
|
+
return Roast::Helpers::CmdRunner.capture2e({}, ::Regexp.last_match(1).to_s).first.strip
|
56
56
|
end
|
57
57
|
|
58
58
|
# Not a shell command, return as is
|