ukiryu 0.1.0 → 0.1.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/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +99 -0
- data/.github/workflows/rake.yml +19 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +18 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +213 -0
- data/Gemfile +12 -8
- data/README.adoc +613 -0
- data/Rakefile +2 -2
- data/docs/assets/logo.svg +1 -0
- data/exe/ukiryu +11 -0
- data/lib/ukiryu/action/base.rb +77 -0
- data/lib/ukiryu/cache.rb +199 -0
- data/lib/ukiryu/cli.rb +133 -307
- data/lib/ukiryu/cli_commands/base_command.rb +155 -0
- data/lib/ukiryu/cli_commands/commands_command.rb +120 -0
- data/lib/ukiryu/cli_commands/commands_command.rb.fixed +40 -0
- data/lib/ukiryu/cli_commands/config_command.rb +249 -0
- data/lib/ukiryu/cli_commands/describe_command.rb +326 -0
- data/lib/ukiryu/cli_commands/describe_command.rb.fixed +254 -0
- data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +180 -0
- data/lib/ukiryu/cli_commands/extract_command.rb +84 -0
- data/lib/ukiryu/cli_commands/info_command.rb +156 -0
- data/lib/ukiryu/cli_commands/list_command.rb +70 -0
- data/lib/ukiryu/cli_commands/opts_command.rb +106 -0
- data/lib/ukiryu/cli_commands/opts_command.rb.fixed +105 -0
- data/lib/ukiryu/cli_commands/response_formatter.rb +240 -0
- data/lib/ukiryu/cli_commands/run_command.rb +375 -0
- data/lib/ukiryu/cli_commands/run_file_command.rb +215 -0
- data/lib/ukiryu/cli_commands/system_command.rb +90 -0
- data/lib/ukiryu/cli_commands/validate_command.rb +87 -0
- data/lib/ukiryu/cli_commands/version_command.rb +16 -0
- data/lib/ukiryu/cli_commands/which_command.rb +166 -0
- data/lib/ukiryu/command_builder.rb +205 -0
- data/lib/ukiryu/config/env_provider.rb +64 -0
- data/lib/ukiryu/config/env_schema.rb +63 -0
- data/lib/ukiryu/config/override_resolver.rb +68 -0
- data/lib/ukiryu/config/type_converter.rb +59 -0
- data/lib/ukiryu/config.rb +249 -0
- data/lib/ukiryu/errors.rb +3 -0
- data/lib/ukiryu/executable_locator.rb +114 -0
- data/lib/ukiryu/execution/command_info.rb +64 -0
- data/lib/ukiryu/execution/metadata.rb +97 -0
- data/lib/ukiryu/execution/output.rb +144 -0
- data/lib/ukiryu/execution/result.rb +194 -0
- data/lib/ukiryu/execution.rb +15 -0
- data/lib/ukiryu/execution_context.rb +251 -0
- data/lib/ukiryu/executor.rb +76 -493
- data/lib/ukiryu/extractors/base_extractor.rb +63 -0
- data/lib/ukiryu/extractors/extractor.rb +150 -0
- data/lib/ukiryu/extractors/help_parser.rb +188 -0
- data/lib/ukiryu/extractors/native_extractor.rb +47 -0
- data/lib/ukiryu/io.rb +196 -0
- data/lib/ukiryu/logger.rb +544 -0
- data/lib/ukiryu/models/argument.rb +28 -0
- data/lib/ukiryu/models/argument_definition.rb +119 -0
- data/lib/ukiryu/models/arguments.rb +113 -0
- data/lib/ukiryu/models/command_definition.rb +176 -0
- data/lib/ukiryu/models/command_info.rb +37 -0
- data/lib/ukiryu/models/components.rb +107 -0
- data/lib/ukiryu/models/env_var_definition.rb +30 -0
- data/lib/ukiryu/models/error_response.rb +41 -0
- data/lib/ukiryu/models/execution_metadata.rb +31 -0
- data/lib/ukiryu/models/execution_report.rb +236 -0
- data/lib/ukiryu/models/exit_codes.rb +74 -0
- data/lib/ukiryu/models/flag_definition.rb +67 -0
- data/lib/ukiryu/models/option_definition.rb +102 -0
- data/lib/ukiryu/models/output_info.rb +25 -0
- data/lib/ukiryu/models/platform_profile.rb +153 -0
- data/lib/ukiryu/models/routing.rb +211 -0
- data/lib/ukiryu/models/search_paths.rb +39 -0
- data/lib/ukiryu/models/success_response.rb +85 -0
- data/lib/ukiryu/models/tool_definition.rb +145 -0
- data/lib/ukiryu/models/tool_metadata.rb +82 -0
- data/lib/ukiryu/models/validation_result.rb +80 -0
- data/lib/ukiryu/models/version_compatibility.rb +152 -0
- data/lib/ukiryu/models/version_detection.rb +39 -0
- data/lib/ukiryu/models.rb +23 -0
- data/lib/ukiryu/options/base.rb +95 -0
- data/lib/ukiryu/options_builder/formatter.rb +87 -0
- data/lib/ukiryu/options_builder/validator.rb +43 -0
- data/lib/ukiryu/options_builder.rb +311 -0
- data/lib/ukiryu/platform.rb +6 -6
- data/lib/ukiryu/registry.rb +143 -183
- data/lib/ukiryu/response/base.rb +217 -0
- data/lib/ukiryu/runtime.rb +179 -0
- data/lib/ukiryu/schema_validator.rb +8 -10
- data/lib/ukiryu/shell/bash.rb +3 -3
- data/lib/ukiryu/shell/cmd.rb +4 -4
- data/lib/ukiryu/shell/fish.rb +1 -1
- data/lib/ukiryu/shell/powershell.rb +3 -3
- data/lib/ukiryu/shell/sh.rb +1 -1
- data/lib/ukiryu/shell/zsh.rb +1 -1
- data/lib/ukiryu/shell.rb +146 -39
- data/lib/ukiryu/thor_ext.rb +208 -0
- data/lib/ukiryu/tool.rb +649 -258
- data/lib/ukiryu/tool_index.rb +224 -0
- data/lib/ukiryu/tools/base.rb +381 -0
- data/lib/ukiryu/tools/class_generator.rb +132 -0
- data/lib/ukiryu/tools/executable_finder.rb +29 -0
- data/lib/ukiryu/tools/generator.rb +154 -0
- data/lib/ukiryu/tools.rb +109 -0
- data/lib/ukiryu/type.rb +28 -43
- data/lib/ukiryu/validation/constraints.rb +281 -0
- data/lib/ukiryu/validation/validator.rb +188 -0
- data/lib/ukiryu/validation.rb +21 -0
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu/version_detector.rb +51 -0
- data/lib/ukiryu.rb +31 -15
- data/ukiryu-proposal.md +2952 -0
- data/ukiryu.gemspec +18 -14
- metadata +137 -5
- data/.github/workflows/test.yml +0 -143
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../tool'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Show options for a tool or specific command
|
|
9
|
+
class OptsCommand < BaseCommand
|
|
10
|
+
# Execute the opts command
|
|
11
|
+
#
|
|
12
|
+
# @param tool_name [String] the tool name
|
|
13
|
+
# @param command_name [String, nil] optional command name
|
|
14
|
+
def run(tool_name, command_name = nil)
|
|
15
|
+
setup_registry
|
|
16
|
+
|
|
17
|
+
tool = Tool.get(tool_name)
|
|
18
|
+
tool_commands = tool.commands
|
|
19
|
+
|
|
20
|
+
error! "No commands defined for #{tool_name}" unless tool_commands
|
|
21
|
+
|
|
22
|
+
# Find the command
|
|
23
|
+
cmds = if command_name
|
|
24
|
+
cmd = tool_commands[command_name.to_sym] || tool_commands[command_name]
|
|
25
|
+
cmd ? [cmd] : []
|
|
26
|
+
else
|
|
27
|
+
tool_commands.values
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
cmds.each do |cmd|
|
|
31
|
+
cmd_title = command_name ? "#{tool_name} #{command_name}" : tool_name
|
|
32
|
+
say '', :clear
|
|
33
|
+
say "Options for #{cmd_title}:", :cyan
|
|
34
|
+
say cmd.description.to_s if cmd.description
|
|
35
|
+
|
|
36
|
+
# Arguments
|
|
37
|
+
if cmd.arguments && !cmd.arguments.empty?
|
|
38
|
+
say '', :clear
|
|
39
|
+
say 'Arguments:', :yellow
|
|
40
|
+
cmd.arguments.each do |arg|
|
|
41
|
+
name = arg.name || 'unnamed'
|
|
42
|
+
type = arg.type || 'unknown'
|
|
43
|
+
position = arg.position || 'default'
|
|
44
|
+
variadic = arg.variadic ? '(variadic)' : ''
|
|
45
|
+
|
|
46
|
+
say " #{name} (#{type}#{variadic})", :white
|
|
47
|
+
say " Position: #{position}", :dim if position != 'default'
|
|
48
|
+
say " Description: #{arg.description}", :dim if arg.description
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Options
|
|
53
|
+
if cmd.options && !cmd.options.empty?
|
|
54
|
+
say '', :clear
|
|
55
|
+
say 'Options:', :yellow
|
|
56
|
+
cmd.options.each do |opt|
|
|
57
|
+
name = opt.name || 'unnamed'
|
|
58
|
+
cli = opt.cli || 'N/A'
|
|
59
|
+
type = opt.type || 'unknown'
|
|
60
|
+
description = opt.description || ''
|
|
61
|
+
|
|
62
|
+
say " --#{name.ljust(20)} #{cli}", :white
|
|
63
|
+
say " Type: #{type}", :dim
|
|
64
|
+
say " #{description}", :dim if description
|
|
65
|
+
say " Values: #{opt.values.join(', ')}", :dim if opt.values
|
|
66
|
+
say " Range: #{opt.range.join('..')}", :dim if opt.range
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Post-options (options between input and output)
|
|
71
|
+
if cmd.post_options && !cmd.post_options.empty?
|
|
72
|
+
say '', :clear
|
|
73
|
+
say 'Post-Options (between input and output):', :yellow
|
|
74
|
+
cmd.post_options.each do |opt|
|
|
75
|
+
name = opt.name || 'unnamed'
|
|
76
|
+
cli = opt.cli || 'N/A'
|
|
77
|
+
type = opt.type || 'unknown'
|
|
78
|
+
description = opt.description || ''
|
|
79
|
+
|
|
80
|
+
say " --#{name.ljust(20)} #{cli}", :white
|
|
81
|
+
say " Type: #{type}", :dim
|
|
82
|
+
say " #{description}", :dim if description
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Flags
|
|
87
|
+
next unless cmd.flags && !cmd.flags.empty?
|
|
88
|
+
|
|
89
|
+
say '', :clear
|
|
90
|
+
say 'Flags:', :yellow
|
|
91
|
+
cmd.flags.each do |flag|
|
|
92
|
+
name = flag.name || 'unnamed'
|
|
93
|
+
cli = flag.cli || 'N/A'
|
|
94
|
+
default = flag.default
|
|
95
|
+
default_str = default.nil? ? '' : " (default: #{default})"
|
|
96
|
+
|
|
97
|
+
say " #{cli.ljust(25)} #{name}#{default_str}", :white
|
|
98
|
+
say " #{flag.description}", :dim if flag.description
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ukiryu
|
|
4
|
+
module CliCommands
|
|
5
|
+
# Response formatting module for CLI commands
|
|
6
|
+
#
|
|
7
|
+
# Provides reusable formatting methods for execution responses
|
|
8
|
+
# in multiple formats (YAML, JSON, table, raw).
|
|
9
|
+
module ResponseFormatter
|
|
10
|
+
# Supported output formats
|
|
11
|
+
OUTPUT_FORMATS = %i[yaml json table raw].freeze
|
|
12
|
+
|
|
13
|
+
# Output response in specified format
|
|
14
|
+
#
|
|
15
|
+
# @param response [Models::SuccessResponse, Models::ErrorResponse] the response to format
|
|
16
|
+
# @param format [Symbol] the output format (:yaml, :json, :table, or :raw)
|
|
17
|
+
# @param output_file [String, nil] optional file path to write output
|
|
18
|
+
# @param config [Object] the CLI config object
|
|
19
|
+
# @return [void]
|
|
20
|
+
def output_response(response, format, output_file, config)
|
|
21
|
+
if format == :raw
|
|
22
|
+
output_raw_response(response, output_file)
|
|
23
|
+
else
|
|
24
|
+
output_string = case format
|
|
25
|
+
when :yaml
|
|
26
|
+
format_yaml_response(response, config)
|
|
27
|
+
when :json
|
|
28
|
+
format_json_response(response)
|
|
29
|
+
when :table, :human
|
|
30
|
+
format_table_response(response, config)
|
|
31
|
+
else
|
|
32
|
+
response.to_yaml
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if output_file
|
|
36
|
+
File.write(output_file, output_string)
|
|
37
|
+
say "Response written to: #{output_file}", :green
|
|
38
|
+
else
|
|
39
|
+
say output_string
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Show dry run output
|
|
45
|
+
#
|
|
46
|
+
# @param request [Hash] the execution request
|
|
47
|
+
def say_dry_run(request)
|
|
48
|
+
say 'DRY RUN - Ukiryu Structured Execution Request:', :yellow
|
|
49
|
+
say '', :clear
|
|
50
|
+
say "Tool: #{request['tool']}", :cyan
|
|
51
|
+
say "Command: #{request['command']}", :cyan
|
|
52
|
+
say 'Arguments:', :cyan
|
|
53
|
+
request['arguments'].each do |key, value|
|
|
54
|
+
say " #{key}: #{value.inspect}", :white
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Format response as colored YAML
|
|
61
|
+
#
|
|
62
|
+
# @param response [Models::SuccessResponse, Models::ErrorResponse] the response
|
|
63
|
+
# @param config [Object] the CLI config object
|
|
64
|
+
# @return [String] formatted YAML
|
|
65
|
+
def format_yaml_response(response, config)
|
|
66
|
+
yaml_content = response.to_yaml
|
|
67
|
+
|
|
68
|
+
# Determine if we should use colors:
|
|
69
|
+
# - Config.use_color can be true, false, or nil (auto-detect)
|
|
70
|
+
# - Auto-detect: only use colors if TTY and colors are not disabled via config/NO_COLOR
|
|
71
|
+
use_colors = if config.use_color.nil?
|
|
72
|
+
$stdout.tty? && !config.colors_disabled?
|
|
73
|
+
else
|
|
74
|
+
config.use_color
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
return yaml_content unless use_colors
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
require 'paint'
|
|
81
|
+
paint = Paint.method(:[])
|
|
82
|
+
# Add color coding to YAML output (no explicit newlines, let say handle it)
|
|
83
|
+
yaml_content.each_line.map do |line|
|
|
84
|
+
case line
|
|
85
|
+
when /^status:/
|
|
86
|
+
paint[line, :cyan, :bright]
|
|
87
|
+
when /^exit_code:/
|
|
88
|
+
paint[line, :yellow]
|
|
89
|
+
when /^ executable:/
|
|
90
|
+
paint[line, :green]
|
|
91
|
+
when /^ full_command:/
|
|
92
|
+
paint[line, :blue, :bright]
|
|
93
|
+
when /^stdout:/
|
|
94
|
+
line # No color for stdout - let terminal decide
|
|
95
|
+
when /^stderr:/
|
|
96
|
+
paint[line, :red]
|
|
97
|
+
when /^ started_at:|^ finished_at:/
|
|
98
|
+
line # No color for timestamps - let terminal decide
|
|
99
|
+
when /^ duration_seconds:|^ formatted_duration:/
|
|
100
|
+
paint[line, :magenta]
|
|
101
|
+
else
|
|
102
|
+
line
|
|
103
|
+
end
|
|
104
|
+
end.join
|
|
105
|
+
rescue LoadError
|
|
106
|
+
yaml_content
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Format response as JSON
|
|
111
|
+
#
|
|
112
|
+
# @param response [Models::SuccessResponse, Models::ErrorResponse] the response
|
|
113
|
+
# @return [String] formatted JSON
|
|
114
|
+
def format_json_response(response)
|
|
115
|
+
response.to_json(pretty: true)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Output response in raw format (for pipe composition)
|
|
119
|
+
#
|
|
120
|
+
# In raw mode:
|
|
121
|
+
# - stdout from the command is written directly to stdout
|
|
122
|
+
# - stderr from the command is written directly to stderr
|
|
123
|
+
# - No wrapping in YAML/JSON structures
|
|
124
|
+
# - Enables clean pipe composition: echo "test" | ukiryu exec jq --raw filter="." | other_tool
|
|
125
|
+
#
|
|
126
|
+
# @param response [Models::SuccessResponse, Models::ErrorResponse] the response
|
|
127
|
+
# @param output_file [String, nil] optional file path to write output
|
|
128
|
+
# @return [void]
|
|
129
|
+
def output_raw_response(response, output_file)
|
|
130
|
+
if response.is_a?(Models::SuccessResponse)
|
|
131
|
+
# Write stdout to stdout (without say() to avoid extra newlines)
|
|
132
|
+
$stdout.write(response.output.stdout)
|
|
133
|
+
$stdout.flush if response.output.stdout.empty? || response.output.stdout.end_with?("\n")
|
|
134
|
+
|
|
135
|
+
# Write stderr to stderr
|
|
136
|
+
unless response.output.stderr.empty?
|
|
137
|
+
$stderr.write(response.output.stderr)
|
|
138
|
+
$stderr.flush
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Write to file if specified
|
|
142
|
+
File.write(output_file, response.output.stdout) if output_file
|
|
143
|
+
else
|
|
144
|
+
# Error response: write error message to stderr
|
|
145
|
+
$stderr.write("#{response.error}\n")
|
|
146
|
+
$stderr.flush
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Format response as human-readable table
|
|
151
|
+
#
|
|
152
|
+
# @param response [Models::SuccessResponse, Models::ErrorResponse] the response
|
|
153
|
+
# @param config [Object] the CLI config object
|
|
154
|
+
# @return [String] formatted table
|
|
155
|
+
def format_table_response(response, config)
|
|
156
|
+
if response.is_a?(Models::SuccessResponse)
|
|
157
|
+
format_success_table(response, config)
|
|
158
|
+
else
|
|
159
|
+
format_error_table(response, config)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Format success response as table
|
|
164
|
+
#
|
|
165
|
+
# @param response [Models::SuccessResponse] the success response
|
|
166
|
+
# @param config [Object] the CLI config object
|
|
167
|
+
# @return [String] formatted success table
|
|
168
|
+
def format_success_table(response, config)
|
|
169
|
+
return format_plain_success_table(response, config) unless defined?(Paint)
|
|
170
|
+
|
|
171
|
+
begin
|
|
172
|
+
require 'paint'
|
|
173
|
+
paint = Paint.method(:[])
|
|
174
|
+
success_icon = paint['✓', :green]
|
|
175
|
+
|
|
176
|
+
output = []
|
|
177
|
+
output << "#{success_icon} #{paint['Command completed successfully', :green, :bright]}\n"
|
|
178
|
+
output << "#{paint['Exit code:', :white]} #{response.exit_code}\n"
|
|
179
|
+
output << "#{paint['Duration:', :white]} #{response.metadata.formatted_duration}\n"
|
|
180
|
+
output << "\n#{paint['Request:', :cyan, :bright]} #{format_request_summary(response.request)}\n"
|
|
181
|
+
output << "\n#{paint['Command:', :cyan, :bright]} #{response.command.full_command}\n"
|
|
182
|
+
|
|
183
|
+
unless response.output.stdout.empty?
|
|
184
|
+
first_line = response.output.stdout.split("\n").first
|
|
185
|
+
output << "\n#{paint['Output:', :white]} #{first_line}\n"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
output << "\n#{paint['Errors:', :red]} #{response.output.stderr}\n" unless response.output.stderr.empty?
|
|
189
|
+
|
|
190
|
+
output.join("\n")
|
|
191
|
+
rescue LoadError
|
|
192
|
+
format_plain_success_table(response, config)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Format request summary for table display
|
|
197
|
+
#
|
|
198
|
+
# @param request [Models::Request] the request object
|
|
199
|
+
# @return [String] formatted request summary
|
|
200
|
+
def format_request_summary(request)
|
|
201
|
+
parts = []
|
|
202
|
+
parts << request.options.map { |opt| "#{opt.name}=#{opt.value}" }.join(' ') unless request.options.empty?
|
|
203
|
+
parts << request.flags.map { |flag| "--#{flag}" }.join(' ') unless request.flags.empty?
|
|
204
|
+
parts << request.positional.map(&:value).join(' ') unless request.positional.empty?
|
|
205
|
+
parts.join(' ')
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Format success response without Paint
|
|
209
|
+
#
|
|
210
|
+
# @param response [Models::SuccessResponse] the success response
|
|
211
|
+
# @param _config [Object] the CLI config object (unused)
|
|
212
|
+
# @return [String] plain text success table
|
|
213
|
+
def format_plain_success_table(response, _config)
|
|
214
|
+
"✓ Command completed successfully\n" \
|
|
215
|
+
"Exit code: #{response.exit_code}\n" \
|
|
216
|
+
"Duration: #{response.metadata.formatted_duration}\n" \
|
|
217
|
+
"\nRequest: #{format_request_summary(response.request)}\n" \
|
|
218
|
+
"\nCommand: #{response.command.full_command}\n"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Format error response as table
|
|
222
|
+
#
|
|
223
|
+
# @param response [Models::ErrorResponse] the error response
|
|
224
|
+
# @param _config [Object] the CLI config object (unused)
|
|
225
|
+
# @return [String] formatted error table
|
|
226
|
+
def format_error_table(response, _config)
|
|
227
|
+
return "✗ Command failed: #{response.error}\n" unless defined?(Paint)
|
|
228
|
+
|
|
229
|
+
begin
|
|
230
|
+
require 'paint'
|
|
231
|
+
paint = Paint.method(:[])
|
|
232
|
+
error_icon = paint['✗', :red]
|
|
233
|
+
"#{error_icon} #{paint['Command failed:', :red, :bright]} #{response.error}\n"
|
|
234
|
+
rescue LoadError
|
|
235
|
+
"✗ Command failed: #{response.error}\n"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|