roast-ai 0.4.10 → 0.5.1
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/.claude/commands/docs/write-comments.md +36 -0
- data/.github/CODEOWNERS +1 -1
- data/.github/workflows/ci.yaml +10 -6
- data/.gitignore +0 -1
- data/.rubocop.yml +7 -1
- data/.ruby-version +1 -1
- data/CLAUDE.md +2 -2
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +19 -18
- data/Gemfile.lock +35 -58
- data/README.md +118 -1432
- data/README_LEGACY.md +1464 -0
- data/Rakefile +39 -4
- data/dev.yml +29 -0
- data/dsl/agent_sessions.rb +20 -0
- data/dsl/async_cogs.rb +49 -0
- data/dsl/async_cogs_complex.rb +67 -0
- data/dsl/call.rb +44 -0
- data/dsl/collect_from.rb +72 -0
- data/dsl/json_output.rb +28 -0
- data/dsl/map.rb +55 -0
- data/dsl/map_reduce.rb +37 -0
- data/dsl/map_with_index.rb +49 -0
- data/dsl/next_break.rb +45 -0
- data/dsl/next_break_parallel.rb +44 -0
- data/dsl/outputs.rb +39 -0
- data/dsl/outputs_bang.rb +36 -0
- data/dsl/parallel_map.rb +37 -0
- data/dsl/prompts/simple_prompt.md.erb +3 -0
- data/dsl/prototype.rb +5 -7
- data/dsl/repeat_loop_results.rb +53 -0
- data/dsl/ruby_cog.rb +72 -0
- data/dsl/simple_agent.rb +18 -0
- data/dsl/simple_chat.rb +15 -1
- data/dsl/simple_repeat.rb +29 -0
- data/dsl/skip.rb +36 -0
- data/dsl/step_communication.rb +2 -3
- data/dsl/targets_and_params.rb +57 -0
- data/dsl/temperature.rb +17 -0
- data/dsl/temporary_directory.rb +22 -0
- data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
- data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
- data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
- data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
- data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
- data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
- data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
- data/dsl/tutorial/03_targets_and_params/README.md +230 -0
- data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
- data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
- data/dsl/tutorial/04_configuration_options/README.md +209 -0
- data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
- data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
- data/dsl/tutorial/05_control_flow/README.md +156 -0
- data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
- data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
- data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
- data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
- data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
- data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
- data/dsl/tutorial/07_processing_collections/README.md +152 -0
- data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
- data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
- data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
- data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
- data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
- data/dsl/tutorial/09_async_cogs/README.md +197 -0
- data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
- data/dsl/tutorial/README.md +222 -0
- data/dsl/working_directory.rb +16 -0
- data/exe/roast +1 -1
- data/internal/documentation/architectural-notes.md +115 -0
- data/internal/documentation/doc-comments-external.md +686 -0
- data/internal/documentation/doc-comments-internal.md +342 -0
- data/internal/documentation/doc-comments.md +211 -0
- data/lib/roast/dsl/cog/config.rb +274 -3
- data/lib/roast/dsl/cog/input.rb +53 -10
- data/lib/roast/dsl/cog/output.rb +297 -8
- data/lib/roast/dsl/cog/registry.rb +35 -3
- data/lib/roast/dsl/cog/stack.rb +1 -1
- data/lib/roast/dsl/cog/store.rb +5 -5
- data/lib/roast/dsl/cog.rb +70 -14
- data/lib/roast/dsl/cog_input_context.rb +36 -1
- data/lib/roast/dsl/cog_input_manager.rb +116 -7
- data/lib/roast/dsl/cogs/agent/config.rb +465 -0
- data/lib/roast/dsl/cogs/agent/input.rb +81 -0
- data/lib/roast/dsl/cogs/agent/output.rb +59 -0
- data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
- data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
- data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
- data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
- data/lib/roast/dsl/cogs/agent.rb +75 -0
- data/lib/roast/dsl/cogs/chat/config.rb +453 -0
- data/lib/roast/dsl/cogs/chat/input.rb +92 -0
- data/lib/roast/dsl/cogs/chat/output.rb +64 -0
- data/lib/roast/dsl/cogs/chat/session.rb +68 -0
- data/lib/roast/dsl/cogs/chat.rb +59 -56
- data/lib/roast/dsl/cogs/cmd.rb +251 -61
- data/lib/roast/dsl/cogs/ruby.rb +171 -0
- data/lib/roast/dsl/command_runner.rb +191 -0
- data/lib/roast/dsl/config_manager.rb +58 -11
- data/lib/roast/dsl/control_flow.rb +41 -0
- data/lib/roast/dsl/execution_manager.rb +162 -32
- data/lib/roast/dsl/nil_assertions.rb +23 -0
- data/lib/roast/dsl/system_cog/params.rb +32 -0
- data/lib/roast/dsl/system_cog.rb +36 -0
- data/lib/roast/dsl/system_cogs/call.rb +163 -0
- data/lib/roast/dsl/system_cogs/map.rb +454 -0
- data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
- data/lib/roast/dsl/workflow.rb +26 -16
- data/lib/roast/dsl/workflow_context.rb +20 -0
- data/lib/roast/dsl/workflow_params.rb +24 -0
- data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
- data/lib/roast/sorbet_runtime_stub.rb +154 -0
- data/lib/roast/tools/apply_diff.rb +1 -3
- data/lib/roast/tools/cmd.rb +4 -3
- data/lib/roast/tools/read_file.rb +1 -1
- data/lib/roast/tools/update_files.rb +1 -1
- data/lib/roast/tools/write_file.rb +1 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_workflow.rb +4 -0
- data/lib/roast/workflow/step_loader.rb +14 -2
- data/lib/roast-ai.rb +4 -0
- data/lib/roast.rb +58 -21
- data/{roast.gemspec → roast-ai.gemspec} +9 -13
- data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
- data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
- data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
- data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
- data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
- data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
- data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
- data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
- data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
- data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
- data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1185 -5
- data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +311 -5
- data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +486 -5
- data/sorbet/tapioca/config.yml +6 -0
- data/sorbet/tapioca/require.rb +2 -0
- metadata +157 -30
- data/dsl/less_simple.rb +0 -112
- data/dsl/scoped_executors.rb +0 -28
- data/dsl/simple.rb +0 -8
- data/lib/roast/dsl/cogs/execute.rb +0 -46
- data/lib/roast/dsl/cogs/graph.rb +0 -53
- data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
- data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
- data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
- data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
- data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
- data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
- data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
- data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
- data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
- data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
- data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
- data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
- data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
- data/sorbet/rbi/gems/rack@2.2.19.rbi +0 -5676
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
- data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
data/lib/roast/dsl/cogs/chat.rb
CHANGED
|
@@ -4,73 +4,76 @@
|
|
|
4
4
|
module Roast
|
|
5
5
|
module DSL
|
|
6
6
|
module Cogs
|
|
7
|
+
# Chat cog for pure LLM interaction
|
|
8
|
+
#
|
|
9
|
+
# The chat cog provides pure LLM interaction without local system access. While it
|
|
10
|
+
# cannot access local files or run local tools, it can still perform complex reasoning and
|
|
11
|
+
# access any cloud-based tools and MCP servers according to the capabilities of the model and
|
|
12
|
+
# the capabilities that may be provided to it by the LLM provider.
|
|
13
|
+
#
|
|
14
|
+
# Key characteristics:
|
|
15
|
+
# - No access to local filesystem (cannot read or write local files)
|
|
16
|
+
# - Cannot run local tools or commands
|
|
17
|
+
# - Can access cloud-based tools and MCP servers provided by the LLM provider
|
|
18
|
+
# - Performs request-response interactions
|
|
19
|
+
# - Does not currently maintain conversation state across invocations (not yet implemented)
|
|
20
|
+
# - Does not currently support automatic session resumption (not yet implemented)
|
|
21
|
+
#
|
|
22
|
+
# For tasks requiring local filesystem access or locally-configured tools, use the `agent` cog instead.
|
|
7
23
|
class Chat < Cog
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
24
|
+
# The configuration object for this chat cog instance
|
|
25
|
+
#
|
|
26
|
+
#: Roast::DSL::Cogs::Chat::Config
|
|
27
|
+
attr_reader :config
|
|
11
28
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
# Execute the chat completion with the given input and return the output
|
|
30
|
+
#
|
|
31
|
+
#: (Input) -> Output
|
|
32
|
+
def execute(input)
|
|
33
|
+
chat = ruby_llm_context.chat(
|
|
34
|
+
model: config.valid_model,
|
|
35
|
+
provider: config.valid_provider!,
|
|
36
|
+
assume_model_exists: !config.verify_model_exists?,
|
|
37
|
+
)
|
|
38
|
+
input.valid_session&.apply!(chat)
|
|
39
|
+
chat = chat.with_temperature(config.valid_temperature) if config.valid_temperature
|
|
40
|
+
num_existing_messages = chat.messages.length
|
|
22
41
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
response = chat.ask(input.valid_prompt!)
|
|
43
|
+
chat.messages[num_existing_messages..].each do |message|
|
|
44
|
+
case message.role
|
|
45
|
+
when :user
|
|
46
|
+
puts "[USER PROMPT] #{message.content}" if config.show_prompt?
|
|
47
|
+
when :assistant
|
|
48
|
+
puts "[LLM RESPONSE] #{message.content}" if config.show_response?
|
|
49
|
+
else
|
|
50
|
+
# No other message types are expected, but let's show them if they do appear
|
|
51
|
+
# but only the user has requested some form of output
|
|
52
|
+
puts "[UNKNOWN] #{message.content}" if config.show_prompt? || config.show_response?
|
|
27
53
|
end
|
|
28
54
|
end
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def initialize(response)
|
|
37
|
-
super()
|
|
38
|
-
@response = response
|
|
55
|
+
if config.show_stats?
|
|
56
|
+
temperature = chat.instance_variable_get(:@temperature)
|
|
57
|
+
puts "[LLM STATS]"
|
|
58
|
+
puts "\tModel: #{response.model_id}"
|
|
59
|
+
puts "\tTemperature: #{format("%0.2f", temperature)}" if temperature
|
|
60
|
+
puts "\tInput Tokens: #{response.input_tokens}"
|
|
61
|
+
puts "\tOutput Tokens: #{response.output_tokens}"
|
|
39
62
|
end
|
|
40
|
-
end
|
|
41
63
|
|
|
42
|
-
|
|
43
|
-
#: () -> String?
|
|
44
|
-
def openai_api_key
|
|
45
|
-
@values[:openai_api_key] ||= ENV["OPENAI_API_KEY"]
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
#: () -> String?
|
|
49
|
-
def openai_api_base_url
|
|
50
|
-
@values[:openai_api_base_url] ||= ENV["OPENAI_API_BASE_URL"]
|
|
51
|
-
end
|
|
64
|
+
Output.new(Session.from_chat(chat), response.content)
|
|
52
65
|
end
|
|
53
66
|
|
|
54
|
-
|
|
55
|
-
def execute(input)
|
|
56
|
-
config = @config #: as Config
|
|
57
|
-
RubyLLM.configure do |ruby_llm_config|
|
|
58
|
-
ruby_llm_config.openai_api_key = config.openai_api_key
|
|
59
|
-
ruby_llm_config.openai_api_base = config.openai_api_base_url
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
chat = RubyLLM.chat
|
|
63
|
-
resp = chat.ask(input.prompt)
|
|
64
|
-
puts "Model: #{resp.model_id}"
|
|
65
|
-
puts "Role: #{resp.role}"
|
|
66
|
-
puts "Input Tokens: #{resp.input_tokens}"
|
|
67
|
-
puts "Output Tokens: #{resp.output_tokens}"
|
|
67
|
+
private
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
# Get a RubyLLM context configured for this chat cog
|
|
70
|
+
#
|
|
71
|
+
#: () -> RubyLLM::Context
|
|
72
|
+
def ruby_llm_context
|
|
73
|
+
@ruby_llm_context ||= RubyLLM.context do |context|
|
|
74
|
+
context.openai_api_key = config.valid_api_key!
|
|
75
|
+
context.openai_api_base = config.valid_base_url
|
|
71
76
|
end
|
|
72
|
-
|
|
73
|
-
Output.new(resp.content)
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
end
|
data/lib/roast/dsl/cogs/cmd.rb
CHANGED
|
@@ -5,25 +5,244 @@ module Roast
|
|
|
5
5
|
module DSL
|
|
6
6
|
module Cogs
|
|
7
7
|
class Cmd < Cog
|
|
8
|
+
# Configure the `cmd` cog
|
|
9
|
+
#
|
|
10
|
+
# See sorbet/rbi/shims/lib/roast/dsl/config_context.rbi for full class documentation.
|
|
11
|
+
class Config < Cog::Config
|
|
12
|
+
# Configure the cog to consider itself failed if the command returns a non-zero exit status
|
|
13
|
+
#
|
|
14
|
+
# Enabled by default. When enabled, a non-zero exit status will mark the cog as failed,
|
|
15
|
+
# which may also abort the workflow depending on the cog's `abort_on_failure` configuration.
|
|
16
|
+
#
|
|
17
|
+
# #### Inverse Methods
|
|
18
|
+
# - `no_fail_on_error!`
|
|
19
|
+
#
|
|
20
|
+
# #### See Also
|
|
21
|
+
# - `fail_on_error?`
|
|
22
|
+
# - `abort_on_failure!`
|
|
23
|
+
#
|
|
24
|
+
#: () -> void
|
|
25
|
+
def fail_on_error!
|
|
26
|
+
@values[:fail_on_error] = true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Configure the cog __not__ to consider itself failed if the command returns a non-zero exit status
|
|
30
|
+
#
|
|
31
|
+
# When disabled, the cog will complete successfully regardless of the command's exit status.
|
|
32
|
+
# The exit status will still be available in the output for inspection.
|
|
33
|
+
#
|
|
34
|
+
# #### Inverse Methods
|
|
35
|
+
# - `fail_on_error!`
|
|
36
|
+
#
|
|
37
|
+
# #### See Also
|
|
38
|
+
# - `fail_on_error?`
|
|
39
|
+
#
|
|
40
|
+
#: () -> void
|
|
41
|
+
def no_fail_on_error!
|
|
42
|
+
@values[:fail_on_error] = false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check if the cog is configured to fail when the command returns a non-zero exit status
|
|
46
|
+
#
|
|
47
|
+
# #### See Also
|
|
48
|
+
# - `fail_on_error!`
|
|
49
|
+
# - `no_fail_on_error!`
|
|
50
|
+
#
|
|
51
|
+
#: () -> bool
|
|
52
|
+
def fail_on_error?
|
|
53
|
+
@values[:fail_on_error] != false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Configure the cog to write STDOUT to the console
|
|
57
|
+
#
|
|
58
|
+
# Disabled by default.
|
|
59
|
+
#
|
|
60
|
+
# #### See Also
|
|
61
|
+
# - `no_show_stdout!`
|
|
62
|
+
# - `show_stdout?`
|
|
63
|
+
# - `display!`
|
|
64
|
+
#
|
|
65
|
+
#: () -> void
|
|
66
|
+
def show_stdout!
|
|
67
|
+
raise "⚠️ DEPRECATION: use #{__callee__.to_s.sub("print_", "show_")} instead of #{__callee__}" if __callee__.to_s.include?("print_")
|
|
68
|
+
|
|
69
|
+
@values[:show_stdout] = true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Configure the cog __not__ to write STDOUT to the console
|
|
73
|
+
#
|
|
74
|
+
# #### See Also
|
|
75
|
+
# - `show_stdout!`
|
|
76
|
+
# - `show_stdout?`
|
|
77
|
+
# - `no_display!`
|
|
78
|
+
#
|
|
79
|
+
#: () -> void
|
|
80
|
+
def no_show_stdout!
|
|
81
|
+
raise "⚠️ DEPRECATION: use #{__callee__.to_s.sub("print_", "show_")} instead of #{__callee__}" if __callee__.to_s.include?("print_")
|
|
82
|
+
|
|
83
|
+
@values[:show_stdout] = false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check if the cog is configured to write STDOUT to the console
|
|
87
|
+
#
|
|
88
|
+
# #### See Also
|
|
89
|
+
# - `show_stdout!`
|
|
90
|
+
# - `no_show_stdout!`
|
|
91
|
+
#
|
|
92
|
+
#: () -> bool
|
|
93
|
+
def show_stdout?
|
|
94
|
+
!!@values[:show_stdout]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Configure the cog to write STDERR to the console
|
|
98
|
+
#
|
|
99
|
+
# Disabled by default.
|
|
100
|
+
#
|
|
101
|
+
# #### See Also
|
|
102
|
+
# - `no_show_stderr!`
|
|
103
|
+
# - `show_stderr?`
|
|
104
|
+
# - `display!`
|
|
105
|
+
#
|
|
106
|
+
#: () -> void
|
|
107
|
+
def show_stderr!
|
|
108
|
+
raise "⚠️ DEPRECATION: use #{__callee__.to_s.sub("print_", "show_")} instead of #{__callee__}" if __callee__.to_s.include?("print_")
|
|
109
|
+
|
|
110
|
+
@values[:show_stderr] = true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Configure the cog __not__ to write STDERR to the console
|
|
114
|
+
#
|
|
115
|
+
# #### See Also
|
|
116
|
+
# - `show_stderr!`
|
|
117
|
+
# - `show_stderr?`
|
|
118
|
+
# - `no_display!`
|
|
119
|
+
#
|
|
120
|
+
#: () -> void
|
|
121
|
+
def no_show_stderr!
|
|
122
|
+
raise "⚠️ DEPRECATION: use #{__callee__.to_s.sub("print_", "show_")} instead of #{__callee__}" if __callee__.to_s.include?("print_")
|
|
123
|
+
|
|
124
|
+
@values[:show_stderr] = false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Check if the cog is configured to write STDERR to the console
|
|
128
|
+
#
|
|
129
|
+
# #### See Also
|
|
130
|
+
# - `show_stderr!`
|
|
131
|
+
# - `no_show_stderr!`
|
|
132
|
+
#
|
|
133
|
+
#: () -> bool
|
|
134
|
+
def show_stderr?
|
|
135
|
+
!!@values[:show_stderr]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Configure the cog to write both STDOUT and STDERR to the console
|
|
139
|
+
#
|
|
140
|
+
# #### Alias Methods
|
|
141
|
+
# - `display!`
|
|
142
|
+
# - `print_all!`
|
|
143
|
+
#
|
|
144
|
+
# #### See Also
|
|
145
|
+
# - `no_display!`
|
|
146
|
+
# - `show_stdout!`
|
|
147
|
+
# - `show_stderr!`
|
|
148
|
+
#
|
|
149
|
+
#: () -> void
|
|
150
|
+
def display!
|
|
151
|
+
raise "⚠️ DEPRECATION: use display! instead of #{__callee__}" if __callee__.to_s.include?("print_")
|
|
152
|
+
|
|
153
|
+
@values[:show_stdout] = true
|
|
154
|
+
@values[:show_stderr] = true
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Configure the cog to write __no output__ to the console, neither STDOUT nor STDERR
|
|
158
|
+
#
|
|
159
|
+
# #### Alias Methods
|
|
160
|
+
# - `no_display!`
|
|
161
|
+
# - `print_none!`
|
|
162
|
+
# - `quiet!`
|
|
163
|
+
#
|
|
164
|
+
# #### See Also
|
|
165
|
+
# - `display!`
|
|
166
|
+
# - `no_show_stdout!`
|
|
167
|
+
# - `no_show_stderr!`
|
|
168
|
+
#
|
|
169
|
+
#: () -> void
|
|
170
|
+
def no_display!
|
|
171
|
+
raise "⚠️ DEPRECATION: use no_display! instead of #{__callee__}" if __callee__.to_s.include?("print_")
|
|
172
|
+
|
|
173
|
+
@values[:show_stdout] = false
|
|
174
|
+
@values[:show_stderr] = false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Check if the cog is configured to display any output while running
|
|
178
|
+
#
|
|
179
|
+
# #### See Also
|
|
180
|
+
# - `display!`
|
|
181
|
+
# - `no_display!`
|
|
182
|
+
# - `show_stdout?`
|
|
183
|
+
# - `show_stderr?`
|
|
184
|
+
#
|
|
185
|
+
#: () -> bool
|
|
186
|
+
def display?
|
|
187
|
+
show_stdout? || show_stderr?
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
alias_method(:quiet!, :no_display!)
|
|
191
|
+
alias_method(:print_all!, :display!)
|
|
192
|
+
alias_method(:print_none!, :no_display!)
|
|
193
|
+
alias_method(:print_stdout!, :show_stdout!)
|
|
194
|
+
alias_method(:no_print_stdout!, :no_show_stdout!)
|
|
195
|
+
alias_method(:print_stderr!, :show_stderr!)
|
|
196
|
+
alias_method(:no_print_stderr!, :no_show_stderr!)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Input specification for the cmd cog
|
|
200
|
+
#
|
|
201
|
+
# The cmd cog requires a command to execute, optionally with arguments.
|
|
202
|
+
# The command will be executed in the configured working directory.
|
|
8
203
|
class Input < Cog::Input
|
|
204
|
+
# The command to execute
|
|
205
|
+
#
|
|
9
206
|
#: String?
|
|
10
207
|
attr_accessor :command
|
|
11
208
|
|
|
209
|
+
# Arguments to pass to the command
|
|
210
|
+
#
|
|
12
211
|
#: Array[String]
|
|
13
212
|
attr_accessor :args
|
|
14
213
|
|
|
214
|
+
# Data to pass to command's standard input
|
|
215
|
+
#
|
|
216
|
+
#: String?
|
|
217
|
+
attr_accessor :stdin
|
|
218
|
+
|
|
15
219
|
#: () -> void
|
|
16
220
|
def initialize
|
|
17
221
|
super
|
|
18
|
-
@command = nil
|
|
19
222
|
@args = []
|
|
20
223
|
end
|
|
21
224
|
|
|
225
|
+
# Validate that the input has all required parameters
|
|
226
|
+
#
|
|
227
|
+
# This method ensures that a command has been provided before the cmd cog executes.
|
|
228
|
+
#
|
|
229
|
+
# #### See Also
|
|
230
|
+
# - `coerce`
|
|
231
|
+
#
|
|
22
232
|
#: () -> void
|
|
23
233
|
def validate!
|
|
24
234
|
raise Cog::Input::InvalidInputError, "'command' is required" unless command.present?
|
|
25
235
|
end
|
|
26
236
|
|
|
237
|
+
# Coerce the input from the return value of the input block
|
|
238
|
+
#
|
|
239
|
+
# If the input block returns a String, it will be used as the command value.
|
|
240
|
+
# If the input block returns an Array, the first element will be used as the command
|
|
241
|
+
# and the remaining elements will be used as arguments.
|
|
242
|
+
#
|
|
243
|
+
# #### See Also
|
|
244
|
+
# - `validate!`
|
|
245
|
+
#
|
|
27
246
|
#: (String | Array[untyped]) -> void
|
|
28
247
|
def coerce(input_return_value)
|
|
29
248
|
case input_return_value
|
|
@@ -37,13 +256,27 @@ module Roast
|
|
|
37
256
|
end
|
|
38
257
|
end
|
|
39
258
|
|
|
259
|
+
# Output from running the cmd cog
|
|
260
|
+
#
|
|
261
|
+
# Contains the standard output, standard error, and exit status from the executed command.
|
|
262
|
+
# Includes JSON and text parsing capabilities via `WithJson` and `WithText` modules.
|
|
40
263
|
class Output < Cog::Output
|
|
264
|
+
include Cog::Output::WithJson
|
|
265
|
+
include Cog::Output::WithNumber
|
|
266
|
+
include Cog::Output::WithText
|
|
267
|
+
|
|
268
|
+
# The standard output (STDOUT) from the command
|
|
269
|
+
#
|
|
41
270
|
#: String
|
|
42
271
|
attr_reader :out
|
|
43
272
|
|
|
273
|
+
# The standard error (STDERR) from the command
|
|
274
|
+
#
|
|
44
275
|
#: String
|
|
45
276
|
attr_reader :err
|
|
46
277
|
|
|
278
|
+
# The exit status of the command process
|
|
279
|
+
#
|
|
47
280
|
#: Process::Status
|
|
48
281
|
attr_reader :status
|
|
49
282
|
|
|
@@ -54,77 +287,34 @@ module Roast
|
|
|
54
287
|
@err = err #: String
|
|
55
288
|
@status = status #: Process::Status
|
|
56
289
|
end
|
|
57
|
-
end
|
|
58
290
|
|
|
59
|
-
|
|
60
|
-
#: () -> void
|
|
61
|
-
def print_all!
|
|
62
|
-
@values[:print_stdout] = true
|
|
63
|
-
@values[:print_stderr] = true
|
|
64
|
-
end
|
|
291
|
+
private
|
|
65
292
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@values[:print_stdout] = true
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
#: () -> void
|
|
72
|
-
def print_stderr!
|
|
73
|
-
@values[:print_stderr] = true
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
#: () -> bool
|
|
77
|
-
def print_stdout?
|
|
78
|
-
!!@values[:print_stdout]
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
#: () -> bool
|
|
82
|
-
def print_stderr?
|
|
83
|
-
!!@values[:print_stderr]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
#: () -> void
|
|
87
|
-
def display!
|
|
88
|
-
print_all!
|
|
293
|
+
def raw_text
|
|
294
|
+
out
|
|
89
295
|
end
|
|
90
296
|
end
|
|
91
297
|
|
|
92
298
|
#: (Input) -> Output
|
|
93
299
|
def execute(input)
|
|
94
300
|
config = @config #: as Config
|
|
95
|
-
result = T.unsafe(Roast::Helpers::CmdRunner).popen3(input.command, *input.args) do |stdin, stdout, stderr, wait_thread|
|
|
96
|
-
stdin.close
|
|
97
|
-
command_output = ""
|
|
98
|
-
command_error = ""
|
|
99
|
-
|
|
100
|
-
# Thread to read and accumulate stdout
|
|
101
|
-
stdout_thread = Thread.new do
|
|
102
|
-
stdout.each_line do |line|
|
|
103
|
-
command_output += line
|
|
104
|
-
$stdout.puts(line) if config.print_stdout?
|
|
105
|
-
end
|
|
106
|
-
rescue IOError => e
|
|
107
|
-
Roast::Helpers::Logger.debug("IOError while reading stdout: #{e.message}")
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Thread to read and accumulate stderr
|
|
111
|
-
stderr_thread = Thread.new do
|
|
112
|
-
stderr.each_line do |line|
|
|
113
|
-
command_error += line
|
|
114
|
-
$stderr.puts(line) if config.print_stderr?
|
|
115
|
-
end
|
|
116
|
-
rescue IOError => e
|
|
117
|
-
Roast::Helpers::Logger.debug("IOError while reading stderr: #{e.message}")
|
|
118
|
-
end
|
|
119
301
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
stderr_thread.join
|
|
302
|
+
stdout_handler = config.show_stdout? ? ->(line) { $stdout.print(line) } : nil
|
|
303
|
+
stderr_handler = config.show_stderr? ? ->(line) { $stderr.print(line) } : nil
|
|
123
304
|
|
|
124
|
-
|
|
125
|
-
|
|
305
|
+
stdout, stderr, status = CommandRunner #: as untyped
|
|
306
|
+
.execute(
|
|
307
|
+
[input.command] + input.args,
|
|
308
|
+
working_directory: config.valid_working_directory,
|
|
309
|
+
stdin_content: input.stdin,
|
|
310
|
+
stdout_handler: stdout_handler,
|
|
311
|
+
stderr_handler: stderr_handler,
|
|
312
|
+
)
|
|
313
|
+
if !status.success? && config.fail_on_error?
|
|
314
|
+
raise ControlFlow::FailCog, "Process exited with status code #{status.exitstatus}"
|
|
315
|
+
end
|
|
126
316
|
|
|
127
|
-
Output.new(
|
|
317
|
+
Output.new(stdout, stderr, status)
|
|
128
318
|
end
|
|
129
319
|
end
|
|
130
320
|
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Roast
|
|
5
|
+
module DSL
|
|
6
|
+
module Cogs
|
|
7
|
+
class Ruby < Cog
|
|
8
|
+
class Config < Cog::Config; end
|
|
9
|
+
|
|
10
|
+
# Input specification for the ruby cog
|
|
11
|
+
#
|
|
12
|
+
# The ruby cog accepts any Ruby value from the input block, which will be directly
|
|
13
|
+
# passed through to the output without modification.
|
|
14
|
+
class Input < Cog::Input
|
|
15
|
+
# The value to pass through to the output
|
|
16
|
+
#
|
|
17
|
+
# This value will be directly returned as the `value` attribute on the cog's output object.
|
|
18
|
+
#
|
|
19
|
+
#: untyped
|
|
20
|
+
attr_accessor :value
|
|
21
|
+
|
|
22
|
+
# Validate that the input has all required parameters
|
|
23
|
+
#
|
|
24
|
+
# This method ensures that a value has been provided, either directly via the `value`
|
|
25
|
+
# attribute or through the input block (via `coerce`).
|
|
26
|
+
#
|
|
27
|
+
# #### See Also
|
|
28
|
+
# - `coerce`
|
|
29
|
+
#
|
|
30
|
+
#: () -> void
|
|
31
|
+
def validate!
|
|
32
|
+
raise Cog::Input::InvalidInputError if value.nil? && !coerce_ran?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Coerce the input from the return value of the input block
|
|
36
|
+
#
|
|
37
|
+
# The return value from the input block will be used directly as the `value` attribute.
|
|
38
|
+
# This allows any Ruby object to be passed through the ruby cog.
|
|
39
|
+
#
|
|
40
|
+
# #### See Also
|
|
41
|
+
# - `validate!`
|
|
42
|
+
#
|
|
43
|
+
#: (untyped) -> void
|
|
44
|
+
def coerce(input_return_value)
|
|
45
|
+
super
|
|
46
|
+
@value = input_return_value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Output from running the ruby cog
|
|
51
|
+
#
|
|
52
|
+
# Contains the value that was provided to the input block, passed through unchanged.
|
|
53
|
+
# This allows Ruby values to be used directly in workflow steps.
|
|
54
|
+
#
|
|
55
|
+
# The output provides convenient dynamic method dispatch with the following priority:
|
|
56
|
+
# 1. If the value object responds to a method, it delegates to that method
|
|
57
|
+
# 2. If the value is a Hash, methods correspond to hash keys
|
|
58
|
+
# 3. Hash values that are Procs can be called directly as methods
|
|
59
|
+
#
|
|
60
|
+
# Additional conveniences:
|
|
61
|
+
# - Use `[]` for direct hash key access when the value is a Hash
|
|
62
|
+
# - Use `call()` to invoke the value if it's a Proc, or to call a Proc stored in a Hash
|
|
63
|
+
class Output < Cog::Output
|
|
64
|
+
# The value passed through from the input
|
|
65
|
+
#
|
|
66
|
+
#: untyped
|
|
67
|
+
attr_reader :value
|
|
68
|
+
|
|
69
|
+
#: (untyped) -> void
|
|
70
|
+
def initialize(value)
|
|
71
|
+
super()
|
|
72
|
+
@value = value
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Access a hash key directly when the value is a Hash
|
|
76
|
+
#
|
|
77
|
+
# Provides direct bracket notation access to hash keys without going through
|
|
78
|
+
# method dispatch. This is useful when you need explicit hash key access.
|
|
79
|
+
#
|
|
80
|
+
# #### See Also
|
|
81
|
+
# - `call`
|
|
82
|
+
# - `method_missing`
|
|
83
|
+
#
|
|
84
|
+
#: (Symbol) -> untyped
|
|
85
|
+
def [](key)
|
|
86
|
+
value[key]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Call the value as a Proc, or call a Proc stored in a Hash
|
|
90
|
+
#
|
|
91
|
+
# This method provides two calling patterns:
|
|
92
|
+
# - If the value is a Proc, calls it directly with the provided arguments
|
|
93
|
+
# - If the value is a Hash, expects the first argument to be a Symbol key, retrieves
|
|
94
|
+
# the Proc at that key, and calls it with the remaining arguments
|
|
95
|
+
#
|
|
96
|
+
# Raises `ArgumentError` if called on a Hash without a Symbol key.
|
|
97
|
+
# Raises `NoMethodError` if the Hash key doesn't contain a Proc.
|
|
98
|
+
#
|
|
99
|
+
# #### See Also
|
|
100
|
+
# - `[]`
|
|
101
|
+
# - `method_missing`
|
|
102
|
+
#
|
|
103
|
+
#: (*untyped, **untyped) ?{ (*untyped, **untyped) -> untyped } -> untyped
|
|
104
|
+
def call(*args, **kwargs, &blk)
|
|
105
|
+
return value.call(*args, **kwargs, &blk) if value.is_a?(Proc)
|
|
106
|
+
|
|
107
|
+
key = args.first
|
|
108
|
+
raise ArgumentError unless key.is_a?(Symbol)
|
|
109
|
+
|
|
110
|
+
proc = value[key]
|
|
111
|
+
raise NoMethodError, key unless proc.is_a?(Proc)
|
|
112
|
+
|
|
113
|
+
proc = proc #: as untyped
|
|
114
|
+
proc.call(*args, **kwargs, &blk)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Handle dynamic method calls with intelligent dispatch
|
|
118
|
+
#
|
|
119
|
+
# This method implements a multi-level dispatch strategy:
|
|
120
|
+
#
|
|
121
|
+
# 1. **Value delegation**: If the value object responds to the method, delegates directly to it.
|
|
122
|
+
# This allows calling methods like `lines`, `length`, `upcase` on String values, or any
|
|
123
|
+
# method on the underlying object.
|
|
124
|
+
#
|
|
125
|
+
# 2. **Hash key access**: If the value is a Hash and contains the method name as a key,
|
|
126
|
+
# returns the value at that key. If the value is a Proc, calls it with the provided arguments.
|
|
127
|
+
#
|
|
128
|
+
# 3. **Fallback**: If neither condition is met, calls `super` to trigger standard Ruby behavior.
|
|
129
|
+
#
|
|
130
|
+
# #### See Also
|
|
131
|
+
# - `respond_to_missing?`
|
|
132
|
+
# - `[]`
|
|
133
|
+
# - `call`
|
|
134
|
+
#
|
|
135
|
+
#: (Symbol, *untyped, **untyped) ?{ (*untyped, **untyped) -> untyped } -> untyped
|
|
136
|
+
def method_missing(name, *args, **kwargs, &blk)
|
|
137
|
+
return value.public_send(name, *args, **kwargs, &blk) if value.respond_to?(name, false)
|
|
138
|
+
return super unless value.is_a?(Hash) && value.key?(name)
|
|
139
|
+
|
|
140
|
+
if value[name].is_a?(Proc)
|
|
141
|
+
proc = value[name] #: as untyped
|
|
142
|
+
proc.call(*args, **kwargs, &blk)
|
|
143
|
+
else
|
|
144
|
+
value[name]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Check if a dynamic method should respond
|
|
149
|
+
#
|
|
150
|
+
# Returns `true` if any of the following conditions are met:
|
|
151
|
+
# 1. The value object responds to the method
|
|
152
|
+
# 2. The value is a Hash and contains the method name as a key
|
|
153
|
+
# 3. The parent class would respond to the method
|
|
154
|
+
#
|
|
155
|
+
# #### See Also
|
|
156
|
+
# - `method_missing`
|
|
157
|
+
#
|
|
158
|
+
#: (Symbol | String, ?bool) -> bool
|
|
159
|
+
def respond_to_missing?(name, include_private = false)
|
|
160
|
+
value.respond_to?(name, false) || value.is_a?(Hash) && value.key?(name) || super
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
#: (Input) -> Output
|
|
165
|
+
def execute(input)
|
|
166
|
+
Output.new(input.value)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|