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
data/lib/ukiryu/cli.rb
CHANGED
|
@@ -1,348 +1,174 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
|
|
5
|
-
require_relative
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative 'cli_commands/base_command'
|
|
5
|
+
require_relative 'cli_commands/run_file_command'
|
|
6
|
+
require_relative 'cli_commands/run_command'
|
|
7
|
+
require_relative 'cli_commands/list_command'
|
|
8
|
+
require_relative 'cli_commands/info_command'
|
|
9
|
+
require_relative 'cli_commands/commands_command'
|
|
10
|
+
require_relative 'cli_commands/opts_command'
|
|
11
|
+
require_relative 'cli_commands/describe_command'
|
|
12
|
+
require_relative 'cli_commands/which_command'
|
|
13
|
+
require_relative 'cli_commands/config_command'
|
|
14
|
+
require_relative 'cli_commands/version_command'
|
|
15
|
+
require_relative 'cli_commands/system_command'
|
|
16
|
+
require_relative 'cli_commands/validate_command'
|
|
17
|
+
require_relative 'cli_commands/extract_command'
|
|
18
|
+
require_relative 'thor_ext'
|
|
19
|
+
require_relative 'version'
|
|
6
20
|
|
|
7
21
|
module Ukiryu
|
|
8
22
|
# CLI for exploring and interacting with Ukiryu tool profiles
|
|
23
|
+
#
|
|
24
|
+
# Each command is implemented as a separate class in the CliCommands::Commands namespace.
|
|
25
|
+
# This file just delegates to those command classes, keeping each file under 200 lines.
|
|
9
26
|
class Cli < Thor
|
|
10
|
-
package_name
|
|
27
|
+
package_name 'ukiryu'
|
|
28
|
+
|
|
29
|
+
# Extend FriendlyCLI for better Thor behavior
|
|
30
|
+
extend FriendlyCLI
|
|
11
31
|
|
|
12
32
|
# Set default registry path if not configured
|
|
13
33
|
def self.exit_on_failure?
|
|
14
34
|
false
|
|
15
35
|
end
|
|
16
36
|
|
|
17
|
-
desc
|
|
18
|
-
method_option :registry, aliases: :r, desc:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
say "Available tools (#{tools.count}):", :cyan
|
|
29
|
-
tools.sort.each do |name|
|
|
30
|
-
begin
|
|
31
|
-
tool = Tool.get(name)
|
|
32
|
-
version_info = tool.version ? "v#{tool.version}" : "version unknown"
|
|
33
|
-
available = tool.available? ? "[✓]" : "[✗]"
|
|
34
|
-
say " #{available.ljust(4)} #{name.ljust(20)} #{version_info}", tool.available? ? :green : :red
|
|
35
|
-
rescue Ukiryu::Error => e
|
|
36
|
-
say " [?] #{name.ljust(20)} error: #{e.message}", :red
|
|
37
|
-
end
|
|
38
|
-
end
|
|
37
|
+
desc 'run-file REQUEST_FILE', 'Execute a Ukiryu Structured Execution Request from a YAML file'
|
|
38
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
39
|
+
method_option :output, aliases: :o, desc: 'Output file for response (default: stdout)', type: :string
|
|
40
|
+
method_option :format, aliases: :f, desc: 'Response format (yaml, json, table, raw)', type: :string, default: 'yaml'
|
|
41
|
+
method_option :dry_run, aliases: :d, desc: 'Show execution request without executing', type: :boolean,
|
|
42
|
+
default: false
|
|
43
|
+
method_option :shell, desc: 'Shell to use for command execution (bash, zsh, fish, sh, powershell, cmd)', type: :string
|
|
44
|
+
def run_file(request_file)
|
|
45
|
+
CliCommands::RunFileCommand.new(options).run(request_file)
|
|
39
46
|
end
|
|
40
47
|
|
|
41
|
-
desc
|
|
42
|
-
method_option :registry, aliases: :r, desc:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
say "Homepage: #{profile[:homepage] || 'N/A'}", :white
|
|
54
|
-
|
|
55
|
-
if profile[:aliases] && !profile[:aliases].empty?
|
|
56
|
-
say "Aliases: #{profile[:aliases].join(', ')}", :white
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Version detection
|
|
60
|
-
if profile[:version_detection]
|
|
61
|
-
vd = profile[:version_detection]
|
|
62
|
-
say "", :clear
|
|
63
|
-
say "Version Detection:", :yellow
|
|
64
|
-
say " Command: #{vd[:command]}", :white
|
|
65
|
-
say " Pattern: #{vd[:pattern]}", :white
|
|
66
|
-
if vd[:modern_threshold]
|
|
67
|
-
say " Modern Threshold: #{vd[:modern_threshold]}", :white
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Search paths
|
|
72
|
-
if profile[:search_paths]
|
|
73
|
-
say "", :clear
|
|
74
|
-
say "Search Paths:", :yellow
|
|
75
|
-
profile[:search_paths].each do |platform, paths|
|
|
76
|
-
next if paths.nil? || paths.empty?
|
|
77
|
-
say " #{platform}:", :white
|
|
78
|
-
Array(paths).each { |p| say " - #{p}", :white }
|
|
79
|
-
end
|
|
80
|
-
end
|
|
48
|
+
desc 'exec TOOL [COMMAND] [KEY=VALUE ...]', 'Execute a tool command inline'
|
|
49
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
50
|
+
method_option :format, aliases: :f, desc: 'Response format (yaml, json, table, raw)', type: :string, default: 'yaml'
|
|
51
|
+
method_option :output, aliases: :o, desc: 'Output file for response (default: stdout)', type: :string
|
|
52
|
+
method_option :dry_run, aliases: :d, desc: 'Show execution request without executing', type: :boolean,
|
|
53
|
+
default: false
|
|
54
|
+
method_option :shell, desc: 'Shell to use for command execution (bash, zsh, fish, sh, powershell, cmd)', type: :string
|
|
55
|
+
method_option :stdin, desc: 'Read input from stdin (pass to command)', type: :boolean, default: false
|
|
56
|
+
method_option :raw, desc: 'Output raw stdout/stderr (for pipe composition)', type: :boolean, default: false
|
|
57
|
+
def exec(tool_name, command_name = nil, *params)
|
|
58
|
+
CliCommands::RunCommand.new(options).run(tool_name, command_name, *params)
|
|
59
|
+
end
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
platforms = Array(prof[:platforms] || ['all']).join(', ')
|
|
88
|
-
shells = Array(prof[:shells] || ['all']).join(', ')
|
|
89
|
-
say " #{prof[:name] || 'unnamed'}:", :white
|
|
90
|
-
say " Platforms: #{platforms}", :white
|
|
91
|
-
say " Shells: #{shells}", :white
|
|
92
|
-
say " Version: #{prof[:version] || 'any'}", :white
|
|
93
|
-
end
|
|
94
|
-
end
|
|
61
|
+
desc 'list', 'List all available tools in the registry'
|
|
62
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
63
|
+
def list
|
|
64
|
+
CliCommands::ListCommand.new(options).run
|
|
65
|
+
end
|
|
95
66
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
67
|
+
desc 'info [TOOL]', 'Show detailed information about a tool (or general info if no tool specified)'
|
|
68
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
69
|
+
method_option :all, desc: 'Show all implementations for interfaces', type: :boolean, default: false
|
|
70
|
+
def info(tool_name = nil)
|
|
71
|
+
if tool_name.nil?
|
|
72
|
+
# Show general info when no tool specified
|
|
73
|
+
show_general_info
|
|
102
74
|
else
|
|
103
|
-
|
|
75
|
+
CliCommands::InfoCommand.new(options).run(tool_name)
|
|
104
76
|
end
|
|
105
77
|
end
|
|
106
78
|
|
|
107
|
-
desc
|
|
108
|
-
method_option :registry, aliases: :r, desc:
|
|
79
|
+
desc 'commands TOOL', 'List all commands available for a tool'
|
|
80
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
109
81
|
def commands(tool_name)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
tool = Tool.get(tool_name)
|
|
113
|
-
commands = tool.commands
|
|
114
|
-
|
|
115
|
-
unless commands
|
|
116
|
-
say "No commands defined for #{tool_name}", :red
|
|
117
|
-
return
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
say "Commands for #{tool_name}:", :cyan
|
|
121
|
-
commands.each do |cmd_name, cmd|
|
|
122
|
-
cmd_name = cmd_name || 'unnamed'
|
|
123
|
-
description = cmd[:description] || 'No description'
|
|
124
|
-
say " #{cmd_name.to_s.ljust(20)} #{description}", :white
|
|
125
|
-
|
|
126
|
-
# Show usage if available
|
|
127
|
-
if cmd[:usage]
|
|
128
|
-
say " Usage: #{cmd[:usage]}", :dim
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Show subcommand if exists
|
|
132
|
-
if cmd[:subcommand]
|
|
133
|
-
subcommand_info = cmd[:subcommand].nil? ? '(none)' : cmd[:subcommand]
|
|
134
|
-
say " Subcommand: #{subcommand_info}", :dim
|
|
135
|
-
end
|
|
136
|
-
end
|
|
82
|
+
CliCommands::CommandsCommand.new(options).run(tool_name)
|
|
137
83
|
end
|
|
138
84
|
|
|
139
|
-
desc
|
|
140
|
-
method_option :registry, aliases: :r, desc:
|
|
85
|
+
desc 'opts TOOL [COMMAND]', 'Show options for a tool or specific command'
|
|
86
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
141
87
|
def opts(tool_name, command_name = nil)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
tool = Tool.get(tool_name)
|
|
145
|
-
commands = tool.commands
|
|
146
|
-
|
|
147
|
-
unless commands
|
|
148
|
-
say "No commands defined for #{tool_name}", :red
|
|
149
|
-
return
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Find the command
|
|
153
|
-
cmds = if command_name
|
|
154
|
-
cmd = commands[command_name.to_sym] || commands[command_name]
|
|
155
|
-
cmd ? [cmd] : []
|
|
156
|
-
else
|
|
157
|
-
commands.values
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
cmds.each do |cmd|
|
|
161
|
-
cmd_title = command_name ? "#{tool_name} #{command_name}" : tool_name
|
|
162
|
-
say "", :clear
|
|
163
|
-
say "Options for #{cmd_title}:", :cyan
|
|
164
|
-
say "#{cmd[:description]}" if cmd[:description]
|
|
165
|
-
|
|
166
|
-
# Arguments
|
|
167
|
-
if cmd[:arguments] && !cmd[:arguments].empty?
|
|
168
|
-
say "", :clear
|
|
169
|
-
say "Arguments:", :yellow
|
|
170
|
-
cmd[:arguments].each do |arg|
|
|
171
|
-
name = arg[:name] || 'unnamed'
|
|
172
|
-
type = arg[:type] || 'unknown'
|
|
173
|
-
position = arg[:position] || 'default'
|
|
174
|
-
variadic = arg[:variadic] ? '(variadic)' : ''
|
|
175
|
-
|
|
176
|
-
say " #{name} (#{type}#{variadic})", :white
|
|
177
|
-
say " Position: #{position}", :dim if position != 'default'
|
|
178
|
-
say " Description: #{arg[:description]}", :dim if arg[:description]
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Options
|
|
183
|
-
if cmd[:options] && !cmd[:options].empty?
|
|
184
|
-
say "", :clear
|
|
185
|
-
say "Options:", :yellow
|
|
186
|
-
cmd[:options].each do |opt|
|
|
187
|
-
name = opt[:name] || 'unnamed'
|
|
188
|
-
cli = opt[:cli] || 'N/A'
|
|
189
|
-
type = opt[:type] || 'unknown'
|
|
190
|
-
description = opt[:description] || ''
|
|
191
|
-
|
|
192
|
-
say " --#{name.ljust(20)} #{cli}", :white
|
|
193
|
-
say " Type: #{type}", :dim
|
|
194
|
-
say " #{description}", :dim if description
|
|
195
|
-
if opt[:values]
|
|
196
|
-
say " Values: #{opt[:values].join(', ')}", :dim
|
|
197
|
-
end
|
|
198
|
-
if opt[:range]
|
|
199
|
-
say " Range: #{opt[:range].join('..')}", :dim
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
# Post-options (options between input and output)
|
|
205
|
-
if cmd[:post_options] && !cmd[:post_options].empty?
|
|
206
|
-
say "", :clear
|
|
207
|
-
say "Post-Options (between input and output):", :yellow
|
|
208
|
-
cmd[:post_options].each do |opt|
|
|
209
|
-
name = opt[:name] || 'unnamed'
|
|
210
|
-
cli = opt[:cli] || 'N/A'
|
|
211
|
-
type = opt[:type] || 'unknown'
|
|
212
|
-
description = opt[:description] || ''
|
|
213
|
-
|
|
214
|
-
say " --#{name.ljust(20)} #{cli}", :white
|
|
215
|
-
say " Type: #{type}", :dim
|
|
216
|
-
say " #{description}", :dim if description
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Flags
|
|
221
|
-
if cmd[:flags] && !cmd[:flags].empty?
|
|
222
|
-
say "", :clear
|
|
223
|
-
say "Flags:", :yellow
|
|
224
|
-
cmd[:flags].each do |flag|
|
|
225
|
-
name = flag[:name] || 'unnamed'
|
|
226
|
-
cli = flag[:cli] || 'N/A'
|
|
227
|
-
default = flag[:default]
|
|
228
|
-
default_str = default.nil? ? '' : " (default: #{default})"
|
|
229
|
-
|
|
230
|
-
say " #{cli.ljust(25)} #{name}#{default_str}", :white
|
|
231
|
-
say " #{flag[:description]}", :dim if flag[:description]
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
end
|
|
88
|
+
CliCommands::OptsCommand.new(options).run(tool_name, command_name)
|
|
235
89
|
end
|
|
236
90
|
|
|
237
|
-
desc
|
|
238
|
-
method_option :registry, aliases: :r, desc:
|
|
239
|
-
method_option :
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
setup_registry(options[:registry])
|
|
244
|
-
|
|
245
|
-
tool = Tool.get(tool_name)
|
|
246
|
-
|
|
247
|
-
# Build params from options
|
|
248
|
-
params = {}
|
|
249
|
-
params[:inputs] = options[:inputs] if options[:inputs]
|
|
250
|
-
params[:output] = options[:output] if options[:output]
|
|
251
|
-
|
|
252
|
-
# Add extra options as params
|
|
253
|
-
extra_opts.each do |key, value|
|
|
254
|
-
params[key.to_sym] = value
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
if options[:dry_run]
|
|
258
|
-
# Show what would be executed without actually running
|
|
259
|
-
say "DRY RUN - Would execute:", :yellow
|
|
260
|
-
say " Tool: #{tool_name}", :white
|
|
261
|
-
say " Command: #{command_name}", :white
|
|
262
|
-
say " Parameters:", :white
|
|
263
|
-
params.each do |k, v|
|
|
264
|
-
say " #{k}: #{v.inspect}", :dim
|
|
265
|
-
end
|
|
266
|
-
else
|
|
267
|
-
result = tool.execute(command_name.to_sym, params)
|
|
268
|
-
|
|
269
|
-
if result.success?
|
|
270
|
-
say "Command completed successfully", :green
|
|
271
|
-
say "Exit status: #{result.status}", :white
|
|
272
|
-
say "Duration: #{result.metadata.formatted_duration}", :white
|
|
273
|
-
|
|
274
|
-
if result.output.stdout && !result.output.stdout.empty?
|
|
275
|
-
say "", :clear
|
|
276
|
-
say "STDOUT:", :yellow
|
|
277
|
-
say result.output.stdout
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
if result.output.stderr && !result.output.stderr.empty?
|
|
281
|
-
say "", :clear
|
|
282
|
-
say "STDERR:", :yellow
|
|
283
|
-
say result.output.stderr
|
|
284
|
-
end
|
|
285
|
-
else
|
|
286
|
-
say "Command failed", :red
|
|
287
|
-
say "Exit status: #{result.status}", :white
|
|
288
|
-
say "Duration: #{result.metadata.formatted_duration}", :white
|
|
289
|
-
|
|
290
|
-
if result.output.stdout && !result.output.stdout.empty?
|
|
291
|
-
say "", :clear
|
|
292
|
-
say "STDOUT:", :yellow
|
|
293
|
-
say result.output.stdout
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
if result.output.stderr && !result.output.stderr.empty?
|
|
297
|
-
say "", :clear
|
|
298
|
-
say "STDERR:", :yellow
|
|
299
|
-
say result.output.stderr
|
|
300
|
-
end
|
|
91
|
+
desc 'describe TOOL [COMMAND]', 'Show comprehensive documentation for a tool or specific command'
|
|
92
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
93
|
+
method_option :format, aliases: :f, desc: 'Output format (text, yaml, json)', type: :string, default: 'text'
|
|
94
|
+
def describe(tool_name, command_name = nil)
|
|
95
|
+
CliCommands::DescribeCommand.new(options).run(tool_name, command_name)
|
|
96
|
+
end
|
|
301
97
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
98
|
+
desc 'system [SUBCOMMAND]', 'Show system information (shells, etc.)'
|
|
99
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
100
|
+
def system(subcommand = nil)
|
|
101
|
+
CliCommands::SystemCommand.new(options).run(subcommand)
|
|
305
102
|
end
|
|
306
103
|
|
|
307
|
-
desc
|
|
104
|
+
desc 'version', 'Show Ukiryu version'
|
|
308
105
|
def version
|
|
309
|
-
|
|
106
|
+
CliCommands::VersionCommand.new(options).run
|
|
310
107
|
end
|
|
311
108
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if registry_path && Dir.exist?(registry_path)
|
|
319
|
-
Registry.default_registry_path = registry_path
|
|
320
|
-
end
|
|
109
|
+
desc 'which IDENTIFIER', 'Show which tool implementation would be selected'
|
|
110
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
111
|
+
method_option :platform, desc: 'Platform to check (macos, linux, windows)', type: :string
|
|
112
|
+
method_option :shell, desc: 'Shell to check (bash, zsh, fish, sh, powershell, cmd)', type: :string
|
|
113
|
+
def which(identifier)
|
|
114
|
+
CliCommands::WhichCommand.new(options).run(identifier)
|
|
321
115
|
end
|
|
322
116
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
117
|
+
desc 'config [ACTION] [KEY] [VALUE]', 'Manage configuration (list, get, set, unset)'
|
|
118
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
119
|
+
def config(action = 'list', key = nil, value = nil)
|
|
120
|
+
CliCommands::ConfigCommand.new(options).run(action, key, value)
|
|
121
|
+
end
|
|
328
122
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return File.expand_path(registry_path)
|
|
335
|
-
end
|
|
123
|
+
desc 'validate [TOOL]', 'Validate tool profile(s) against schema'
|
|
124
|
+
method_option :registry, aliases: :r, desc: 'Path to tool registry', type: :string
|
|
125
|
+
def validate(tool_name = nil)
|
|
126
|
+
CliCommands::ValidateCommand.new(options).run(tool_name)
|
|
127
|
+
end
|
|
336
128
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
129
|
+
desc 'extract TOOL', 'Extract tool definition from an installed CLI tool'
|
|
130
|
+
method_option :output, aliases: :o, desc: 'Output file for extracted definition', type: :string
|
|
131
|
+
method_option :method, aliases: :m, desc: 'Extraction method (auto, native, help)', type: :string, default: 'auto'
|
|
132
|
+
method_option :verbose, aliases: :v, desc: 'Enable verbose output', type: :boolean, default: false
|
|
133
|
+
def extract(tool_name)
|
|
134
|
+
CliCommands::ExtractCommand.new(options).run(tool_name)
|
|
135
|
+
end
|
|
340
136
|
|
|
341
|
-
|
|
342
|
-
parent = File.expand_path('../../register', Dir.pwd)
|
|
343
|
-
return parent if Dir.exist?(parent)
|
|
137
|
+
private
|
|
344
138
|
|
|
345
|
-
|
|
139
|
+
# Show general information when no specific tool is requested
|
|
140
|
+
def show_general_info
|
|
141
|
+
require_relative 'shell'
|
|
142
|
+
require_relative 'runtime'
|
|
143
|
+
require_relative 'platform'
|
|
144
|
+
|
|
145
|
+
puts "Ukiryu v#{VERSION}"
|
|
146
|
+
puts ''
|
|
147
|
+
puts 'System Information:'
|
|
148
|
+
puts " Platform: #{Platform.detect}"
|
|
149
|
+
puts " Shell: #{Runtime.instance.shell}"
|
|
150
|
+
puts " Ruby: #{RUBY_VERSION}"
|
|
151
|
+
puts ''
|
|
152
|
+
puts 'Available Shells:'
|
|
153
|
+
Shell.available_shells.each do |shell|
|
|
154
|
+
puts " - #{shell}"
|
|
155
|
+
end
|
|
156
|
+
puts ''
|
|
157
|
+
puts 'Available Commands:'
|
|
158
|
+
puts ' list - List all available tools'
|
|
159
|
+
puts ' info TOOL - Show detailed information about a tool'
|
|
160
|
+
puts ' which IDENTIFIER - Show which tool implementation would be selected'
|
|
161
|
+
puts ' commands TOOL - List all commands available for a tool'
|
|
162
|
+
puts ' opts TOOL [COMMAND] - Show options for a tool or specific command'
|
|
163
|
+
puts ' describe TOOL [COMMAND] - Show comprehensive documentation'
|
|
164
|
+
puts ' config [ACTION] - Manage configuration (list, get, set, unset)'
|
|
165
|
+
puts ' system [shells] - Show system information (shells, etc.)'
|
|
166
|
+
puts ' exec ... - Execute a tool command inline'
|
|
167
|
+
puts ' run-file ... - Execute from a YAML file'
|
|
168
|
+
puts ' version - Show Ukiryu version'
|
|
169
|
+
puts ''
|
|
170
|
+
puts 'For more information on a specific command:'
|
|
171
|
+
puts ' ukiryu help COMMAND'
|
|
346
172
|
end
|
|
347
173
|
end
|
|
348
174
|
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../config'
|
|
4
|
+
|
|
5
|
+
module Ukiryu
|
|
6
|
+
module CliCommands
|
|
7
|
+
# Base class for CLI commands
|
|
8
|
+
#
|
|
9
|
+
# Provides shared functionality for all CLI commands including
|
|
10
|
+
# registry setup, output formatting, and error handling.
|
|
11
|
+
#
|
|
12
|
+
# @abstract Subclasses must implement the `run` method
|
|
13
|
+
class BaseCommand
|
|
14
|
+
attr_reader :options, :config
|
|
15
|
+
|
|
16
|
+
# Initialize a new command
|
|
17
|
+
#
|
|
18
|
+
# @param options [Hash] command options from Thor
|
|
19
|
+
def initialize(options = {})
|
|
20
|
+
@options = options
|
|
21
|
+
@config = Ukiryu::Config.instance
|
|
22
|
+
apply_cli_options_to_config
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Execute the command
|
|
26
|
+
#
|
|
27
|
+
# Subclasses must implement this method
|
|
28
|
+
#
|
|
29
|
+
# @raise [NotImplementedError] if not implemented in subclass
|
|
30
|
+
def run
|
|
31
|
+
raise NotImplementedError, "#{self.class} must implement #run"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Setup the registry path
|
|
35
|
+
#
|
|
36
|
+
# @param custom_path [String, nil] custom registry path
|
|
37
|
+
def setup_registry(custom_path = nil)
|
|
38
|
+
registry_path = custom_path || config.registry || default_registry_path
|
|
39
|
+
return unless registry_path && Dir.exist?(registry_path)
|
|
40
|
+
|
|
41
|
+
Registry.default_registry_path = registry_path
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Apply CLI options to the Config instance
|
|
45
|
+
# CLI options have the highest priority in the configuration chain
|
|
46
|
+
def apply_cli_options_to_config
|
|
47
|
+
# Thor option defaults that should not override ENV or programmatic config
|
|
48
|
+
# Only apply CLI option if it's not the default value
|
|
49
|
+
cli_mappings = {
|
|
50
|
+
format: 'yaml', # default format in Thor
|
|
51
|
+
output: nil,
|
|
52
|
+
registry: nil,
|
|
53
|
+
timeout: nil,
|
|
54
|
+
shell: nil
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
cli_mappings.each do |cli_key, default_value|
|
|
58
|
+
next unless options.key?(cli_key)
|
|
59
|
+
|
|
60
|
+
# Only set CLI option if it's not the default value
|
|
61
|
+
# This allows ENV and programmatic config to take precedence when user doesn't specify
|
|
62
|
+
option_value = options[cli_key]
|
|
63
|
+
should_set = if default_value.nil?
|
|
64
|
+
# No default, always set
|
|
65
|
+
!option_value.nil? && !option_value.empty?
|
|
66
|
+
else
|
|
67
|
+
# Only set if different from default (user explicitly specified)
|
|
68
|
+
option_value != default_value
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
config.set_cli_option(cli_key, option_value) if should_set
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Handle boolean options from Thor
|
|
75
|
+
config.set_cli_option(:debug, options[:verbose]) if options.key?(:verbose)
|
|
76
|
+
|
|
77
|
+
# Handle dry_run option
|
|
78
|
+
return unless options.key?(:dry_run)
|
|
79
|
+
|
|
80
|
+
config.set_cli_option(:dry_run, options[:dry_run])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get the default registry path
|
|
84
|
+
#
|
|
85
|
+
# @return [String, nil] the default registry path
|
|
86
|
+
def default_registry_path
|
|
87
|
+
# Try multiple approaches to find the registry
|
|
88
|
+
# Note: ENV and Config.registry are already checked by setup_registry
|
|
89
|
+
|
|
90
|
+
# 1. Try relative to gem location
|
|
91
|
+
gem_root = File.dirname(File.dirname(File.dirname(__FILE__)))
|
|
92
|
+
registry_path = File.join(gem_root, '..', 'register')
|
|
93
|
+
return File.expand_path(registry_path) if Dir.exist?(registry_path)
|
|
94
|
+
|
|
95
|
+
# 2. Try from current directory (development setup)
|
|
96
|
+
current = File.expand_path('../register', Dir.pwd)
|
|
97
|
+
return current if Dir.exist?(current)
|
|
98
|
+
|
|
99
|
+
# 3. Try from parent directory
|
|
100
|
+
parent = File.expand_path('../../register', Dir.pwd)
|
|
101
|
+
return parent if Dir.exist?(parent)
|
|
102
|
+
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Convert string keys to symbols
|
|
107
|
+
#
|
|
108
|
+
# @param hash [Hash] hash with string keys
|
|
109
|
+
# @return [Hash] hash with symbol keys
|
|
110
|
+
def stringify_keys(hash)
|
|
111
|
+
hash.transform_keys(&:to_sym)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
protected
|
|
115
|
+
|
|
116
|
+
# Say output (Thor compatibility)
|
|
117
|
+
#
|
|
118
|
+
# Respects the use_color configuration option and NO_COLOR environment variable.
|
|
119
|
+
#
|
|
120
|
+
# @param message [String] the message
|
|
121
|
+
# @param color [Symbol] the color
|
|
122
|
+
def say(message, color = nil)
|
|
123
|
+
# Check if colors should be disabled
|
|
124
|
+
# Config handles both use_color setting and NO_COLOR environment variable
|
|
125
|
+
colors_disabled = config.colors_disabled?
|
|
126
|
+
|
|
127
|
+
if color && !colors_disabled
|
|
128
|
+
# Use ANSI color codes
|
|
129
|
+
colors = {
|
|
130
|
+
black: 30,
|
|
131
|
+
red: 31,
|
|
132
|
+
green: 32,
|
|
133
|
+
yellow: 33,
|
|
134
|
+
blue: 34,
|
|
135
|
+
magenta: 35,
|
|
136
|
+
cyan: 36,
|
|
137
|
+
white: 37,
|
|
138
|
+
dim: 2 # bright/black for dim
|
|
139
|
+
}
|
|
140
|
+
code = colors[color] || 37
|
|
141
|
+
puts "\e[#{code}m#{message}\e[0m"
|
|
142
|
+
else
|
|
143
|
+
puts message
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Exit with error message
|
|
148
|
+
#
|
|
149
|
+
# @param message [String] the error message
|
|
150
|
+
def error!(message)
|
|
151
|
+
raise Thor::Error, message
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|