aia 0.9.11 → 0.9.12

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/CHANGELOG.md +66 -2
  4. data/README.md +133 -4
  5. data/docs/advanced-prompting.md +721 -0
  6. data/docs/cli-reference.md +582 -0
  7. data/docs/configuration.md +347 -0
  8. data/docs/contributing.md +332 -0
  9. data/docs/directives-reference.md +490 -0
  10. data/docs/examples/index.md +277 -0
  11. data/docs/examples/mcp/index.md +479 -0
  12. data/docs/examples/prompts/analysis/index.md +78 -0
  13. data/docs/examples/prompts/automation/index.md +108 -0
  14. data/docs/examples/prompts/development/index.md +125 -0
  15. data/docs/examples/prompts/index.md +333 -0
  16. data/docs/examples/prompts/learning/index.md +127 -0
  17. data/docs/examples/prompts/writing/index.md +62 -0
  18. data/docs/examples/tools/index.md +292 -0
  19. data/docs/faq.md +414 -0
  20. data/docs/guides/available-models.md +366 -0
  21. data/docs/guides/basic-usage.md +477 -0
  22. data/docs/guides/chat.md +474 -0
  23. data/docs/guides/executable-prompts.md +417 -0
  24. data/docs/guides/first-prompt.md +454 -0
  25. data/docs/guides/getting-started.md +455 -0
  26. data/docs/guides/image-generation.md +507 -0
  27. data/docs/guides/index.md +46 -0
  28. data/docs/guides/models.md +507 -0
  29. data/docs/guides/tools.md +856 -0
  30. data/docs/index.md +173 -0
  31. data/docs/installation.md +238 -0
  32. data/docs/mcp-integration.md +612 -0
  33. data/docs/prompt_management.md +579 -0
  34. data/docs/security.md +629 -0
  35. data/docs/tools-and-mcp-examples.md +1186 -0
  36. data/docs/workflows-and-pipelines.md +563 -0
  37. data/examples/tools/mcp/github_mcp_server.json +11 -0
  38. data/examples/tools/mcp/imcp.json +7 -0
  39. data/lib/aia/chat_processor_service.rb +19 -3
  40. data/lib/aia/config/base.rb +224 -0
  41. data/lib/aia/config/cli_parser.rb +409 -0
  42. data/lib/aia/config/defaults.rb +88 -0
  43. data/lib/aia/config/file_loader.rb +131 -0
  44. data/lib/aia/config/validator.rb +184 -0
  45. data/lib/aia/config.rb +10 -860
  46. data/lib/aia/directive_processor.rb +27 -372
  47. data/lib/aia/directives/configuration.rb +114 -0
  48. data/lib/aia/directives/execution.rb +37 -0
  49. data/lib/aia/directives/models.rb +178 -0
  50. data/lib/aia/directives/registry.rb +120 -0
  51. data/lib/aia/directives/utility.rb +70 -0
  52. data/lib/aia/directives/web_and_file.rb +71 -0
  53. data/lib/aia/prompt_handler.rb +23 -3
  54. data/lib/aia/ruby_llm_adapter.rb +307 -128
  55. data/lib/aia/session.rb +27 -14
  56. data/lib/aia/utility.rb +12 -8
  57. data/lib/aia.rb +11 -2
  58. data/lib/extensions/ruby_llm/.irbrc +56 -0
  59. data/mkdocs.yml +165 -0
  60. metadata +77 -20
  61. /data/{images → docs/assets/images}/aia.png +0 -0
@@ -0,0 +1,88 @@
1
+ # lib/aia/config/defaults.rb
2
+
3
+ require 'yaml'
4
+ require 'toml-rb'
5
+ require 'date'
6
+ require 'prompt_manager'
7
+
8
+ module AIA
9
+ module ConfigModules
10
+ module Defaults
11
+ DEFAULT_CONFIG = OpenStruct.new({
12
+ adapter: 'ruby_llm', # 'ruby_llm' or ???
13
+ #
14
+ aia_dir: File.join(ENV['HOME'], '.aia'),
15
+ config_file: File.join(ENV['HOME'], '.aia', 'config.yml'),
16
+ out_file: 'temp.md',
17
+ log_file: File.join(ENV['HOME'], '.prompts', '_prompts.log'),
18
+ context_files: [],
19
+ #
20
+ prompts_dir: File.join(ENV['HOME'], '.prompts'),
21
+ prompt_extname: PromptManager::Storage::FileSystemAdapter::PROMPT_EXTENSION,
22
+ #
23
+ roles_prefix: 'roles',
24
+ roles_dir: File.join(ENV['HOME'], '.prompts', 'roles'),
25
+ role: '',
26
+
27
+ #
28
+ system_prompt: '',
29
+
30
+ # Tools
31
+ tools: '', # Comma-separated string of loaded tool names (set by adapter)
32
+ allowed_tools: nil, # nil means all tools are allowed; otherwise an Array of Strings which are the tool names
33
+ rejected_tools: nil, # nil means no tools are rejected
34
+ tool_paths: [], # Strings - absolute and relative to tools
35
+
36
+ # Flags
37
+ markdown: true,
38
+ shell: true,
39
+ erb: true,
40
+ chat: false,
41
+ clear: false,
42
+ terse: false,
43
+ verbose: false,
44
+ debug: $DEBUG_ME,
45
+ fuzzy: false,
46
+ speak: false,
47
+ append: false, # Default to not append to existing out_file
48
+
49
+ # workflow
50
+ pipeline: [],
51
+
52
+ # PromptManager::Prompt Tailoring
53
+ parameter_regex: PromptManager::Prompt.parameter_regex.to_s,
54
+
55
+ # LLM tuning parameters
56
+ temperature: 0.7,
57
+ max_tokens: 2048,
58
+ top_p: 1.0,
59
+ frequency_penalty: 0.0,
60
+ presence_penalty: 0.0,
61
+
62
+ # Audio Parameters
63
+ voice: 'alloy',
64
+ speak_command: 'afplay', # 'afplay' for audio files on MacOS
65
+
66
+ # Image Parameters
67
+ image_size: '1024x1024',
68
+ image_quality: 'standard',
69
+ image_style: 'vivid',
70
+
71
+ # Models
72
+ model: ['gpt-4o-mini'],
73
+ consensus: nil, # nil/false = individual responses; true = consensus response
74
+ speech_model: 'tts-1',
75
+ transcription_model: 'whisper-1',
76
+ embedding_model: 'text-embedding-ada-002',
77
+ image_model: 'dall-e-3',
78
+
79
+ # Model Regristery
80
+ refresh: 7, # days between refreshes of model info; 0 means every startup
81
+ last_refresh: Date.today - 1,
82
+
83
+ # Ruby libraries to require for Ruby binding
84
+ require_libs: [],
85
+ }).freeze
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,131 @@
1
+ # lib/aia/config/file_loader.rb
2
+
3
+ require 'yaml'
4
+ require 'toml-rb'
5
+ require 'erb'
6
+ require 'date'
7
+
8
+ module AIA
9
+ module ConfigModules
10
+ module FileLoader
11
+ class << self
12
+ def load_config_file(file, config)
13
+ if File.exist?(file)
14
+ ext = File.extname(file).downcase
15
+ content = File.read(file)
16
+
17
+ # Process ERB if filename ends with .erb
18
+ if file.end_with?('.erb')
19
+ content = ERB.new(content).result
20
+ file = file.chomp('.erb')
21
+ File.write(file, content)
22
+ end
23
+
24
+ file_config = case ext
25
+ when '.yml', '.yaml'
26
+ YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
27
+ when '.toml'
28
+ TomlRB.parse(content)
29
+ else
30
+ raise "Unsupported config file format: #{ext}"
31
+ end
32
+
33
+ file_config.each do |key, value|
34
+ config[key.to_sym] = value
35
+ end
36
+ else
37
+ raise "Config file not found: #{file}"
38
+ end
39
+ end
40
+
41
+ def cf_options(file)
42
+ config = OpenStruct.new
43
+
44
+ if File.exist?(file)
45
+ content = read_and_process_config_file(file)
46
+ file_config = parse_config_content(content, File.extname(file).downcase)
47
+ apply_file_config_to_struct(config, file_config)
48
+ else
49
+ STDERR.puts "WARNING:Config file not found: #{file}"
50
+ end
51
+
52
+ normalize_last_refresh_date(config)
53
+ config
54
+ end
55
+
56
+ def read_and_process_config_file(file)
57
+ content = File.read(file)
58
+
59
+ # Process ERB if filename ends with .erb
60
+ if file.end_with?('.erb')
61
+ content = ERB.new(content).result
62
+ processed_file = file.chomp('.erb')
63
+ File.write(processed_file, content)
64
+ end
65
+
66
+ content
67
+ end
68
+
69
+ def parse_config_content(content, ext)
70
+ case ext
71
+ when '.yml', '.yaml'
72
+ YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true)
73
+ when '.toml'
74
+ TomlRB.parse(content)
75
+ else
76
+ raise "Unsupported config file format: #{ext}"
77
+ end
78
+ end
79
+
80
+ def apply_file_config_to_struct(config, file_config)
81
+ file_config.each do |key, value|
82
+ config[key] = value
83
+ end
84
+ end
85
+
86
+ def normalize_last_refresh_date(config)
87
+ return unless config.last_refresh&.is_a?(String)
88
+
89
+ config.last_refresh = Date.strptime(config.last_refresh, '%Y-%m-%d')
90
+ end
91
+
92
+ def dump_config(config, file)
93
+ # Implementation for config dump
94
+ ext = File.extname(file).downcase
95
+
96
+ config.last_refresh = config.last_refresh.to_s if config.last_refresh.is_a? Date
97
+
98
+ config_hash = config.to_h
99
+
100
+ # Remove prompt_id to prevent automatic initial pompting in --chat mode
101
+ config_hash.delete(:prompt_id)
102
+
103
+ # Remove dump_file key to prevent automatic exit on next load
104
+ config_hash.delete(:dump_file)
105
+
106
+ content = case ext
107
+ when '.yml', '.yaml'
108
+ YAML.dump(config_hash)
109
+ when '.toml'
110
+ TomlRB.dump(config_hash)
111
+ else
112
+ raise "Unsupported config file format: #{ext}"
113
+ end
114
+
115
+ File.write(file, content)
116
+ puts "Config successfully dumped to #{file}"
117
+ end
118
+
119
+ def generate_completion_script(shell)
120
+ script_path = File.join(File.dirname(__FILE__), "../../aia_completion.#{shell}")
121
+
122
+ if File.exist?(script_path)
123
+ puts File.read(script_path)
124
+ else
125
+ STDERR.puts "ERROR: The shell '#{shell}' is not supported or the completion script is missing."
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,184 @@
1
+ # lib/aia/config/validator.rb
2
+
3
+ require 'ostruct'
4
+
5
+ module AIA
6
+ module ConfigModules
7
+ module Validator
8
+ class << self
9
+ def tailor_the_config(config)
10
+ remaining_args = config.remaining_args.dup
11
+ config.remaining_args = nil
12
+
13
+ stdin_content = process_stdin_content
14
+ config.stdin_content = stdin_content if stdin_content && !stdin_content.strip.empty?
15
+
16
+ process_prompt_id_from_args(config, remaining_args)
17
+ validate_and_set_context_files(config, remaining_args)
18
+ handle_executable_prompt(config)
19
+ validate_required_prompt_id(config)
20
+ process_role_configuration(config)
21
+ handle_fuzzy_search_prompt_id(config)
22
+ normalize_boolean_flags(config)
23
+ handle_completion_script(config)
24
+ validate_final_prompt_requirements(config)
25
+ configure_prompt_manager(config)
26
+ prepare_pipeline(config)
27
+ validate_pipeline_prompts(config)
28
+
29
+ config
30
+ end
31
+
32
+ def process_stdin_content
33
+ stdin_content = ''
34
+
35
+ if !STDIN.tty? && !STDIN.closed?
36
+ begin
37
+ stdin_content << "\n" + STDIN.read
38
+ STDIN.reopen('/dev/tty') # Reopen STDIN for interactive use
39
+ rescue => _
40
+ # If we can't reopen, continue without error
41
+ end
42
+ end
43
+
44
+ stdin_content
45
+ end
46
+
47
+ def process_prompt_id_from_args(config, remaining_args)
48
+ return if remaining_args.empty?
49
+
50
+ maybe_id = remaining_args.first
51
+ maybe_id_plus = File.join(config.prompts_dir, maybe_id + config.prompt_extname)
52
+
53
+ if AIA.bad_file?(maybe_id) && AIA.good_file?(maybe_id_plus)
54
+ config.prompt_id = remaining_args.shift
55
+ end
56
+ end
57
+
58
+ def validate_and_set_context_files(config, remaining_args)
59
+ return if remaining_args.empty?
60
+
61
+ bad_files = remaining_args.reject { |filename| AIA.good_file?(filename) }
62
+ if bad_files.any?
63
+ STDERR.puts "Error: The following files do not exist: #{bad_files.join(', ')}"
64
+ exit 1
65
+ end
66
+
67
+ config.context_files ||= []
68
+ config.context_files += remaining_args
69
+ end
70
+
71
+ def handle_executable_prompt(config)
72
+ return unless config.executable_prompt && config.context_files && !config.context_files.empty?
73
+
74
+ config.executable_prompt_file = config.context_files.pop
75
+ end
76
+
77
+ def validate_required_prompt_id(config)
78
+ return unless config.prompt_id.nil? && !config.chat && !config.fuzzy
79
+
80
+ STDERR.puts "Error: A prompt ID is required unless using --chat, --fuzzy, or providing context files. Use -h or --help for help."
81
+ exit 1
82
+ end
83
+
84
+ def process_role_configuration(config)
85
+ return if config.role.empty?
86
+
87
+ unless config.roles_prefix.empty?
88
+ unless config.role.start_with?(config.roles_prefix)
89
+ config.role.prepend "#{config.roles_prefix}/"
90
+ end
91
+ end
92
+
93
+ config.roles_dir ||= File.join(config.prompts_dir, config.roles_prefix)
94
+
95
+ if config.prompt_id.nil? || config.prompt_id.empty?
96
+ if !config.role.nil? && !config.role.empty?
97
+ config.prompt_id = config.role
98
+ config.pipeline.prepend config.prompt_id
99
+ config.role = ''
100
+ end
101
+ end
102
+ end
103
+
104
+ def handle_fuzzy_search_prompt_id(config)
105
+ return unless config.fuzzy && config.prompt_id.empty?
106
+
107
+ # When fuzzy search is enabled but no prompt ID is provided,
108
+ # set a special value to trigger fuzzy search without an initial query
109
+ # SMELL: This feels like a cludge
110
+ config.prompt_id = '__FUZZY_SEARCH__'
111
+ end
112
+
113
+ def normalize_boolean_flags(config)
114
+ normalize_boolean_flag(config, :chat)
115
+ normalize_boolean_flag(config, :fuzzy)
116
+ normalize_boolean_flag(config, :consensus)
117
+ end
118
+
119
+ def normalize_boolean_flag(config, flag)
120
+ return if [TrueClass, FalseClass].include?(config[flag].class)
121
+
122
+ config[flag] = case config[flag]
123
+ when nil, '', 'false', false
124
+ false
125
+ when 'true', true
126
+ true
127
+ else
128
+ # For any other non-empty string value, treat as true
129
+ true
130
+ end
131
+ end
132
+
133
+ def handle_completion_script(config)
134
+ return unless config.completion
135
+
136
+ FileLoader.generate_completion_script(config.completion)
137
+ exit
138
+ end
139
+
140
+ def validate_final_prompt_requirements(config)
141
+ # Only require a prompt_id if we're not in chat mode, not using fuzzy search, and no context files
142
+ if !config.chat && !config.fuzzy && (config.prompt_id.nil? || config.prompt_id.empty?) && (!config.context_files || config.context_files.empty?)
143
+ STDERR.puts "Error: A prompt ID is required unless using --chat, --fuzzy, or providing context files. Use -h or --help for help."
144
+ exit 1
145
+ end
146
+
147
+ # If we're in chat mode with context files but no prompt_id, that's valid
148
+ # This is handled implicitly - no action needed
149
+ end
150
+
151
+ def configure_prompt_manager(config)
152
+ return unless config.parameter_regex
153
+
154
+ PromptManager::Prompt.parameter_regex = Regexp.new(config.parameter_regex)
155
+ end
156
+
157
+ def prepare_pipeline(config)
158
+ return if config.prompt_id.nil? || config.prompt_id.empty? || config.prompt_id == config.pipeline.first
159
+
160
+ config.pipeline.prepend config.prompt_id
161
+ end
162
+
163
+ def validate_pipeline_prompts(config)
164
+ return if config.pipeline.empty?
165
+
166
+ and_exit = false
167
+
168
+ config.pipeline.each do |prompt_id|
169
+ # Skip empty prompt IDs (can happen in chat-only mode)
170
+ next if prompt_id.nil? || prompt_id.empty?
171
+
172
+ prompt_file_path = File.join(config.prompts_dir, "#{prompt_id}.txt")
173
+ unless File.exist?(prompt_file_path)
174
+ STDERR.puts "Error: Prompt ID '#{prompt_id}' does not exist at #{prompt_file_path}"
175
+ and_exit = true
176
+ end
177
+ end
178
+
179
+ exit(1) if and_exit
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end