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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +99 -0
  4. data/.github/workflows/rake.yml +19 -0
  5. data/.github/workflows/release.yml +27 -0
  6. data/.gitignore +18 -4
  7. data/.rubocop.yml +1 -0
  8. data/.rubocop_todo.yml +213 -0
  9. data/Gemfile +12 -8
  10. data/README.adoc +613 -0
  11. data/Rakefile +2 -2
  12. data/docs/assets/logo.svg +1 -0
  13. data/exe/ukiryu +11 -0
  14. data/lib/ukiryu/action/base.rb +77 -0
  15. data/lib/ukiryu/cache.rb +199 -0
  16. data/lib/ukiryu/cli.rb +133 -307
  17. data/lib/ukiryu/cli_commands/base_command.rb +155 -0
  18. data/lib/ukiryu/cli_commands/commands_command.rb +120 -0
  19. data/lib/ukiryu/cli_commands/commands_command.rb.fixed +40 -0
  20. data/lib/ukiryu/cli_commands/config_command.rb +249 -0
  21. data/lib/ukiryu/cli_commands/describe_command.rb +326 -0
  22. data/lib/ukiryu/cli_commands/describe_command.rb.fixed +254 -0
  23. data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +180 -0
  24. data/lib/ukiryu/cli_commands/extract_command.rb +84 -0
  25. data/lib/ukiryu/cli_commands/info_command.rb +156 -0
  26. data/lib/ukiryu/cli_commands/list_command.rb +70 -0
  27. data/lib/ukiryu/cli_commands/opts_command.rb +106 -0
  28. data/lib/ukiryu/cli_commands/opts_command.rb.fixed +105 -0
  29. data/lib/ukiryu/cli_commands/response_formatter.rb +240 -0
  30. data/lib/ukiryu/cli_commands/run_command.rb +375 -0
  31. data/lib/ukiryu/cli_commands/run_file_command.rb +215 -0
  32. data/lib/ukiryu/cli_commands/system_command.rb +90 -0
  33. data/lib/ukiryu/cli_commands/validate_command.rb +87 -0
  34. data/lib/ukiryu/cli_commands/version_command.rb +16 -0
  35. data/lib/ukiryu/cli_commands/which_command.rb +166 -0
  36. data/lib/ukiryu/command_builder.rb +205 -0
  37. data/lib/ukiryu/config/env_provider.rb +64 -0
  38. data/lib/ukiryu/config/env_schema.rb +63 -0
  39. data/lib/ukiryu/config/override_resolver.rb +68 -0
  40. data/lib/ukiryu/config/type_converter.rb +59 -0
  41. data/lib/ukiryu/config.rb +249 -0
  42. data/lib/ukiryu/errors.rb +3 -0
  43. data/lib/ukiryu/executable_locator.rb +114 -0
  44. data/lib/ukiryu/execution/command_info.rb +64 -0
  45. data/lib/ukiryu/execution/metadata.rb +97 -0
  46. data/lib/ukiryu/execution/output.rb +144 -0
  47. data/lib/ukiryu/execution/result.rb +194 -0
  48. data/lib/ukiryu/execution.rb +15 -0
  49. data/lib/ukiryu/execution_context.rb +251 -0
  50. data/lib/ukiryu/executor.rb +76 -493
  51. data/lib/ukiryu/extractors/base_extractor.rb +63 -0
  52. data/lib/ukiryu/extractors/extractor.rb +150 -0
  53. data/lib/ukiryu/extractors/help_parser.rb +188 -0
  54. data/lib/ukiryu/extractors/native_extractor.rb +47 -0
  55. data/lib/ukiryu/io.rb +196 -0
  56. data/lib/ukiryu/logger.rb +544 -0
  57. data/lib/ukiryu/models/argument.rb +28 -0
  58. data/lib/ukiryu/models/argument_definition.rb +119 -0
  59. data/lib/ukiryu/models/arguments.rb +113 -0
  60. data/lib/ukiryu/models/command_definition.rb +176 -0
  61. data/lib/ukiryu/models/command_info.rb +37 -0
  62. data/lib/ukiryu/models/components.rb +107 -0
  63. data/lib/ukiryu/models/env_var_definition.rb +30 -0
  64. data/lib/ukiryu/models/error_response.rb +41 -0
  65. data/lib/ukiryu/models/execution_metadata.rb +31 -0
  66. data/lib/ukiryu/models/execution_report.rb +236 -0
  67. data/lib/ukiryu/models/exit_codes.rb +74 -0
  68. data/lib/ukiryu/models/flag_definition.rb +67 -0
  69. data/lib/ukiryu/models/option_definition.rb +102 -0
  70. data/lib/ukiryu/models/output_info.rb +25 -0
  71. data/lib/ukiryu/models/platform_profile.rb +153 -0
  72. data/lib/ukiryu/models/routing.rb +211 -0
  73. data/lib/ukiryu/models/search_paths.rb +39 -0
  74. data/lib/ukiryu/models/success_response.rb +85 -0
  75. data/lib/ukiryu/models/tool_definition.rb +145 -0
  76. data/lib/ukiryu/models/tool_metadata.rb +82 -0
  77. data/lib/ukiryu/models/validation_result.rb +80 -0
  78. data/lib/ukiryu/models/version_compatibility.rb +152 -0
  79. data/lib/ukiryu/models/version_detection.rb +39 -0
  80. data/lib/ukiryu/models.rb +23 -0
  81. data/lib/ukiryu/options/base.rb +95 -0
  82. data/lib/ukiryu/options_builder/formatter.rb +87 -0
  83. data/lib/ukiryu/options_builder/validator.rb +43 -0
  84. data/lib/ukiryu/options_builder.rb +311 -0
  85. data/lib/ukiryu/platform.rb +6 -6
  86. data/lib/ukiryu/registry.rb +143 -183
  87. data/lib/ukiryu/response/base.rb +217 -0
  88. data/lib/ukiryu/runtime.rb +179 -0
  89. data/lib/ukiryu/schema_validator.rb +8 -10
  90. data/lib/ukiryu/shell/bash.rb +3 -3
  91. data/lib/ukiryu/shell/cmd.rb +4 -4
  92. data/lib/ukiryu/shell/fish.rb +1 -1
  93. data/lib/ukiryu/shell/powershell.rb +3 -3
  94. data/lib/ukiryu/shell/sh.rb +1 -1
  95. data/lib/ukiryu/shell/zsh.rb +1 -1
  96. data/lib/ukiryu/shell.rb +146 -39
  97. data/lib/ukiryu/thor_ext.rb +208 -0
  98. data/lib/ukiryu/tool.rb +649 -258
  99. data/lib/ukiryu/tool_index.rb +224 -0
  100. data/lib/ukiryu/tools/base.rb +381 -0
  101. data/lib/ukiryu/tools/class_generator.rb +132 -0
  102. data/lib/ukiryu/tools/executable_finder.rb +29 -0
  103. data/lib/ukiryu/tools/generator.rb +154 -0
  104. data/lib/ukiryu/tools.rb +109 -0
  105. data/lib/ukiryu/type.rb +28 -43
  106. data/lib/ukiryu/validation/constraints.rb +281 -0
  107. data/lib/ukiryu/validation/validator.rb +188 -0
  108. data/lib/ukiryu/validation.rb +21 -0
  109. data/lib/ukiryu/version.rb +1 -1
  110. data/lib/ukiryu/version_detector.rb +51 -0
  111. data/lib/ukiryu.rb +31 -15
  112. data/ukiryu-proposal.md +2952 -0
  113. data/ukiryu.gemspec +18 -14
  114. metadata +137 -5
  115. data/.github/workflows/test.yml +0 -143
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'argument'
5
+
6
+ module Ukiryu
7
+ module Models
8
+ # Structured command arguments
9
+ #
10
+ # Contains the structured arguments passed to a command execution.
11
+ # Each argument has a name, value, and type.
12
+ class Arguments < Lutaml::Model::Serializable
13
+ attribute :options, Argument, collection: true, initialize_empty: true
14
+ attribute :flags, :string, collection: true, default: []
15
+ attribute :positional, Argument, collection: true, initialize_empty: true
16
+
17
+ yaml do
18
+ map_element 'options', to: :options
19
+ map_element 'flags', to: :flags
20
+ map_element 'positional', to: :positional
21
+ end
22
+
23
+ json do
24
+ map 'options', to: :options
25
+ map 'flags', to: :flags
26
+ map 'positional', to: :positional
27
+ end
28
+
29
+ # Create Arguments from a hash of parameters
30
+ #
31
+ # @param params [Hash] the parameters hash
32
+ # @param command_definition [CommandDefinition] the command definition for context
33
+ # @return [Arguments] the arguments model
34
+ def self.from_params(params, command_definition = nil)
35
+ options_arr = []
36
+ flags_arr = []
37
+ positional_arr = []
38
+
39
+ params.each do |key, value|
40
+ # Determine argument type based on command definition or heuristics
41
+ arg_type = determine_argument_type(key, value, command_definition)
42
+
43
+ case arg_type
44
+ when :flag
45
+ # Boolean flags
46
+ flags_arr << key.to_s if value
47
+ when :option
48
+ # Named options with values
49
+ options_arr << Argument.new(name: key.to_s, value: stringify_value(value), type: 'option')
50
+ when :positional
51
+ # Positional arguments
52
+ positional_arr << Argument.new(name: key.to_s, value: stringify_value(value), type: 'positional')
53
+ end
54
+ end
55
+
56
+ # Set arrays directly to ensure proper serialization
57
+ args = new
58
+ args.options = options_arr unless options_arr.empty?
59
+ args.flags = flags_arr unless flags_arr.empty?
60
+ args.positional = positional_arr unless positional_arr.empty?
61
+
62
+ args
63
+ end
64
+
65
+ # Determine the type of an argument
66
+ #
67
+ # @param key [Symbol] the argument key
68
+ # @param value [Object] the argument value
69
+ # @param command_definition [CommandDefinition] the command definition
70
+ # @return [Symbol] :option, :flag, or :positional
71
+ def self.determine_argument_type(key, value, command_definition)
72
+ # Check command definition if available
73
+ if command_definition
74
+ flag_def = command_definition.flags&.find { |f| f.name.to_sym == key }
75
+ return :flag if flag_def
76
+
77
+ option_def = command_definition.options&.find { |o| o.name.to_sym == key }
78
+ return :option if option_def
79
+
80
+ arg_def = command_definition.arguments&.find { |a| a.name.to_sym == key }
81
+ return :positional if arg_def
82
+ end
83
+
84
+ # Fall back to heuristics
85
+ if value.is_a?(TrueClass) || value.is_a?(FalseClass)
86
+ :flag
87
+ elsif value.is_a?(Array)
88
+ # Arrays are typically options (like inputs)
89
+ :option
90
+ else
91
+ :option
92
+ end
93
+ end
94
+
95
+ # Convert a value to string for serialization
96
+ #
97
+ # @param value [Object] the value
98
+ # @return [String] string representation
99
+ def self.stringify_value(value)
100
+ case value
101
+ when Array
102
+ value.map(&:to_s).join(', ')
103
+ when Symbol, Integer, Float, TrueClass, FalseClass
104
+ value.to_s
105
+ when NilClass
106
+ ''
107
+ else
108
+ value.to_s
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'option_definition'
5
+ require_relative 'flag_definition'
6
+ require_relative 'argument_definition'
7
+ require_relative 'env_var_definition'
8
+ require_relative 'exit_codes'
9
+
10
+ module Ukiryu
11
+ module Models
12
+ # Command definition for a tool
13
+ #
14
+ # @example
15
+ # cmd = CommandDefinition.new(
16
+ # name: 'convert',
17
+ # description: 'Convert image format',
18
+ # options: [OptionDefinition.new(...)],
19
+ # flags: [FlagDefinition.new(...)]
20
+ # )
21
+ class CommandDefinition < Lutaml::Model::Serializable
22
+ attribute :name, :string
23
+ attribute :description, :string
24
+ attribute :usage, :string
25
+ attribute :subcommand, :string
26
+ attribute :execution_mode, :string
27
+ attribute :belongs_to, :string # Parent command this action belongs to
28
+ attribute :cli_flag, :string # CLI flag for this action (e.g., '-d' for delete)
29
+ attribute :aliases, :string, collection: true, default: []
30
+
31
+ # Collections of model objects (lutaml-model handles serialization automatically)
32
+ attribute :options, OptionDefinition, collection: true
33
+ attribute :flags, FlagDefinition, collection: true
34
+ attribute :arguments, ArgumentDefinition, collection: true
35
+ attribute :post_options, OptionDefinition, collection: true
36
+ attribute :env_vars, EnvVarDefinition, collection: true
37
+ attribute :exit_codes, ExitCodes # Exit code definitions for this command
38
+
39
+ yaml do
40
+ map_element 'name', to: :name
41
+ map_element 'description', to: :description
42
+ map_element 'usage', to: :usage
43
+ map_element 'subcommand', to: :subcommand
44
+ map_element 'options', to: :options
45
+ map_element 'flags', to: :flags
46
+ map_element 'arguments', to: :arguments
47
+ map_element 'post_options', to: :post_options
48
+ map_element 'env_vars', to: :env_vars
49
+ map_element 'exit_codes', to: :exit_codes
50
+ map_element 'execution_mode', to: :execution_mode
51
+ map_element 'belongs_to', to: :belongs_to
52
+ map_element 'cli_flag', to: :cli_flag
53
+ map_element 'aliases', to: :aliases
54
+ end
55
+
56
+ # Check if this command/action belongs to a parent command
57
+ #
58
+ # @return [Boolean] true if belongs_to is set
59
+ def belongs_to_command?
60
+ !belongs_to.nil? && !belongs_to.empty?
61
+ end
62
+
63
+ # Check if this action is expressed as a flag
64
+ #
65
+ # @return [Boolean] true if cli_flag is set
66
+ def flag_action?
67
+ !cli_flag.nil? && !cli_flag.empty?
68
+ end
69
+
70
+ # Get an option by name using indexed O(1) lookup
71
+ #
72
+ # @param name [String, Symbol] the option name
73
+ # @return [OptionDefinition, nil] the option
74
+ def option(name)
75
+ return nil unless options
76
+
77
+ build_options_index unless @options_index_built
78
+ @options_index[name.to_s]
79
+ end
80
+
81
+ # Get a flag by name using indexed O(1) lookup
82
+ #
83
+ # @param name [String, Symbol] the flag name
84
+ # @return [FlagDefinition, nil] the flag
85
+ def flag(name)
86
+ return nil unless flags
87
+
88
+ build_flags_index unless @flags_index_built
89
+ @flags_index[name.to_s]
90
+ end
91
+
92
+ # Get an argument by name using indexed O(1) lookup
93
+ #
94
+ # @param name [String, Symbol] the argument name
95
+ # @return [ArgumentDefinition, nil] the argument
96
+ def argument(name)
97
+ return nil unless arguments
98
+
99
+ build_arguments_index unless @arguments_index_built
100
+ @arguments_index[name.to_s]
101
+ end
102
+
103
+ # Get the last argument
104
+ #
105
+ # @return [ArgumentDefinition, nil] the last argument
106
+ def last_argument
107
+ return nil unless arguments
108
+
109
+ arguments.find { |a| a.is_a?(ArgumentDefinition) && a.last? }
110
+ end
111
+
112
+ # Get regular arguments (not last)
113
+ #
114
+ # @return [Array<ArgumentDefinition>] regular arguments sorted by position
115
+ def regular_arguments
116
+ return [] unless arguments
117
+
118
+ args = arguments.select { |a| a.is_a?(ArgumentDefinition) }
119
+ args.reject(&:last?).sort_by(&:numeric_position)
120
+ end
121
+
122
+ # Check if command has a subcommand
123
+ #
124
+ # @return [Boolean] true if has subcommand
125
+ def has_subcommand?
126
+ !subcommand.nil? && !subcommand.empty?
127
+ end
128
+
129
+ # Clear all indexes
130
+ #
131
+ # Call this if collections are modified after initial loading
132
+ #
133
+ # @api private
134
+ def clear_indexes!
135
+ @options_index = nil
136
+ @options_index_built = false
137
+ @flags_index = nil
138
+ @flags_index_built = false
139
+ @arguments_index = nil
140
+ @arguments_index_built = false
141
+ end
142
+
143
+ private
144
+
145
+ # Build the options index hash for O(1) lookup
146
+ #
147
+ # @api private
148
+ def build_options_index
149
+ return unless options
150
+
151
+ @options_index = options.to_h { |o| [o.name, o] }
152
+ @options_index_built = true
153
+ end
154
+
155
+ # Build the flags index hash for O(1) lookup
156
+ #
157
+ # @api private
158
+ def build_flags_index
159
+ return unless flags
160
+
161
+ @flags_index = flags.to_h { |f| [f.name, f] }
162
+ @flags_index_built = true
163
+ end
164
+
165
+ # Build the arguments index hash for O(1) lookup
166
+ #
167
+ # @api private
168
+ def build_arguments_index
169
+ return unless arguments
170
+
171
+ @arguments_index = arguments.to_h { |a| [a.name, a] }
172
+ @arguments_index_built = true
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'arguments'
5
+
6
+ module Ukiryu
7
+ module Models
8
+ # Command execution information
9
+ #
10
+ # Contains details about the executed command including
11
+ # the executable path, structured arguments, full command string, and shell used.
12
+ class CommandInfo < Lutaml::Model::Serializable
13
+ attribute :executable, :string
14
+ attribute :executable_name, :string
15
+ attribute :tool_name, :string # Name of the tool being executed
16
+ attribute :arguments, Arguments
17
+ attribute :full_command, :string
18
+ attribute :shell, :string
19
+
20
+ yaml do
21
+ map_element 'executable', to: :executable
22
+ map_element 'executable_name', to: :executable_name
23
+ map_element 'tool_name', to: :tool_name
24
+ map_element 'arguments', to: :arguments
25
+ map_element 'full_command', to: :full_command
26
+ map_element 'shell', to: :shell
27
+ end
28
+
29
+ json do
30
+ map 'executable', to: :executable
31
+ map 'arguments', to: :arguments
32
+ map 'full_command', to: :full_command
33
+ map 'shell', to: :shell
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'option_definition'
5
+ require_relative 'flag_definition'
6
+ require_relative 'argument_definition'
7
+ require_relative 'exit_codes'
8
+
9
+ module Ukiryu
10
+ module Models
11
+ # Components registry for reusable definitions
12
+ #
13
+ # Enables sharing common option/argument/flag/exit_codes definitions
14
+ # across commands through `$ref` references.
15
+ #
16
+ # @example
17
+ # components = Components.new(
18
+ # options: { 'verbose' => OptionDefinition.new(...) },
19
+ # flags: { 'help' => FlagDefinition.new(...) }
20
+ # )
21
+ class Components < Lutaml::Model::Serializable
22
+ attribute :options, :hash, default: {}
23
+ attribute :flags, :hash, default: {}
24
+ attribute :arguments, :hash, default: {}
25
+ attribute :exit_codes, ExitCodes
26
+
27
+ yaml do
28
+ map_element 'options', to: :options
29
+ map_element 'flags', to: :flags
30
+ map_element 'arguments', to: :arguments
31
+ map_element 'exit_codes', to: :exit_codes
32
+ end
33
+
34
+ # Get an option by name
35
+ #
36
+ # @param name [String, Symbol] the option name
37
+ # @return [OptionDefinition, nil] the option definition
38
+ def option(name)
39
+ @options&.dig(name.to_s)
40
+ end
41
+
42
+ # Get a flag by name
43
+ #
44
+ # @param name [String, Symbol] the flag name
45
+ # @return [FlagDefinition, nil] the flag definition
46
+ def flag(name)
47
+ @flags&.dig(name.to_s)
48
+ end
49
+
50
+ # Get an argument by name
51
+ #
52
+ # @param name [String, Symbol] the argument name
53
+ # @return [ArgumentDefinition, nil] the argument definition
54
+ def argument(name)
55
+ @arguments&.dig(name.to_s)
56
+ end
57
+
58
+ # Check if a reference can be resolved
59
+ #
60
+ # @param ref [String] the reference path (e.g., '#/components/options/verbose')
61
+ # @return [Boolean] true if the reference can be resolved
62
+ def can_resolve?(ref)
63
+ return false unless ref =~ %r{^#/components/(options|flags|arguments|exit_codes)/(.+)$}
64
+
65
+ type = Regexp.last_match(1)
66
+ name = Regexp.last_match(2)
67
+
68
+ case type
69
+ when 'options'
70
+ @options&.key?(name)
71
+ when 'flags'
72
+ @flags&.key?(name)
73
+ when 'arguments'
74
+ @arguments&.key?(name)
75
+ when 'exit_codes'
76
+ !@exit_codes.nil?
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ # Resolve a reference path to a component
83
+ #
84
+ # @param ref [String] the reference path (e.g., '#/components/options/verbose')
85
+ # @return [Object, nil] the component or nil if not found
86
+ def resolve(ref)
87
+ return nil unless ref =~ %r{^#/components/(options|flags|arguments|exit_codes)/(.+)$}
88
+
89
+ type = Regexp.last_match(1)
90
+ name = Regexp.last_match(2)
91
+
92
+ case type
93
+ when 'options'
94
+ option(name)
95
+ when 'flags'
96
+ flag(name)
97
+ when 'arguments'
98
+ argument(name)
99
+ when 'exit_codes'
100
+ @exit_codes
101
+ else
102
+ nil
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Ukiryu
6
+ module Models
7
+ # Environment variable definition for a command
8
+ #
9
+ # @example
10
+ # env_var = EnvVarDefinition.new(
11
+ # name: 'DISPLAY',
12
+ # env_var: 'DISPLAY',
13
+ # value: '',
14
+ # platforms: [:linux, :macos]
15
+ # )
16
+ class EnvVarDefinition < Lutaml::Model::Serializable
17
+ attribute :name, :string
18
+ attribute :env_var, :string
19
+ attribute :value, :string
20
+ attribute :platforms, :string, collection: true, default: []
21
+
22
+ yaml do
23
+ map_element 'name', to: :name
24
+ map_element 'env_var', to: :env_var
25
+ map_element 'value', to: :value
26
+ map_element 'platforms', to: :platforms
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Ukiryu
6
+ module Models
7
+ # Error CLI execution response
8
+ #
9
+ # Contains error information when a command execution fails.
10
+ class ErrorResponse < Lutaml::Model::Serializable
11
+ attribute :status, :string, default: 'error'
12
+ attribute :exit_code, :integer, default: 1
13
+ attribute :error, :string
14
+
15
+ yaml do
16
+ map_element 'status', to: :status
17
+ map_element 'exit_code', to: :exit_code
18
+ map_element 'error', to: :error
19
+ end
20
+
21
+ json do
22
+ map 'status', to: :status
23
+ map 'exit_code', to: :exit_code
24
+ map 'error', to: :error
25
+ end
26
+
27
+ # Create an ErrorResponse from an error message
28
+ #
29
+ # @param message [String] the error message
30
+ # @param exit_code [Integer] the exit code (default: 1)
31
+ # @return [ErrorResponse] the response model
32
+ def self.from_message(message, exit_code: 1)
33
+ new(
34
+ status: 'error',
35
+ exit_code: exit_code,
36
+ error: message
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Ukiryu
6
+ module Models
7
+ # Execution metadata
8
+ #
9
+ # Contains timing and duration information about command execution.
10
+ class ExecutionMetadata < Lutaml::Model::Serializable
11
+ attribute :started_at, :string
12
+ attribute :finished_at, :string
13
+ attribute :duration_seconds, :float
14
+ attribute :formatted_duration, :string
15
+
16
+ yaml do
17
+ map_element 'started_at', to: :started_at
18
+ map_element 'finished_at', to: :finished_at
19
+ map_element 'duration_seconds', to: :duration_seconds
20
+ map_element 'formatted_duration', to: :formatted_duration
21
+ end
22
+
23
+ json do
24
+ map 'started_at', to: :started_at
25
+ map 'finished_at', to: :finished_at
26
+ map 'duration_seconds', to: :duration_seconds
27
+ map 'formatted_duration', to: :formatted_duration
28
+ end
29
+ end
30
+ end
31
+ end