aia 1.0.0.pre.beta → 1.1.0

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/CHANGELOG.md +89 -0
  4. data/COMMITS.md +192 -11
  5. data/README.md +327 -110
  6. data/docs/cli-reference.md +93 -10
  7. data/docs/configuration.md +29 -36
  8. data/docs/contributing.md +2 -2
  9. data/docs/directives-reference.md +49 -27
  10. data/docs/examples/index.md +2 -2
  11. data/docs/examples/mcp/index.md +93 -97
  12. data/docs/examples/prompts/automation/index.md +3 -2
  13. data/docs/examples/tools/index.md +17 -27
  14. data/docs/faq.md +9 -12
  15. data/docs/guides/basic-usage.md +4 -4
  16. data/docs/guides/chat.md +39 -34
  17. data/docs/guides/tools.md +4 -4
  18. data/docs/index.md +36 -62
  19. data/docs/installation.md +1 -1
  20. data/docs/mcp-integration.md +75 -139
  21. data/docs/prompt_management.md +88 -1
  22. data/docs/security.md +79 -81
  23. data/docs/tools-and-mcp-examples.md +8 -6
  24. data/docs/workflows-and-pipelines.md +2 -6
  25. data/examples/.gitignore +1 -0
  26. data/examples/README.md +41 -0
  27. data/examples/run_all.sh +261 -0
  28. data/lib/aia/adapter/chat_execution.rb +9 -7
  29. data/lib/aia/adapter/mcp_connector.rb +0 -29
  30. data/lib/aia/adapter/modality_handlers.rb +23 -15
  31. data/lib/aia/adapter/tool_filter.rb +21 -0
  32. data/lib/aia/adapter/tool_loader.rb +1 -9
  33. data/lib/aia/chat_loop.rb +244 -0
  34. data/lib/aia/chat_processor_service.rb +6 -3
  35. data/lib/aia/config/cli_parser.rb +56 -18
  36. data/lib/aia/config/defaults.yml +17 -2
  37. data/lib/aia/config/validator.rb +52 -11
  38. data/lib/aia/config.rb +29 -3
  39. data/lib/aia/directive.rb +29 -0
  40. data/lib/aia/directives/configuration_directives.rb +2 -1
  41. data/lib/aia/directives/execution_directives.rb +1 -1
  42. data/lib/aia/directives/model_directives.rb +28 -27
  43. data/lib/aia/directives/web_and_file_directives.rb +78 -40
  44. data/lib/aia/errors.rb +20 -1
  45. data/lib/aia/fzf.rb +8 -7
  46. data/lib/aia/input_collector.rb +24 -0
  47. data/lib/aia/prompt_handler.rb +36 -8
  48. data/lib/aia/prompt_pipeline.rb +183 -0
  49. data/lib/aia/session.rb +22 -372
  50. data/lib/aia/skill_utils.rb +61 -0
  51. data/lib/aia/ui_presenter.rb +8 -0
  52. data/lib/aia.rb +4 -0
  53. metadata +19 -45
data/examples/README.md CHANGED
@@ -185,11 +185,51 @@ Docs: [Executable Prompts](https://madbomber.github.io/aia/guides/executable-pro
185
185
 
186
186
  Docs: [Chat Guide](https://madbomber.github.io/aia/guides/chat/), [Directives Reference](https://madbomber.github.io/aia/directives-reference/), [Workflows and Pipelines](https://madbomber.github.io/aia/workflows-and-pipelines/)
187
187
 
188
+ ## Running All Demos
189
+
190
+ `run_all.sh` runs all non-interactive demo scripts in sequence and captures the combined output. It serves as a structural integration test — since LLM responses are non-deterministic, exact diffs between runs won't match, but you can spot missing sections, crashes, or changed command output.
191
+
192
+ ```bash
193
+ cd examples
194
+
195
+ # Run all demos and save output to a timestamped log
196
+ bash run_all.sh
197
+
198
+ # Print to terminal only (no log file)
199
+ bash run_all.sh --no-save
200
+
201
+ # Save to your own file with tee
202
+ bash run_all.sh --no-save 2>&1 | tee my_run.log
203
+
204
+ # Show help
205
+ bash run_all.sh --help
206
+ ```
207
+
208
+ Output logs are saved to `examples/output/run_YYYYMMDD_HHMMSS.log`. Compare runs with:
209
+
210
+ ```bash
211
+ diff output/run_PREV.log output/run_LATEST.log
212
+ ```
213
+
214
+ **What it runs:** Scripts 01-14, 16-19, and 21 (18 scripts total).
215
+
216
+ **What it skips:**
217
+
218
+ | Script | Reason |
219
+ |--------|--------|
220
+ | `00_setup_aia.sh` | Run manually first (pulls models, writes config) |
221
+ | `15_parameters.sh` | Part 2 prompts interactively for a required parameter |
222
+ | `20_mcp_servers.sh` | Requires Node.js/npx + MCP filesystem server |
223
+ | `22_chat_mode.sh` | Interactive chat session (uses `expect`) |
224
+
225
+ The output includes a banner with version info, per-script pass/fail status, and a summary with counts.
226
+
188
227
  ## Directory Structure
189
228
 
190
229
  ```
191
230
  examples/
192
231
  common.sh # Shared setup sourced by all demos
232
+ run_all.sh # Batch runner for non-interactive demos
193
233
  aia_config.yml # Generated by 00_setup_aia.sh
194
234
  aia_config_with_mcp.yml # Config with MCP server (demo 20)
195
235
  prompts_dir/ # All demo prompt files
@@ -199,6 +239,7 @@ examples/
199
239
  directives/ # Custom directive files (demo 16)
200
240
  tools/ # Local tool files (demo 19)
201
241
  mcp/ # MCP server configs (demo 20)
242
+ output/ # Timestamped run logs (git-ignored)
202
243
  ```
203
244
 
204
245
  ## Notes
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env bash
2
+ # examples/run_all.sh
3
+ #
4
+ # Runs all example demo scripts in order and captures output.
5
+ #
6
+ # The output is saved to a timestamped file for comparison against
7
+ # future runs — a super-meta integration test. Since LLM responses
8
+ # are non-deterministic, exact diffs won't match; look for structural
9
+ # differences (missing sections, crashes, changed command output).
10
+ #
11
+ # What it skips:
12
+ # - 00_setup_aia.sh — run manually first (pulls models, writes config)
13
+ # - 15_parameters.sh — Part 2 prompts interactively for a required param
14
+ # - 20_mcp_servers.sh — requires Node.js/npx + MCP filesystem server
15
+ # - 22_chat_mode.sh — interactive chat (Parts 1-4 use expect; Part 5
16
+ # opens a live session)
17
+ #
18
+ # Prerequisites:
19
+ # - Run 00_setup_aia.sh first
20
+ # - Ollama running with qwen3 model available
21
+ #
22
+ # Usage:
23
+ # cd examples
24
+ # bash run_all.sh # run and save output
25
+ # bash run_all.sh --no-save # run without saving (just print)
26
+ # diff output/run_PREV.log output/run_LATEST.log # compare runs
27
+ #
28
+ # Exit codes:
29
+ # 0 — all scripts completed (some may have non-fatal warnings)
30
+ # 1 — setup not done or a script failed fatally
31
+
32
+ set -uo pipefail
33
+
34
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
+ cd "${SCRIPT_DIR}"
36
+
37
+ # --- Usage ---
38
+
39
+ usage() {
40
+ cat <<'USAGE'
41
+ Usage: bash run_all.sh [OPTIONS]
42
+
43
+ Run all non-interactive AIA example scripts and capture output.
44
+
45
+ Options:
46
+ -h, --help Show this help message and exit
47
+ --no-save Print output to the terminal only (do not save a log file)
48
+
49
+ By default the output is saved to examples/output/run_YYYYMMDD_HHMMSS.log.
50
+ Since LLM responses are non-deterministic, exact diffs between runs won't
51
+ match; look for structural differences (missing sections, crashes, changed
52
+ command output).
53
+
54
+ Saving output with tee:
55
+ bash run_all.sh --no-save 2>&1 | tee my_run.log
56
+
57
+ Use --no-save so the script does not write its own log file, then pipe
58
+ through tee to send output to both the terminal and your chosen file.
59
+
60
+ Comparing runs:
61
+ diff output/run_PREV.log output/run_LATEST.log
62
+
63
+ Skipped scripts (require manual or interactive setup):
64
+ 00_setup_aia.sh — run manually first (pulls models, writes config)
65
+ 15_parameters.sh — Part 2 prompts interactively for a required param
66
+ 20_mcp_servers.sh — requires Node.js/npx + MCP filesystem server
67
+ 22_chat_mode.sh — interactive chat session
68
+
69
+ Prerequisites:
70
+ 1. Run 00_setup_aia.sh first
71
+ 2. Ollama running with the qwen3 model available
72
+
73
+ Exit codes:
74
+ 0 All scripts completed (some may have non-fatal warnings)
75
+ 1 Setup not done or a script failed fatally
76
+ USAGE
77
+ }
78
+
79
+ # --- Parse flags ---
80
+
81
+ SAVE_OUTPUT=true
82
+ case "${1:-}" in
83
+ -h|--help)
84
+ usage
85
+ exit 0
86
+ ;;
87
+ --no-save)
88
+ SAVE_OUTPUT=false
89
+ ;;
90
+ esac
91
+
92
+ # --- Pre-flight checks ---
93
+
94
+ if [[ ! -f "aia_config.yml" ]]; then
95
+ echo "ERROR: aia_config.yml not found."
96
+ echo " Run 00_setup_aia.sh first."
97
+ exit 1
98
+ fi
99
+
100
+ if ! command -v aia &>/dev/null; then
101
+ echo "ERROR: aia is not installed."
102
+ exit 1
103
+ fi
104
+
105
+ # --- Scripts to run ---
106
+ # Order matters: numbered scripts build on each other conceptually.
107
+ # Scripts are listed explicitly so we can annotate skips.
108
+
109
+ SCRIPTS=(
110
+ 01_basic_usage.sh
111
+ 02_frontmatter.sh
112
+ 03_shell_integration.sh
113
+ 04_erb_templating.sh
114
+ 05_shell_then_erb.sh
115
+ 06_prompt_chaining.sh
116
+ 07_pipeline.sh
117
+ 08_context_files.sh
118
+ 09_roles.sh
119
+ 10_stdin_piping.sh
120
+ 11_multi_model.sh
121
+ 12_token_usage.sh
122
+ 13_cost_tracking.sh
123
+ 14_output_file.sh
124
+ # 15_parameters.sh — skipped: Part 2 blocks on interactive input
125
+ 16_directives.sh
126
+ 17_require_and_conditionals.sh
127
+ 18_tools.sh
128
+ 19_local_tools.sh
129
+ # 20_mcp_servers.sh — skipped: requires npx + MCP server
130
+ 21_executable_prompts.sh
131
+ # 22_chat_mode.sh — skipped: interactive chat session
132
+ )
133
+
134
+ SKIPPED=(
135
+ "15_parameters.sh (interactive parameter input)"
136
+ "20_mcp_servers.sh (requires Node.js/npx + MCP server)"
137
+ "22_chat_mode.sh (interactive chat session)"
138
+ )
139
+
140
+ # --- Set up output ---
141
+
142
+ TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
143
+ OUTPUT_DIR="${SCRIPT_DIR}/output"
144
+ OUTPUT_FILE="${OUTPUT_DIR}/run_${TIMESTAMP}.log"
145
+
146
+ if [[ "${SAVE_OUTPUT}" == true ]]; then
147
+ mkdir -p "${OUTPUT_DIR}"
148
+ fi
149
+
150
+ # --- Banner ---
151
+
152
+ banner() {
153
+ cat <<EOF
154
+ ================================================================================
155
+ AIA Examples — Full Run
156
+ Date: $(date '+%Y-%m-%d %H:%M:%S')
157
+ AIA: $(aia --version 2>/dev/null || echo "unknown")
158
+ Ruby: $(ruby --version 2>/dev/null | head -1)
159
+ Model: $(grep 'name:' aia_config.yml | head -1 | sed 's/.*name: *//')
160
+ ================================================================================
161
+
162
+ Skipped scripts:
163
+ $(printf ' - %s\n' "${SKIPPED[@]}")
164
+
165
+ Running ${#SCRIPTS[@]} scripts ...
166
+
167
+ EOF
168
+ }
169
+
170
+ # --- Run a single script ---
171
+
172
+ run_script() {
173
+ local script="$1"
174
+ local index="$2"
175
+ local total="$3"
176
+ local status
177
+
178
+ echo "──────────────────────────────────────────────────────────────"
179
+ echo " [${index}/${total}] ${script}"
180
+ echo "──────────────────────────────────────────────────────────────"
181
+ echo
182
+
183
+ if [[ ! -f "${script}" ]]; then
184
+ echo " SKIP: file not found"
185
+ echo
186
+ return 0
187
+ fi
188
+
189
+ bash "${script}" 2>&1
190
+ status=$?
191
+
192
+ echo
193
+ if [[ ${status} -eq 0 ]]; then
194
+ echo " ✓ ${script} completed (exit ${status})"
195
+ else
196
+ echo " ✗ ${script} FAILED (exit ${status})"
197
+ fi
198
+ echo
199
+
200
+ return ${status}
201
+ }
202
+
203
+ # --- Main ---
204
+
205
+ main() {
206
+ banner
207
+
208
+ local total=${#SCRIPTS[@]}
209
+ local passed=0
210
+ local failed=0
211
+ local failures=()
212
+
213
+ for i in "${!SCRIPTS[@]}"; do
214
+ local script="${SCRIPTS[$i]}"
215
+ local index=$((i + 1))
216
+
217
+ if run_script "${script}" "${index}" "${total}"; then
218
+ ((passed++))
219
+ else
220
+ ((failed++))
221
+ failures+=("${script}")
222
+ fi
223
+ done
224
+
225
+ echo "================================================================================"
226
+ echo " Summary"
227
+ echo "================================================================================"
228
+ echo
229
+ echo " Total: ${total}"
230
+ echo " Passed: ${passed}"
231
+ echo " Failed: ${failed}"
232
+ echo " Skipped: ${#SKIPPED[@]}"
233
+ echo
234
+
235
+ if [[ ${failed} -gt 0 ]]; then
236
+ echo " Failed scripts:"
237
+ printf ' - %s\n' "${failures[@]}"
238
+ echo
239
+ fi
240
+
241
+ if [[ "${SAVE_OUTPUT}" == true ]]; then
242
+ echo " Output saved to: ${OUTPUT_FILE}"
243
+ echo
244
+ fi
245
+
246
+ echo " To compare against a future run:"
247
+ echo " diff output/run_PREV.log output/run_LATEST.log"
248
+ echo
249
+ echo "================================================================================"
250
+
251
+ return ${failed}
252
+ }
253
+
254
+ # --- Execute ---
255
+
256
+ if [[ "${SAVE_OUTPUT}" == true ]]; then
257
+ main 2>&1 | tee "${OUTPUT_FILE}"
258
+ exit ${PIPESTATUS[0]}
259
+ else
260
+ main
261
+ fi
@@ -117,14 +117,16 @@ module AIA
117
117
 
118
118
  # Report failed models but continue with valid ones
119
119
  unless failed_models.empty?
120
- puts "\nFailed to initialize the following models:"
121
- failed_models.each { |failure| puts " - #{failure}" }
120
+ warn "\nFailed to initialize the following models:"
121
+ failed_models.each { |failure| warn " - #{failure}" }
122
+ logger = LoggerManager.aia_logger
123
+ failed_models.each { |failure| logger.error("Model initialization failed: #{failure}") }
122
124
  end
123
125
 
124
126
  # If no models initialized successfully, exit
125
127
  if valid_chats.empty?
126
- puts "\nNo valid models could be initialized. Exiting."
127
- puts "\nAvailable models can be listed with: bin/aia --help models"
128
+ warn "\nNo valid models could be initialized. Exiting."
129
+ warn "\nAvailable models can be listed with: bin/aia --help models"
128
130
  exit 1
129
131
  end
130
132
 
@@ -135,8 +137,7 @@ module AIA
135
137
 
136
138
  # Report successful models
137
139
  if failed_models.any?
138
- puts "\nSuccessfully initialized: #{@models.join(', ')}"
139
- puts
140
+ warn "\nSuccessfully initialized: #{@models.join(', ')}"
140
141
  end
141
142
 
142
143
  # Use the first chat to determine tool support (assuming all models have similar tool support)
@@ -222,7 +223,8 @@ module AIA
222
223
  mcp_connector = McpConnector.new
223
224
  tool_loader = ToolLoader.new(mcp_connector)
224
225
 
225
- @tools = tool_loader.load_tools_with_mcp
226
+ @tools = tool_loader.load_tools
227
+ ToolFilter.detect_conflicts
226
228
  @tools = ToolFilter.filter_allowed(@tools)
227
229
  @tools = ToolFilter.filter_rejected(@tools)
228
230
  @tools = ToolFilter.drop_duplicates(@tools)
@@ -17,35 +17,6 @@ module AIA
17
17
  return
18
18
  end
19
19
 
20
- logger.debug("Starting MCP connection via RubyLLM::MCP.establish_connection")
21
- LoggerManager.configure_mcp_logger
22
-
23
- start_time = Time.now
24
- RubyLLM::MCP.establish_connection
25
- elapsed = Time.now - start_time
26
-
27
- tool_count = RubyLLM::MCP.tools.size
28
- tools.concat(RubyLLM::MCP.tools)
29
-
30
- logger.info("MCP connection established", elapsed_seconds: elapsed.round(2), tool_count: tool_count)
31
- rescue StandardError => e
32
- logger.error("Failed to connect MCP clients", error_class: e.class.name, error_message: e.message)
33
- logger.debug("MCP connection error backtrace", backtrace: e.backtrace&.first(5))
34
- warn "Warning: Failed to connect MCP clients: #{e.message}"
35
- end
36
-
37
- # =========================================================================
38
- # SimpleFlow-based Parallel MCP Connection
39
- # =========================================================================
40
- # Uses fiber-based concurrency to connect to all MCP servers in parallel.
41
- # This reduces total connection time from sum(timeouts) to max(timeouts).
42
-
43
- def support_mcp_with_simple_flow(tools)
44
- if AIA.config.flags.no_mcp
45
- logger.debug("MCP processing bypassed via --no-mcp flag")
46
- return
47
- end
48
-
49
20
  if AIA.config.mcp_servers.nil? || AIA.config.mcp_servers.empty?
50
21
  logger.debug("No MCP servers configured, skipping MCP setup")
51
22
  return
@@ -1,6 +1,8 @@
1
1
  # lib/aia/adapter/modality_handlers.rb
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'tempfile'
5
+
4
6
  module AIA
5
7
  module Adapter
6
8
  module ModalityHandlers
@@ -11,18 +13,21 @@ module AIA
11
13
  end
12
14
 
13
15
  def speak(_text)
14
- output_file = "#{Time.now.to_i}.mp3"
15
-
16
16
  # NOTE: RubyLLM doesn't have a direct text-to-speech feature
17
17
  # This is a placeholder for a custom implementation or external service
18
+ tmpfile = Tempfile.new(['aia-tts-', '.mp3'])
18
19
  begin
19
- File.write(output_file, 'Mock TTS audio content')
20
- if File.exist?(output_file) && system("which #{AIA.config.audio.speak_command} > /dev/null 2>&1")
21
- system("#{AIA.config.audio.speak_command} #{output_file}")
20
+ tmpfile.write('Mock TTS audio content')
21
+ tmpfile.close
22
+ speak_cmd = AIA.config.audio.speak_command
23
+ if system('which', speak_cmd, out: File::NULL, err: File::NULL)
24
+ system(speak_cmd, tmpfile.path)
22
25
  end
23
- "Audio generated and saved to: #{output_file}"
26
+ "Audio generated successfully"
24
27
  rescue StandardError => e
25
28
  "Error generating audio: #{e.message}"
29
+ ensure
30
+ tmpfile.unlink
26
31
  end
27
32
  end
28
33
 
@@ -55,8 +60,7 @@ module AIA
55
60
 
56
61
  # Return the full response object to preserve token information
57
62
  response
58
- rescue Exception => e # rubocop:disable Lint/RescueException
59
- # Catch ALL exceptions including LoadError, ScriptError, etc.
63
+ rescue StandardError => e
60
64
  # Tool crashes should not crash AIA - log and continue gracefully
61
65
  handle_tool_crash(chat_instance, e)
62
66
  end
@@ -114,18 +118,22 @@ module AIA
114
118
 
115
119
  def text_to_audio_single(prompt, model_name)
116
120
  text_prompt = extract_text_prompt(prompt)
117
- output_file = "#{Time.now.to_i}.mp3"
118
121
 
122
+ # NOTE: RubyLLM doesn't have a direct TTS feature
123
+ # TODO: This is a placeholder for a custom implementation
124
+ tmpfile = Tempfile.new(['aia-tts-', '.mp3'])
119
125
  begin
120
- # NOTE: RubyLLM doesn't have a direct TTS feature
121
- # TODO: This is a placeholder for a custom implementation
122
- File.write(output_file, text_prompt)
123
- if File.exist?(output_file) && system("which #{AIA.config.audio.speak_command} > /dev/null 2>&1")
124
- system("#{AIA.config.audio.speak_command} #{output_file}")
126
+ tmpfile.write(text_prompt)
127
+ tmpfile.close
128
+ speak_cmd = AIA.config.audio.speak_command
129
+ if system('which', speak_cmd, out: File::NULL, err: File::NULL)
130
+ system(speak_cmd, tmpfile.path)
125
131
  end
126
- "Audio generated and saved to: #{output_file}"
132
+ "Audio generated successfully"
127
133
  rescue StandardError => e
128
134
  "Error generating audio: #{e.message}"
135
+ ensure
136
+ tmpfile.unlink
129
137
  end
130
138
  end
131
139
 
@@ -30,6 +30,27 @@ module AIA
30
30
  end
31
31
  end
32
32
 
33
+ def self.detect_conflicts
34
+ allowed = AIA.config.tools.allowed
35
+ rejected = AIA.config.tools.rejected
36
+ return [] if allowed.nil? || allowed.empty? || rejected.nil? || rejected.empty?
37
+
38
+ allowed_list = Array(allowed).map(&:strip)
39
+ rejected_list = Array(rejected).map(&:strip)
40
+
41
+ conflicts = allowed_list & rejected_list
42
+ return [] if conflicts.empty?
43
+
44
+ logger = LoggerManager.aia_logger
45
+ conflicts.each do |pattern|
46
+ msg = "Tool pattern '#{pattern}' appears in both --allowed-tools and --rejected-tools. Rejected takes precedence."
47
+ logger.warn(msg)
48
+ warn "WARNING: #{msg}"
49
+ end
50
+
51
+ conflicts
52
+ end
53
+
33
54
  def self.drop_duplicates(tools)
34
55
  seen_names = Set.new
35
56
  original_size = tools.size
@@ -8,15 +8,7 @@ module AIA
8
8
  @mcp_connector = mcp_connector
9
9
  end
10
10
 
11
- def load_tools_with_mcp
12
- tools = []
13
-
14
- tools += scan_local_tools
15
- @mcp_connector.support_mcp_with_simple_flow(tools)
16
- tools
17
- end
18
-
19
- def load_tools_legacy
11
+ def load_tools
20
12
  tools = []
21
13
 
22
14
  tools += scan_local_tools