aia 1.1.1 → 2.0.0.0.pre.beta2
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/.envrc +9 -1
- data/.loki +11 -0
- data/.reek.yml +160 -0
- data/.rubocop.yml +116 -0
- data/.rubocop_strict.yml +15 -0
- data/.version +1 -1
- data/CHANGELOG.md +269 -55
- data/IMPLEMENTATION_PLAN.md +506 -0
- data/README.md +267 -239
- data/Rakefile +5 -5
- data/_typos.toml +10 -0
- data/aia.gemspec +92 -0
- data/architecture_review.md +314 -0
- data/bin/aia +16 -0
- data/config/aia.yml +13 -0
- data/docs/AGENTS.md +40 -0
- data/docs/advanced-prompting.md +67 -3
- data/docs/cli-reference.md +312 -56
- data/docs/configuration.md +130 -19
- data/docs/contributing.md +56 -2
- data/docs/directives-reference.md +593 -78
- data/docs/faq.md +85 -3
- data/docs/guides/available-models.md +1 -1
- data/docs/guides/basic-usage.md +6 -6
- data/docs/guides/chat.md +40 -16
- data/docs/guides/crew.md +239 -0
- data/docs/guides/executable-prompts.md +1 -1
- data/docs/guides/index.md +1 -0
- data/docs/guides/models.md +15 -0
- data/docs/index.md +29 -2
- data/docs/installation.md +44 -17
- data/docs/mcp-integration.md +40 -0
- data/docs/prompt_management.md +85 -86
- data/docs/security.md +47 -0
- data/docs/special_projects_guide.md +386 -0
- data/docs/tools-and-mcp-examples.md +23 -0
- data/docs/workflows-and-pipelines.md +84 -7
- data/examples/.gitignore +1 -0
- data/examples/00_setup_aia.sh +27 -44
- data/examples/11_multi_model.sh +4 -14
- data/examples/12_token_usage.sh +3 -12
- data/examples/18_tools.sh +10 -2
- data/examples/22_chat_mode.sh +0 -10
- data/examples/23_verify.sh +139 -0
- data/examples/24_decompose.sh +139 -0
- data/examples/25_spawn.sh +139 -0
- data/examples/26_debate.sh +97 -0
- data/examples/27_mention_routing.sh +157 -0
- data/examples/28_model_switching.sh +106 -0
- data/examples/29_agent_harness.sh +177 -0
- data/examples/README.md +65 -0
- data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
- data/examples/aia_config.yml +1 -1
- data/examples/aia_config_orchestrator.yml +45 -0
- data/examples/common.sh +18 -6
- data/examples/context/tech_stack.md +2 -2
- data/examples/prompts_dir/roles/orchestrator.md +21 -0
- data/examples/requirements/sinatra_taskflow_app.md +139 -0
- data/examples/rules/01_classify_ruby.rb +16 -0
- data/examples/rules/02_prefer_claude_for_code.rb +19 -0
- data/examples/rules/03_gate_prompt_length.rb +19 -0
- data/examples/rules/04_tool_selection.rb +41 -0
- data/examples/rules/README.md +30 -0
- data/examples/run_all.sh +48 -15
- data/examples/tools/word_count_tool.rb +1 -1
- data/lib/AGENTS.md +57 -0
- data/lib/aia/chat_loop.rb +263 -167
- data/lib/aia/config/cli_parser.rb +217 -145
- data/lib/aia/config/defaults.yml +62 -33
- data/lib/aia/config/mcp_parser.rb +52 -51
- data/lib/aia/config/model_spec.rb +34 -2
- data/lib/aia/config/validator.rb +171 -216
- data/lib/aia/config.rb +111 -145
- data/lib/aia/content_extractor.rb +155 -0
- data/lib/aia/cost_calculator.rb +39 -0
- data/lib/aia/crew.rb +164 -0
- data/lib/aia/debate_handler.rb +174 -0
- data/lib/aia/delegate_handler.rb +116 -0
- data/lib/aia/directive.rb +43 -26
- data/lib/aia/directive_processor.rb +16 -7
- data/lib/aia/directives/configuration_directives.rb +214 -60
- data/lib/aia/directives/context_directives.rb +67 -52
- data/lib/aia/directives/execution_directives.rb +141 -4
- data/lib/aia/directives/model_directives.rb +163 -141
- data/lib/aia/directives/trakflow_directives.rb +62 -0
- data/lib/aia/directives/utility_directives.rb +227 -30
- data/lib/aia/directives/web_and_file_directives.rb +126 -77
- data/lib/aia/errors.rb +15 -0
- data/lib/aia/fact_asserter.rb +27 -0
- data/lib/aia/fzf.rb +9 -31
- data/lib/aia/handler_context.rb +17 -0
- data/lib/aia/handler_protocol.rb +19 -0
- data/lib/aia/history_transfer.rb +55 -0
- data/lib/aia/input_collector.rb +3 -3
- data/lib/aia/layered_orchestrator.rb +471 -0
- data/lib/aia/logger.rb +45 -25
- data/lib/aia/mcp_config_normalizer.rb +35 -0
- data/lib/aia/mcp_connection_manager.rb +315 -0
- data/lib/aia/mcp_discovery.rb +44 -0
- data/lib/aia/mcp_grouper.rb +33 -0
- data/lib/aia/mcp_server_config.rb +30 -0
- data/lib/aia/mcp_utility.rb +60 -0
- data/lib/aia/mention_router.rb +217 -0
- data/lib/aia/model_alias_registry.rb +97 -0
- data/lib/aia/model_switch_handler.rb +100 -0
- data/lib/aia/network_builder.rb +160 -0
- data/lib/aia/network_memory_manager.rb +55 -0
- data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
- data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
- data/lib/aia/pipeline_orchestrator.rb +272 -0
- data/lib/aia/plugin_loader.rb +170 -0
- data/lib/aia/plugin_monitor.rb +211 -0
- data/lib/aia/prompt_decomposer.rb +159 -0
- data/lib/aia/prompt_handler.rb +56 -82
- data/lib/aia/robot_builder.rb +51 -0
- data/lib/aia/robot_factory.rb +338 -0
- data/lib/aia/robot_namer.rb +110 -0
- data/lib/aia/session.rb +87 -17
- data/lib/aia/session_tracker.rb +207 -0
- data/lib/aia/similarity_scorer.rb +41 -0
- data/lib/aia/skill_utils.rb +105 -1
- data/lib/aia/spawn_handler.rb +129 -0
- data/lib/aia/spawn_spec_parser.rb +65 -0
- data/lib/aia/special_mode_handler.rb +322 -0
- data/lib/aia/speech.rb +67 -0
- data/lib/aia/startup_coordinator.rb +151 -0
- data/lib/aia/streaming_runner.rb +172 -0
- data/lib/aia/system_prompt_assembler.rb +92 -0
- data/lib/aia/task_coordinator.rb +207 -0
- data/lib/aia/task_decomposer.rb +57 -0
- data/lib/aia/task_executor.rb +51 -0
- data/lib/aia/tfidf_math.rb +27 -0
- data/lib/aia/timing.rb +15 -0
- data/lib/aia/tool_filter/tfidf.rb +116 -0
- data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
- data/lib/aia/tool_filter.rb +83 -0
- data/lib/aia/tool_filter_registry.rb +30 -0
- data/lib/aia/tool_filter_strategy.rb +146 -0
- data/lib/aia/tool_introspection.rb +17 -0
- data/lib/aia/tool_loader.rb +216 -0
- data/lib/aia/tool_utility.rb +30 -0
- data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
- data/lib/aia/tools/recruit_robot_tool.rb +60 -0
- data/lib/aia/tools/reskill_robot_tool.rb +44 -0
- data/lib/aia/tools/task_board_tool.rb +115 -0
- data/lib/aia/trakflow_bridge.rb +175 -0
- data/lib/aia/turn_state.rb +95 -0
- data/lib/aia/ui_presenter.rb +182 -206
- data/lib/aia/utility.rb +136 -87
- data/lib/aia/{history_manager.rb → variable_input_collector.rb} +9 -9
- data/lib/aia/verification_network.rb +57 -0
- data/lib/aia.rb +124 -63
- data/mkdocs.yml +1 -0
- metadata +187 -58
- data/justfile +0 -215
- data/lib/aia/adapter/chat_execution.rb +0 -242
- data/lib/aia/adapter/error_handler.rb +0 -68
- data/lib/aia/adapter/gem_activator.rb +0 -57
- data/lib/aia/adapter/mcp_connector.rb +0 -274
- data/lib/aia/adapter/modality_handlers.rb +0 -167
- data/lib/aia/adapter/model_registry.rb +0 -81
- data/lib/aia/adapter/multi_model_chat.rb +0 -218
- data/lib/aia/adapter/provider_configurator.rb +0 -59
- data/lib/aia/adapter/tool_filter.rb +0 -85
- data/lib/aia/adapter/tool_loader.rb +0 -90
- data/lib/aia/chat_processor_service.rb +0 -178
- data/lib/aia/prompt_pipeline.rb +0 -183
- data/lib/aia/ruby_llm_adapter.rb +0 -95
- data/lib/extensions/openstruct_merge.rb +0 -48
- data/lib/extensions/ruby_llm/.irbrc +0 -56
- data/lib/extensions/ruby_llm/modalities.rb +0 -36
- data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
- data/lib/refinements/string.rb +0 -16
- data/main.just +0 -76
|
@@ -11,6 +11,7 @@ require_relative 'model_spec'
|
|
|
11
11
|
require_relative '../skill_utils'
|
|
12
12
|
|
|
13
13
|
module AIA
|
|
14
|
+
# rubocop:disable Metrics/ModuleLength
|
|
14
15
|
module CLIParser
|
|
15
16
|
class << self
|
|
16
17
|
# Parse CLI arguments and return a hash of overrides
|
|
@@ -23,8 +24,8 @@ module AIA
|
|
|
23
24
|
parser = create_option_parser(options)
|
|
24
25
|
parser.parse!
|
|
25
26
|
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
$stderr.puts "ERROR: #{e.message}"
|
|
28
|
+
$stderr.puts " use --help for usage report"
|
|
28
29
|
exit 1
|
|
29
30
|
end
|
|
30
31
|
|
|
@@ -51,10 +52,11 @@ module AIA
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def setup_banner(opts)
|
|
54
|
-
opts.banner = "Usage: aia [options] [PROMPT_ID] [CONTEXT_FILE]*\n"
|
|
55
|
-
"
|
|
55
|
+
opts.banner = "Usage: aia [options] [PROMPT_ID] [CONTEXT_FILE]*\n " \
|
|
56
|
+
"aia [options] --chat [PROMPT_ID] [CONTEXT_FILE]*"
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
58
60
|
def setup_mode_options(opts, options)
|
|
59
61
|
opts.separator "\nMode Options:"
|
|
60
62
|
|
|
@@ -64,17 +66,38 @@ module AIA
|
|
|
64
66
|
|
|
65
67
|
opts.on("-f", "--fuzzy", "Use fuzzy matching for prompt search") do
|
|
66
68
|
unless system("which fzf > /dev/null 2>&1")
|
|
67
|
-
|
|
69
|
+
$stderr.puts "Error: 'fzf' is not installed. Please install 'fzf' to use the --fuzzy option."
|
|
68
70
|
exit 1
|
|
69
71
|
end
|
|
70
72
|
options[:fuzzy] = true
|
|
71
73
|
end
|
|
72
74
|
|
|
73
|
-
opts.on("--
|
|
74
|
-
|
|
75
|
+
opts.on("--expert-routing", "Route each prompt to a specialist robot based on domain classification") do
|
|
76
|
+
options[:expert_routing] = true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
opts.on("--track-pipeline", "Track pipeline execution in TrakFlow") do
|
|
80
|
+
options[:track_pipeline] = true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
opts.on("--concurrent-auto", "Automatically parallelize independent MCP servers") do
|
|
84
|
+
options[:concurrent_auto] = true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
opts.on("-A", "--[no-]auto-tool-filter", "Use TF-IDF similarity-based tool filtering (default: on)") do |v|
|
|
88
|
+
options[:auto_tool_filter] = v
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
opts.on("--load", "Load persisted tool filter databases from ~/.config/aia/") do
|
|
92
|
+
options[:tool_filter_load] = true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
opts.on("--save", "Save tool filter databases to ~/.config/aia/") do
|
|
96
|
+
options[:tool_filter_save] = true
|
|
75
97
|
end
|
|
76
98
|
end
|
|
77
99
|
|
|
100
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
78
101
|
def setup_model_options(opts, options)
|
|
79
102
|
opts.separator "\nModel Options:"
|
|
80
103
|
|
|
@@ -82,8 +105,10 @@ module AIA
|
|
|
82
105
|
list_available_models(query)
|
|
83
106
|
end
|
|
84
107
|
|
|
85
|
-
opts.on("-m MODEL", "--model MODEL",
|
|
86
|
-
|
|
108
|
+
opts.on("-m MODEL", "--model MODEL",
|
|
109
|
+
"Set LLM model(s). Format: MODEL[=ROLE][,MODEL[=ROLE]]... " \
|
|
110
|
+
"Multiple models run in parallel; use --consensus to synthesize results") do |model_string|
|
|
111
|
+
options[:models] = (options[:models] || []) + parse_models_with_roles(model_string)
|
|
87
112
|
end
|
|
88
113
|
|
|
89
114
|
opts.on("--[no-]consensus", "Enable/disable consensus mode for multi-model responses") do |consensus|
|
|
@@ -104,6 +129,7 @@ module AIA
|
|
|
104
129
|
end
|
|
105
130
|
end
|
|
106
131
|
|
|
132
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
107
133
|
def setup_file_options(opts, options)
|
|
108
134
|
opts.separator "\nFile & Output Options:"
|
|
109
135
|
|
|
@@ -112,13 +138,13 @@ module AIA
|
|
|
112
138
|
end
|
|
113
139
|
|
|
114
140
|
opts.on("-o", "--[no-]output [FILE]", "Write response to FILE (default: temp.md; --no-output to disable)") do |file|
|
|
115
|
-
if file == false
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
options[:output] = if file == false
|
|
142
|
+
nil
|
|
143
|
+
elsif file.nil?
|
|
144
|
+
'temp.md'
|
|
145
|
+
else
|
|
146
|
+
File.expand_path(file, Dir.pwd)
|
|
147
|
+
end
|
|
122
148
|
end
|
|
123
149
|
|
|
124
150
|
opts.on("-a", "--[no-]append", "Append to output file instead of overwriting") do |append|
|
|
@@ -134,6 +160,8 @@ module AIA
|
|
|
134
160
|
end
|
|
135
161
|
end
|
|
136
162
|
|
|
163
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
164
|
+
# :reek:DuplicateMethodCall -- options[:pipeline] is appended inside two separate OptionParser closures
|
|
137
165
|
def setup_prompt_options(opts, options)
|
|
138
166
|
opts.separator "\nPrompt Options:"
|
|
139
167
|
|
|
@@ -141,6 +169,10 @@ module AIA
|
|
|
141
169
|
options[:prompts_dir] = dir
|
|
142
170
|
end
|
|
143
171
|
|
|
172
|
+
opts.on("--plugins-dir DIR", "Set directory containing AIA plugin files") do |dir|
|
|
173
|
+
options[:plugins_dir] = dir
|
|
174
|
+
end
|
|
175
|
+
|
|
144
176
|
opts.on("--roles-prefix PREFIX", "Set subdirectory name for role files (default: roles)") do |prefix|
|
|
145
177
|
options[:roles_prefix] = prefix
|
|
146
178
|
end
|
|
@@ -149,6 +181,19 @@ module AIA
|
|
|
149
181
|
options[:role] = role
|
|
150
182
|
end
|
|
151
183
|
|
|
184
|
+
opts.on("--skills-dir DIR", "Set directory containing skill subdirectories") do |dir|
|
|
185
|
+
options[:skills_dir] = dir
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
opts.on("-s", "--skill SKILL_IDS", "Append skill(s) to prompt; comma-separated IDs or paths") do |skill_ids|
|
|
189
|
+
options[:skills] ||= []
|
|
190
|
+
options[:skills] += skill_ids.split(',').map(&:strip).reject(&:empty?)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
opts.on("--list-skills", "List available skills and exit") do
|
|
194
|
+
options[:list_skills] = true
|
|
195
|
+
end
|
|
196
|
+
|
|
152
197
|
opts.on("-n", "--next PROMPT_ID", "Set next prompt to process") do |next_prompt|
|
|
153
198
|
options[:pipeline] ||= []
|
|
154
199
|
options[:pipeline] << next_prompt
|
|
@@ -159,32 +204,9 @@ module AIA
|
|
|
159
204
|
options[:pipeline] += pipeline.split(',').map(&:strip)
|
|
160
205
|
end
|
|
161
206
|
|
|
162
|
-
|
|
163
207
|
opts.on("--system-prompt PROMPT_ID", "Set system prompt for chat sessions") do |prompt_id|
|
|
164
208
|
options[:system_prompt] = prompt_id
|
|
165
209
|
end
|
|
166
|
-
|
|
167
|
-
opts.on('--regex PATTERN', '[DEPRECATED] Parameter regex (PM v1.0.0 uses ERB parameters)') do |pattern|
|
|
168
|
-
warn "Warning: --regex is deprecated. PM v1.0.0 uses ERB parameters (<%= param %>)."
|
|
169
|
-
options[:parameter_regex] = pattern
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
opts.on("--skills-dir DIR", "Set directory containing skill subdirectories") do |dir|
|
|
173
|
-
options[:skills_dir] = dir
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
opts.on("--skills-prefix PREFIX", "Set subdirectory name for skill files (default: skills)") do |prefix|
|
|
177
|
-
options[:skills_prefix] = prefix
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
opts.on("-s", "--skill SKILL_IDS", "Prepend skill(s) to prompt (comma-separated IDs or paths)") do |ids|
|
|
181
|
-
options[:skills] ||= []
|
|
182
|
-
options[:skills] += ids.split(',').map(&:strip)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
opts.on("--list-skills", "List available skills and exit") do
|
|
186
|
-
options[:list_skills] = true
|
|
187
|
-
end
|
|
188
210
|
end
|
|
189
211
|
|
|
190
212
|
def setup_ai_parameters(opts, options)
|
|
@@ -194,7 +216,7 @@ module AIA
|
|
|
194
216
|
options[:temperature] = temp
|
|
195
217
|
end
|
|
196
218
|
|
|
197
|
-
opts.on("--max-tokens TOKENS", Integer, "Set maximum tokens for text generation (default:
|
|
219
|
+
opts.on("--max-tokens TOKENS", Integer, "Set maximum tokens for text generation (default: 32767)") do |tokens|
|
|
198
220
|
options[:max_tokens] = tokens
|
|
199
221
|
end
|
|
200
222
|
|
|
@@ -211,6 +233,7 @@ module AIA
|
|
|
211
233
|
end
|
|
212
234
|
end
|
|
213
235
|
|
|
236
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
214
237
|
def setup_audio_image_options(opts, options)
|
|
215
238
|
opts.separator "\nAudio & Image Options:"
|
|
216
239
|
|
|
@@ -218,10 +241,22 @@ module AIA
|
|
|
218
241
|
options[:speak] = true
|
|
219
242
|
end
|
|
220
243
|
|
|
221
|
-
opts.on("--voice VOICE", "Set voice for speech output
|
|
244
|
+
opts.on("--voice VOICE", "Set voice for speech output") do |voice|
|
|
222
245
|
options[:voice] = voice
|
|
223
246
|
end
|
|
224
247
|
|
|
248
|
+
opts.on("--speak-command CMD", "Command used for TTS (default: say)") do |cmd|
|
|
249
|
+
options[:speak_command] = cmd
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
opts.on("--paint", "Generate an image from the prompt instead of a text response") do
|
|
253
|
+
options[:paint] = true
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
opts.on("--im", "--image-model MODEL", "Set image model for generation (default: dall-e-3)") do |model|
|
|
257
|
+
options[:image_model] = model
|
|
258
|
+
end
|
|
259
|
+
|
|
225
260
|
opts.on("--is", "--image-size SIZE", "Set image size for generation (default: 1024x1024)") do |size|
|
|
226
261
|
options[:image_size] = size
|
|
227
262
|
end
|
|
@@ -235,9 +270,14 @@ module AIA
|
|
|
235
270
|
end
|
|
236
271
|
end
|
|
237
272
|
|
|
273
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
238
274
|
def setup_tool_options(opts, options)
|
|
239
275
|
opts.separator "\nTool & Extension Options:"
|
|
240
276
|
|
|
277
|
+
opts.on('--allow-ruby-eval', 'Enable /ruby directive (executes arbitrary Ruby code with full process privileges)') do
|
|
278
|
+
options[:allow_ruby_eval] = true
|
|
279
|
+
end
|
|
280
|
+
|
|
241
281
|
opts.on("--rq LIBS", "--require LIBS", "Require Ruby libraries for Ruby directive") do |libs|
|
|
242
282
|
options[:require_libs] ||= []
|
|
243
283
|
options[:require_libs] += libs.split(',')
|
|
@@ -264,11 +304,18 @@ module AIA
|
|
|
264
304
|
|
|
265
305
|
def setup_utility_options(opts, options)
|
|
266
306
|
opts.separator "\nUtility Options:"
|
|
307
|
+
setup_logging_options(opts, options)
|
|
308
|
+
setup_output_options(opts, options)
|
|
309
|
+
setup_mcp_options(opts, options)
|
|
310
|
+
setup_meta_options(opts, options)
|
|
311
|
+
end
|
|
267
312
|
|
|
313
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
314
|
+
def setup_logging_options(opts, options)
|
|
268
315
|
opts.on("--log-level LEVEL", "Set log level (debug|info|warn|error|fatal)") do |level|
|
|
269
316
|
level = level.downcase
|
|
270
317
|
unless %w[debug info warn error fatal].include?(level)
|
|
271
|
-
|
|
318
|
+
$stderr.puts "ERROR: Invalid log level '#{level}'. Must be one of: debug, info, warn, error, fatal"
|
|
272
319
|
exit 1
|
|
273
320
|
end
|
|
274
321
|
options[:log_level_override] = level
|
|
@@ -296,6 +343,22 @@ module AIA
|
|
|
296
343
|
opts.on("-v", "--[no-]verbose", "Enable verbose output") do |value|
|
|
297
344
|
options[:verbose] = value
|
|
298
345
|
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
349
|
+
def setup_output_options(opts, options)
|
|
350
|
+
opts.on("--[no-]thinking", "Show raw thinking/reasoning blocks in output (default: off)") do |v|
|
|
351
|
+
options[:thinking] = v
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
opts.on("--tokens", "Display token usage and elapsed response time after each turn") do
|
|
355
|
+
options[:tokens] = true
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
opts.on("--cost", "Display cost calculations and elapsed time (implies --tokens)") do
|
|
359
|
+
options[:cost] = true
|
|
360
|
+
options[:tokens] = true
|
|
361
|
+
end
|
|
299
362
|
|
|
300
363
|
opts.on("--refresh DAYS", Integer, "Set refresh interval (days) for cached models list (default: 7)") do |days|
|
|
301
364
|
options[:refresh] = days || 0
|
|
@@ -308,16 +371,10 @@ module AIA
|
|
|
308
371
|
opts.on("--completion SHELL", "Generate shell completion script (bash|zsh|fish) and exit") do |shell|
|
|
309
372
|
options[:completion] = shell
|
|
310
373
|
end
|
|
374
|
+
end
|
|
311
375
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
opts.on("--cost", "Display cost calculations (implies --tokens)") do
|
|
317
|
-
options[:cost] = true
|
|
318
|
-
options[:tokens] = true # --cost implies --tokens
|
|
319
|
-
end
|
|
320
|
-
|
|
376
|
+
# :reek:TooManyStatements -- one opts.on registration per CLI flag; splitting the group would scatter related flags
|
|
377
|
+
def setup_mcp_options(opts, options)
|
|
321
378
|
opts.on("--mcp FILE", "Load MCP server(s) from JSON file (repeatable)") do |file|
|
|
322
379
|
options[:mcp_files] ||= []
|
|
323
380
|
options[:mcp_files] << file
|
|
@@ -340,55 +397,65 @@ module AIA
|
|
|
340
397
|
options[:mcp_skip] ||= []
|
|
341
398
|
options[:mcp_skip] += names.split(',').map(&:strip)
|
|
342
399
|
end
|
|
400
|
+
end
|
|
343
401
|
|
|
402
|
+
def setup_meta_options(opts, _options)
|
|
344
403
|
opts.on("--version", "Show version and exit") do
|
|
345
404
|
puts AIA::VERSION
|
|
346
405
|
exit
|
|
347
406
|
end
|
|
348
407
|
|
|
349
408
|
opts.on("-h", "--help", "Show this help and exit") do
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
AIA - Your AI Assistant (v#{AIA::VERSION})
|
|
353
|
-
- Manage AI prompts with embedded directives
|
|
354
|
-
- Integrate with shell and Ruby (ERB) processing
|
|
355
|
-
- Run batch processes and prompt pipelines
|
|
356
|
-
- Engage in interactive chat sessions
|
|
357
|
-
- Use custom tools and MCP servers
|
|
358
|
-
|
|
359
|
-
HELP
|
|
360
|
-
|
|
361
|
-
puts opts
|
|
362
|
-
|
|
363
|
-
puts <<~EXTRA
|
|
364
|
-
|
|
365
|
-
Explore Further:
|
|
366
|
-
- AIA GitHub Repository: https://github.com/MadBomber/aia
|
|
367
|
-
- AIA Documentation: https://madbomber.github.io/aia
|
|
368
|
-
- AIA Changelog: https://github.com/MadBomber/aia/blob/main/CHANGELOG.md
|
|
369
|
-
- AIA Examples: https://github.com/MadBomber/aia/tree/main/examples
|
|
370
|
-
- Report an Issue: https://github.com/MadBomber/aia/issues
|
|
371
|
-
- RubyLLM Documentation: https://rubyllm.com
|
|
372
|
-
- RubyLLM Tool Docs: https://rubyllm.com/guides/tools
|
|
373
|
-
- PromptManager Docs: https://madbomber.github.io/prompt_manager
|
|
374
|
-
- ERB Documentation: https://docs.ruby-lang.org/en/master/ERB.html
|
|
375
|
-
- MCP Specification: https://modelcontextprotocol.io
|
|
376
|
-
- MCP Client Docs: https://github.com/patvice/ruby_llm-mcp/blob/main/README.md
|
|
377
|
-
|
|
378
|
-
EXTRA
|
|
379
|
-
|
|
409
|
+
print_help(opts)
|
|
380
410
|
exit
|
|
381
411
|
end
|
|
382
412
|
end
|
|
383
413
|
|
|
414
|
+
def print_help(opts)
|
|
415
|
+
puts <<~HELP
|
|
416
|
+
|
|
417
|
+
AIA - Your AI Assistant (v#{AIA::VERSION})
|
|
418
|
+
- Manage AI prompts with embedded directives
|
|
419
|
+
- Integrate with shell and Ruby (ERB) processing
|
|
420
|
+
- Run batch processes and prompt pipelines
|
|
421
|
+
- Engage in interactive chat sessions
|
|
422
|
+
- Use custom tools and MCP servers
|
|
423
|
+
- Compare the same prompt across multiple models (--model m1,m2,m3 [--consensus])
|
|
424
|
+
- Measure response time and token usage with --tokens; add cost with --cost
|
|
425
|
+
- Benchmark concurrent vs. serial performance (/decompose reports speedup metrics)
|
|
426
|
+
|
|
427
|
+
HELP
|
|
428
|
+
|
|
429
|
+
puts opts
|
|
430
|
+
|
|
431
|
+
puts <<~EXTRA
|
|
432
|
+
|
|
433
|
+
Explore Further:
|
|
434
|
+
- AIA GitHub Repository: https://github.com/MadBomber/aia
|
|
435
|
+
- AIA Documentation: https://madbomber.github.io/aia
|
|
436
|
+
- AIA Changelog: https://github.com/MadBomber/aia/blob/main/CHANGELOG.md
|
|
437
|
+
- AIA Examples: https://github.com/MadBomber/aia/tree/main/examples
|
|
438
|
+
- Report an Issue: https://github.com/MadBomber/aia/issues
|
|
439
|
+
- RubyLLM Documentation: https://rubyllm.com
|
|
440
|
+
- RubyLLM Tool Docs: https://rubyllm.com/guides/tools
|
|
441
|
+
- PromptManager Docs: https://madbomber.github.io/prompt_manager
|
|
442
|
+
- ERB Documentation: https://docs.ruby-lang.org/en/master/ERB.html
|
|
443
|
+
- MCP Specification: https://modelcontextprotocol.io
|
|
444
|
+
- MCP Client Docs: https://github.com/patvice/ruby_llm-mcp/blob/main/README.md
|
|
445
|
+
|
|
446
|
+
EXTRA
|
|
447
|
+
end
|
|
448
|
+
|
|
384
449
|
# Parse model string into array of ModelSpec-compatible hashes
|
|
385
450
|
#
|
|
386
451
|
# @param model_string [String] comma-separated models with optional roles
|
|
387
452
|
# @return [Array<Hash>] array of model specs
|
|
453
|
+
# :reek:TooManyStatements -- one-pass parser over MODEL[=ROLE] specs sharing the instance counter
|
|
388
454
|
def parse_models_with_roles(model_string)
|
|
389
455
|
models = []
|
|
390
456
|
model_counts = Hash.new(0)
|
|
391
457
|
|
|
458
|
+
# rubocop:disable-next Metrics/BlockLength
|
|
392
459
|
model_string.split(',').each do |spec|
|
|
393
460
|
spec.strip!
|
|
394
461
|
|
|
@@ -429,42 +496,42 @@ module AIA
|
|
|
429
496
|
end
|
|
430
497
|
|
|
431
498
|
def validate_role_exists(role_id)
|
|
432
|
-
if AIA::SkillUtils.path_based_id?(role_id)
|
|
433
|
-
expanded = File.expand_path(role_id)
|
|
434
|
-
expanded += '.md' if File.extname(expanded).empty?
|
|
435
|
-
raise ArgumentError, "Role file not found: #{expanded}" unless File.exist?(expanded)
|
|
436
|
-
return
|
|
437
|
-
end
|
|
499
|
+
return validate_role_path!(role_id) if AIA::SkillUtils.path_based_id?(role_id)
|
|
438
500
|
|
|
439
|
-
prompts_dir
|
|
501
|
+
prompts_dir = ENV.fetch('AIA_PROMPTS__DIR', File.join(Dir.home, '.prompts'))
|
|
440
502
|
roles_prefix = ENV.fetch('AIA_PROMPTS__ROLES_PREFIX', 'roles')
|
|
441
|
-
|
|
442
|
-
unless role_id.start_with?(roles_prefix)
|
|
443
|
-
role_id = "#{roles_prefix}/#{role_id}"
|
|
444
|
-
end
|
|
503
|
+
role_id = "#{roles_prefix}/#{role_id}" unless role_id.start_with?(roles_prefix)
|
|
445
504
|
|
|
446
505
|
role_file_path = File.join(prompts_dir, "#{role_id}.md")
|
|
506
|
+
return if File.exist?(role_file_path)
|
|
447
507
|
|
|
448
|
-
|
|
449
|
-
|
|
508
|
+
raise ArgumentError, role_not_found_message(role_file_path, prompts_dir, roles_prefix)
|
|
509
|
+
end
|
|
450
510
|
|
|
451
|
-
|
|
511
|
+
# A path-based role id must resolve to an existing file.
|
|
512
|
+
def validate_role_path!(role_id)
|
|
513
|
+
expanded = File.expand_path(role_id)
|
|
514
|
+
expanded += '.md' if File.extname(expanded).empty?
|
|
515
|
+
raise ArgumentError, "Role file not found: #{expanded}" unless File.exist?(expanded)
|
|
516
|
+
end
|
|
452
517
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
else
|
|
457
|
-
error_msg += "Available roles:\n"
|
|
458
|
-
error_msg += available_roles.map { |r| " - #{r}" }.join("\n")
|
|
459
|
-
error_msg += "\n\nCreate the role file or use an existing role."
|
|
460
|
-
end
|
|
518
|
+
def role_not_found_message(role_file_path, prompts_dir, roles_prefix)
|
|
519
|
+
available_roles = list_available_role_names(prompts_dir, roles_prefix)
|
|
520
|
+
message = "Role file not found: #{role_file_path}\n\n"
|
|
461
521
|
|
|
462
|
-
|
|
522
|
+
if available_roles.empty?
|
|
523
|
+
message + "No roles directory found at #{File.join(prompts_dir, roles_prefix)}\n" \
|
|
524
|
+
"Create the directory and add role files to use this feature."
|
|
525
|
+
else
|
|
526
|
+
message + "Available roles:\n" +
|
|
527
|
+
available_roles.map { |r| " - #{r}" }.join("\n") +
|
|
528
|
+
"\n\nCreate the role file or use an existing role."
|
|
463
529
|
end
|
|
464
530
|
end
|
|
465
531
|
|
|
532
|
+
# :reek:TooManyStatements -- sequential terminal report: guards, then a markdown table per role; exits after printing
|
|
466
533
|
def list_available_roles
|
|
467
|
-
prompts_dir = ENV.fetch('AIA_PROMPTS__DIR', File.join(
|
|
534
|
+
prompts_dir = ENV.fetch('AIA_PROMPTS__DIR', File.join(Dir.home, '.prompts'))
|
|
468
535
|
roles_prefix = ENV.fetch('AIA_PROMPTS__ROLES_PREFIX', 'roles')
|
|
469
536
|
roles_dir = File.join(prompts_dir, roles_prefix)
|
|
470
537
|
|
|
@@ -484,13 +551,16 @@ module AIA
|
|
|
484
551
|
|
|
485
552
|
roles.each do |role_id|
|
|
486
553
|
role_file = File.join(roles_dir, "#{role_id}.md")
|
|
487
|
-
|
|
554
|
+
front_matter = AIA::SkillUtils.parse_front_matter(role_file)
|
|
488
555
|
|
|
489
556
|
puts "## #{role_id}"
|
|
490
557
|
puts
|
|
491
558
|
puts "| Key | Value |"
|
|
492
559
|
puts "|-----|-------|"
|
|
493
|
-
|
|
560
|
+
front_matter.each do |key, value|
|
|
561
|
+
safe_value = value.to_s.gsub('|', '\\|')
|
|
562
|
+
puts "| #{key} | #{safe_value} |"
|
|
563
|
+
end
|
|
494
564
|
puts
|
|
495
565
|
end
|
|
496
566
|
end
|
|
@@ -500,74 +570,76 @@ module AIA
|
|
|
500
570
|
return [] unless Dir.exist?(roles_dir)
|
|
501
571
|
|
|
502
572
|
Dir.glob("**/*.md", base: roles_dir)
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
573
|
+
.map { |f| f.chomp('.md') }
|
|
574
|
+
.reject { |f| f.split('/').any? { |part| part.start_with?('_') } }
|
|
575
|
+
.sort
|
|
506
576
|
end
|
|
507
577
|
|
|
578
|
+
# :reek:TooManyStatements -- sequential --available-models report: parse query, print matches, summary, exit
|
|
508
579
|
def list_available_models(query)
|
|
509
580
|
require 'ruby_llm'
|
|
510
581
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
else
|
|
514
|
-
query = query.split(',')
|
|
515
|
-
end
|
|
582
|
+
query = query.nil? ? [] : query.split(',')
|
|
583
|
+
modality_terms, substring_terms = parse_model_query(query)
|
|
516
584
|
|
|
517
585
|
header = "\nAvailable LLMs"
|
|
518
586
|
header += " for #{query.join(' and ')}" if query.any?
|
|
519
|
-
|
|
520
587
|
puts header + ':'
|
|
521
588
|
puts
|
|
522
589
|
|
|
523
|
-
|
|
524
|
-
q2 = query.reject { |q| q.include?('_to_') }
|
|
525
|
-
|
|
526
|
-
counter = 0
|
|
527
|
-
|
|
528
|
-
RubyLLM.models.all.each do |llm|
|
|
529
|
-
inputs = llm.modalities.input.join(',')
|
|
530
|
-
outputs = llm.modalities.output.join(',')
|
|
531
|
-
entry = "- #{llm.id} (#{llm.provider}) #{inputs} to #{outputs}"
|
|
590
|
+
counter = print_matching_models(modality_terms, substring_terms)
|
|
532
591
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
next
|
|
537
|
-
end
|
|
592
|
+
puts if counter.positive?
|
|
593
|
+
puts "#{counter} LLMs matching your query"
|
|
594
|
+
puts
|
|
538
595
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
q2.each { |q| show_it &&= entry.include?(q) }
|
|
596
|
+
exit
|
|
597
|
+
end
|
|
542
598
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
599
|
+
# Print each matching model entry; returns the number printed.
|
|
600
|
+
def print_matching_models(modality_terms, substring_terms)
|
|
601
|
+
RubyLLM.models.all.count do |llm|
|
|
602
|
+
entry = format_model_entry(llm)
|
|
603
|
+
visible = model_entry_visible?(llm, entry, modality_terms, substring_terms)
|
|
604
|
+
puts entry if visible
|
|
605
|
+
visible
|
|
547
606
|
end
|
|
607
|
+
end
|
|
548
608
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
609
|
+
# Split a query into modality terms (e.g. "text_to_text", leading ':'
|
|
610
|
+
# stripped) and plain substring terms.
|
|
611
|
+
def parse_model_query(query)
|
|
612
|
+
modality, substrings = query.partition { |q| q.include?('_to_') }
|
|
613
|
+
[modality.map { |q| q.delete_prefix(':') }, substrings]
|
|
614
|
+
end
|
|
552
615
|
|
|
553
|
-
|
|
616
|
+
def format_model_entry(llm)
|
|
617
|
+
modalities = llm.modalities
|
|
618
|
+
"- #{llm.id} (#{llm.provider}) #{modalities.input.join(',')} to #{modalities.output.join(',')}"
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def model_entry_visible?(llm, entry, modality_terms, substring_terms)
|
|
622
|
+
modality_terms.all? { |q| llm.modalities.send("#{q}?") } &&
|
|
623
|
+
substring_terms.all? { |q| entry.include?(q) }
|
|
554
624
|
end
|
|
625
|
+
# rubocop:enable Metrics/ModuleLength
|
|
555
626
|
|
|
627
|
+
# :reek:DuplicateMethodCall -- each `exit 1` terminates a distinct validation failure; there is no value to hoist
|
|
556
628
|
def process_tools_paths(path_list)
|
|
557
629
|
paths = []
|
|
558
630
|
|
|
559
631
|
if path_list.empty?
|
|
560
|
-
|
|
632
|
+
$stderr.puts "No list of paths for --tools option"
|
|
561
633
|
exit 1
|
|
562
634
|
end
|
|
563
635
|
|
|
564
636
|
path_list.split(',').map(&:strip).uniq.each do |a_path|
|
|
565
637
|
if File.exist?(a_path)
|
|
566
638
|
if File.file?(a_path)
|
|
567
|
-
if
|
|
639
|
+
if File.extname(a_path) == '.rb'
|
|
568
640
|
paths << a_path
|
|
569
641
|
else
|
|
570
|
-
|
|
642
|
+
$stderr.puts "file should have *.rb extension: #{a_path}"
|
|
571
643
|
exit 1
|
|
572
644
|
end
|
|
573
645
|
elsif File.directory?(a_path)
|
|
@@ -575,7 +647,7 @@ module AIA
|
|
|
575
647
|
paths += rb_files
|
|
576
648
|
end
|
|
577
649
|
else
|
|
578
|
-
|
|
650
|
+
$stderr.puts "file/dir path is not valid: #{a_path}"
|
|
579
651
|
exit 1
|
|
580
652
|
end
|
|
581
653
|
end
|