aia 0.9.23 → 0.10.2

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/CHANGELOG.md +95 -3
  4. data/README.md +187 -60
  5. data/bin/aia +6 -0
  6. data/docs/cli-reference.md +145 -72
  7. data/docs/configuration.md +156 -19
  8. data/docs/directives-reference.md +28 -8
  9. data/docs/examples/tools/index.md +2 -2
  10. data/docs/faq.md +11 -11
  11. data/docs/guides/available-models.md +11 -11
  12. data/docs/guides/basic-usage.md +18 -17
  13. data/docs/guides/chat.md +57 -11
  14. data/docs/guides/executable-prompts.md +15 -15
  15. data/docs/guides/first-prompt.md +2 -2
  16. data/docs/guides/getting-started.md +6 -6
  17. data/docs/guides/image-generation.md +24 -24
  18. data/docs/guides/local-models.md +2 -2
  19. data/docs/guides/models.md +96 -18
  20. data/docs/guides/tools.md +4 -4
  21. data/docs/index.md +2 -2
  22. data/docs/installation.md +2 -2
  23. data/docs/prompt_management.md +11 -11
  24. data/docs/security.md +3 -3
  25. data/docs/workflows-and-pipelines.md +1 -1
  26. data/examples/README.md +6 -6
  27. data/examples/headlines +3 -3
  28. data/lib/aia/aia_completion.bash +2 -2
  29. data/lib/aia/aia_completion.fish +4 -4
  30. data/lib/aia/aia_completion.zsh +2 -2
  31. data/lib/aia/chat_processor_service.rb +31 -21
  32. data/lib/aia/config/cli_parser.rb +403 -403
  33. data/lib/aia/config/config_section.rb +87 -0
  34. data/lib/aia/config/defaults.yml +219 -0
  35. data/lib/aia/config/defaults_loader.rb +147 -0
  36. data/lib/aia/config/mcp_parser.rb +151 -0
  37. data/lib/aia/config/model_spec.rb +67 -0
  38. data/lib/aia/config/validator.rb +185 -136
  39. data/lib/aia/config.rb +336 -17
  40. data/lib/aia/directive_processor.rb +14 -6
  41. data/lib/aia/directives/checkpoint.rb +283 -0
  42. data/lib/aia/directives/configuration.rb +27 -98
  43. data/lib/aia/directives/models.rb +15 -9
  44. data/lib/aia/directives/registry.rb +2 -0
  45. data/lib/aia/directives/utility.rb +25 -9
  46. data/lib/aia/directives/web_and_file.rb +50 -47
  47. data/lib/aia/logger.rb +328 -0
  48. data/lib/aia/prompt_handler.rb +18 -22
  49. data/lib/aia/ruby_llm_adapter.rb +584 -65
  50. data/lib/aia/session.rb +49 -156
  51. data/lib/aia/topic_context.rb +125 -0
  52. data/lib/aia/ui_presenter.rb +20 -16
  53. data/lib/aia/utility.rb +50 -18
  54. data/lib/aia.rb +91 -66
  55. data/lib/extensions/ruby_llm/modalities.rb +2 -0
  56. data/mcp_servers/apple-mcp.json +8 -0
  57. data/mcp_servers/mcp_server_chart.json +11 -0
  58. data/mcp_servers/playwright_one.json +8 -0
  59. data/mcp_servers/playwright_two.json +8 -0
  60. data/mcp_servers/tavily_mcp_server.json +8 -0
  61. metadata +85 -26
  62. data/lib/aia/config/base.rb +0 -288
  63. data/lib/aia/config/defaults.rb +0 -91
  64. data/lib/aia/config/file_loader.rb +0 -163
  65. data/lib/aia/context_manager.rb +0 -134
  66. data/mcp_servers/imcp.json +0 -7
  67. data/mcp_servers/launcher.json +0 -11
  68. data/mcp_servers/timeserver.json +0 -8
@@ -0,0 +1,283 @@
1
+ # lib/aia/directives/checkpoint.rb
2
+ #
3
+ # Checkpoint and restore directives for managing conversation state.
4
+ # Uses RubyLLM's Chat.@messages as the source of truth for conversation history.
5
+ #
6
+
7
+ module AIA
8
+ module Directives
9
+ module Checkpoint
10
+ # Module-level state for checkpoints
11
+ # Note: Using @checkpoint_store to avoid naming conflict with //checkpoints directive
12
+ @checkpoint_store = {}
13
+ @checkpoint_counter = 0
14
+ @last_checkpoint_name = nil
15
+
16
+ class << self
17
+ attr_accessor :checkpoint_store, :checkpoint_counter, :last_checkpoint_name
18
+
19
+ # Reset all checkpoint state (useful for testing)
20
+ def reset!
21
+ @checkpoint_store = {}
22
+ @checkpoint_counter = 0
23
+ @last_checkpoint_name = nil
24
+ end
25
+ end
26
+
27
+ # //checkpoint [name]
28
+ # Creates a named checkpoint of the current conversation state.
29
+ # If no name is provided, uses an auto-incrementing number.
30
+ def self.checkpoint(args, _unused = nil)
31
+ name = args.empty? ? nil : args.join(' ').strip
32
+
33
+ if name.nil? || name.empty?
34
+ self.checkpoint_counter += 1
35
+ name = checkpoint_counter.to_s
36
+ end
37
+
38
+ chats = get_chats
39
+ return "Error: No active chat sessions found." if chats.nil? || chats.empty?
40
+
41
+ # Deep copy messages from all chats
42
+ first_chat_messages = chats.values.first&.messages || []
43
+ checkpoint_store[name] = {
44
+ messages: chats.transform_values { |chat|
45
+ chat.messages.map { |msg| deep_copy_message(msg) }
46
+ },
47
+ position: first_chat_messages.size,
48
+ created_at: Time.now,
49
+ topic_preview: extract_last_user_message(first_chat_messages)
50
+ }
51
+ self.last_checkpoint_name = name
52
+
53
+ puts "Checkpoint '#{name}' created at position #{checkpoint_store[name][:position]}."
54
+ ""
55
+ end
56
+
57
+ # //restore [name]
58
+ # Restores the conversation state to a previously saved checkpoint.
59
+ # If no name is provided, restores to the previous checkpoint (one step back).
60
+ def self.restore(args, _unused = nil)
61
+ name = args.empty? ? nil : args.join(' ').strip
62
+
63
+ if name.nil? || name.empty?
64
+ # Find the previous checkpoint (second-to-last by position)
65
+ name = find_previous_checkpoint
66
+ if name.nil?
67
+ return "Error: No previous checkpoint to restore to."
68
+ end
69
+ end
70
+
71
+ unless checkpoint_store.key?(name)
72
+ available = checkpoint_names.empty? ? "none" : checkpoint_names.join(', ')
73
+ return "Error: Checkpoint '#{name}' not found. Available: #{available}"
74
+ end
75
+
76
+ checkpoint_data = checkpoint_store[name]
77
+ chats = get_chats
78
+
79
+ return "Error: No active chat sessions found." if chats.nil? || chats.empty?
80
+
81
+ # Restore messages to each chat
82
+ checkpoint_data[:messages].each do |model_id, saved_messages|
83
+ chat = chats[model_id]
84
+ next unless chat
85
+
86
+ # Replace the chat's messages with the saved ones
87
+ restored_messages = saved_messages.map { |msg| deep_copy_message(msg) }
88
+ chat.instance_variable_set(:@messages, restored_messages)
89
+ end
90
+
91
+ # Remove checkpoints that are now invalid (position > restored position)
92
+ # because they reference conversation states that no longer exist
93
+ restored_position = checkpoint_data[:position]
94
+ removed_count = remove_invalid_checkpoints(restored_position)
95
+
96
+ # Update last_checkpoint_name to this checkpoint
97
+ self.last_checkpoint_name = name
98
+
99
+ msg = "Context restored to checkpoint '#{name}' (position #{restored_position})."
100
+ msg += " Removed #{removed_count} checkpoint(s) that were beyond this position." if removed_count > 0
101
+ msg
102
+ end
103
+
104
+ # //clear
105
+ # Clears the conversation context, optionally keeping the system prompt.
106
+ def self.clear(args, _unused = nil)
107
+ keep_system = !args.include?('--all')
108
+
109
+ chats = get_chats
110
+ return "Error: No active chat sessions found." if chats.nil? || chats.empty?
111
+
112
+ chats.each do |_model_id, chat|
113
+ if keep_system
114
+ system_msg = chat.messages.find { |m| m.role == :system }
115
+ chat.instance_variable_set(:@messages, [])
116
+ chat.add_message(system_msg) if system_msg
117
+ else
118
+ chat.instance_variable_set(:@messages, [])
119
+ end
120
+ end
121
+
122
+ # Clear all checkpoints
123
+ checkpoint_store.clear
124
+ self.checkpoint_counter = 0
125
+ self.last_checkpoint_name = nil
126
+
127
+ "Chat context cleared."
128
+ end
129
+
130
+ # //review
131
+ # Displays the current conversation context with checkpoint markers.
132
+ def self.review(args, _unused = nil)
133
+ chats = get_chats
134
+ return "Error: No active chat sessions found." if chats.nil? || chats.empty?
135
+
136
+ # For multi-model, show first chat's messages (they should be similar for user messages)
137
+ first_chat = chats.values.first
138
+ messages = first_chat&.messages || []
139
+
140
+ puts "\n=== Chat Context (RubyLLM) ==="
141
+ puts "Total messages: #{messages.size}"
142
+ puts "Models: #{chats.keys.join(', ')}"
143
+ puts "Checkpoints: #{checkpoint_names.join(', ')}" if checkpoint_names.any?
144
+ puts
145
+
146
+ positions = checkpoint_positions
147
+
148
+ messages.each_with_index do |msg, index|
149
+ # Show checkpoint marker if one exists at this position
150
+ if positions[index]
151
+ puts "📍 [Checkpoint: #{positions[index].join(', ')}]"
152
+ puts "-" * 40
153
+ end
154
+
155
+ role = msg.role.to_s.capitalize
156
+ content = format_message_content(msg)
157
+
158
+ puts "#{index + 1}. [#{role}]: #{content}"
159
+ puts
160
+ end
161
+
162
+ # Check for checkpoint at the end
163
+ if positions[messages.size]
164
+ puts "📍 [Checkpoint: #{positions[messages.size].join(', ')}]"
165
+ puts "-" * 40
166
+ end
167
+
168
+ puts "=== End of Context ==="
169
+ ""
170
+ end
171
+
172
+ # //checkpoints
173
+ # Lists all available checkpoints with their details.
174
+ def self.checkpoints_list(args, _unused = nil)
175
+ if checkpoint_store.empty?
176
+ puts "No checkpoints available."
177
+ return ""
178
+ end
179
+
180
+ puts "\n=== Available Checkpoints ==="
181
+ checkpoint_store.each do |name, data|
182
+ created = data[:created_at]&.strftime('%H:%M:%S') || 'unknown'
183
+ puts " #{name}: position #{data[:position]}, created #{created}"
184
+ if data[:topic_preview] && !data[:topic_preview].empty?
185
+ puts " → \"#{data[:topic_preview]}\""
186
+ end
187
+ end
188
+ puts "=== End of Checkpoints ==="
189
+ ""
190
+ end
191
+
192
+ # Helper methods
193
+ def self.checkpoint_names
194
+ checkpoint_store.keys
195
+ end
196
+
197
+ def self.checkpoint_positions
198
+ positions = {}
199
+ checkpoint_store.each do |name, data|
200
+ pos = data[:position]
201
+ positions[pos] ||= []
202
+ positions[pos] << name
203
+ end
204
+ positions
205
+ end
206
+
207
+ # Remove checkpoints with position > the given position
208
+ # Returns the count of removed checkpoints
209
+ def self.remove_invalid_checkpoints(max_position)
210
+ invalid_names = checkpoint_store.select { |_name, data| data[:position] > max_position }.keys
211
+ invalid_names.each { |name| checkpoint_store.delete(name) }
212
+ invalid_names.size
213
+ end
214
+
215
+ # Find the previous checkpoint (second-to-last by position)
216
+ # This is used when //restore is called without a name
217
+ def self.find_previous_checkpoint
218
+ return nil if checkpoint_store.size < 2
219
+
220
+ # Sort checkpoints by position descending, return the second one
221
+ sorted = checkpoint_store.sort_by { |_name, data| -data[:position] }
222
+ sorted[1]&.first # Return the name of the second-to-last checkpoint
223
+ end
224
+
225
+ private
226
+
227
+ def self.get_chats
228
+ return nil unless AIA.client.respond_to?(:chats)
229
+
230
+ AIA.client.chats
231
+ end
232
+
233
+ def self.deep_copy_message(msg)
234
+ RubyLLM::Message.new(
235
+ role: msg.role,
236
+ content: msg.content,
237
+ tool_calls: msg.tool_calls&.transform_values { |tc| tc.dup rescue tc },
238
+ tool_call_id: msg.tool_call_id,
239
+ input_tokens: msg.input_tokens,
240
+ output_tokens: msg.output_tokens,
241
+ model_id: msg.model_id
242
+ )
243
+ end
244
+
245
+ def self.format_message_content(msg)
246
+ if msg.tool_call?
247
+ tool_names = msg.tool_calls.values.map(&:name).join(', ')
248
+ "[Tool calls: #{tool_names}]"
249
+ elsif msg.tool_result?
250
+ result_preview = msg.content.to_s[0..50]
251
+ "[Tool result for: #{msg.tool_call_id}] #{result_preview}..."
252
+ else
253
+ content = msg.content.to_s
254
+ content.length > 150 ? "#{content[0..147]}..." : content
255
+ end
256
+ end
257
+
258
+ # Extract a preview of the last user message for checkpoint context
259
+ def self.extract_last_user_message(messages, max_length: 70)
260
+ return "" if messages.nil? || messages.empty?
261
+
262
+ # Find the last user message (not system, not assistant, not tool)
263
+ last_user_msg = messages.reverse.find { |msg| msg.role == :user }
264
+ return "" unless last_user_msg
265
+
266
+ content = last_user_msg.content.to_s.strip
267
+ # Collapse whitespace and truncate
268
+ content = content.gsub(/\s+/, ' ')
269
+ content.length > max_length ? "#{content[0..max_length - 4]}..." : content
270
+ end
271
+
272
+ # Aliases
273
+ class << self
274
+ alias_method :ckp, :checkpoint
275
+ alias_method :cp, :checkpoint
276
+ alias_method :context, :review
277
+ # Route //checkpoints directive to checkpoints_list
278
+ # (safe now that we renamed attr_accessor to checkpoint_store)
279
+ alias_method :checkpoints, :checkpoints_list
280
+ end
281
+ end
282
+ end
283
+ end
@@ -7,12 +7,17 @@ module AIA
7
7
  args = Array(args)
8
8
 
9
9
  if args.empty?
10
- ap AIA.config
10
+ ap AIA.config.to_h
11
11
  ""
12
12
  elsif args.length == 1
13
13
  config_item = args.first
14
- local_cfg = Hash.new
15
- local_cfg[config_item] = AIA.config[config_item]
14
+ local_cfg = {}
15
+ # Use method-based access for Anyway::Config
16
+ if AIA.config.respond_to?(config_item)
17
+ local_cfg[config_item] = AIA.config.send(config_item)
18
+ else
19
+ local_cfg[config_item] = nil
20
+ end
16
21
  ap local_cfg
17
22
  ""
18
23
  else
@@ -24,7 +29,13 @@ module AIA
24
29
  new_value = %w[true t yes y on 1 yea yeah yep yup].include?(new_value.downcase)
25
30
  end
26
31
 
27
- AIA.config[config_item] = new_value
32
+ # Use method-based setter for Anyway::Config
33
+ setter = "#{config_item}="
34
+ if AIA.config.respond_to?(setter)
35
+ AIA.config.send(setter, new_value)
36
+ else
37
+ puts "Warning: Unknown config option '#{config_item}'"
38
+ end
28
39
  ""
29
40
  end
30
41
  end
@@ -33,29 +44,32 @@ module AIA
33
44
  if args.empty?
34
45
  # Display details for all configured models
35
46
  puts
36
- models = Array(AIA.config.model)
47
+ models = AIA.config.models
37
48
 
38
49
  if models.size == 1
39
50
  puts "Current Model:"
40
51
  puts "=============="
41
- puts AIA.config.client.model.to_h.pretty_inspect
52
+ puts AIA.client.model.to_h.pretty_inspect
42
53
  else
43
54
  puts "Multi-Model Configuration:"
44
55
  puts "=========================="
45
56
  puts "Model count: #{models.size}"
46
- puts "Primary model: #{models.first} (used for consensus when --consensus flag is enabled)"
47
- puts "Consensus mode: #{AIA.config.consensus.nil? ? 'auto-detect (disabled by default)' : AIA.config.consensus}"
57
+ first_model = models.first.respond_to?(:name) ? models.first.name : models.first.to_s
58
+ puts "Primary model: #{first_model} (used for consensus when --consensus flag is enabled)"
59
+ consensus = AIA.config.flags.consensus
60
+ puts "Consensus mode: #{consensus.nil? ? 'auto-detect (disabled by default)' : consensus}"
48
61
  puts
49
62
  puts "Model Details:"
50
63
  puts "-" * 50
51
64
 
52
- models.each_with_index do |model_name, index|
65
+ models.each_with_index do |model_spec, index|
66
+ model_name = model_spec.respond_to?(:name) ? model_spec.name : model_spec.to_s
53
67
  puts "#{index + 1}. #{model_name}#{index == 0 ? ' (primary)' : ''}"
54
68
 
55
69
  # Try to get model details if available
56
70
  begin
57
71
  # Access the model details from RubyLLM's model registry
58
- model_info = RubyLLM::Models.find(name: model_name)
72
+ model_info = RubyLLM::Models.find(model_name)
59
73
  if model_info
60
74
  puts " Provider: #{model_info.provider || 'Unknown'}"
61
75
  puts " Context window: #{model_info.context_window || 'Unknown'}"
@@ -88,100 +102,15 @@ module AIA
88
102
  send(:config, args.prepend('top_p'), context_manager)
89
103
  end
90
104
 
91
- def self.clear(args, context_manager = nil)
92
- if context_manager.nil?
93
- return "Error: Context manager not available for //clear directive."
94
- end
95
-
96
- context_manager.clear_context
97
- ''
98
- end
99
-
100
- def self.review(args, context_manager = nil)
101
- return "Error: Context manager not available for //review directive." if context_manager.nil?
102
-
103
- context = context_manager.get_context
104
- checkpoint_positions = context_manager.checkpoint_positions
105
-
106
- # Display context with checkpoint markers
107
- puts "\n=== Chat Context ==="
108
- puts "Total messages: #{context.size}"
109
-
110
- if checkpoint_positions.any?
111
- puts "Checkpoints: #{context_manager.checkpoint_names.join(', ')}"
112
- end
113
-
114
- puts "\n"
115
-
116
- context.each_with_index do |message, index|
117
- # Check if there's a checkpoint at this position
118
- if checkpoint_positions[index]
119
- checkpoint_names = checkpoint_positions[index].join(', ')
120
- puts "📍 [Checkpoint: #{checkpoint_names}]"
121
- puts "-" * 40
122
- end
123
-
124
- # Display the message
125
- role_display = message[:role].capitalize
126
- content_preview = message[:content].to_s
127
-
128
- # Truncate long content for display
129
- if content_preview.length > 200
130
- content_preview = content_preview[0..197] + "..."
131
- end
132
-
133
- puts "#{index + 1}. [#{role_display}]: #{content_preview}"
134
- puts ""
135
- end
136
-
137
- # Check if there's a checkpoint at the end (after all messages)
138
- if checkpoint_positions[context.size]
139
- checkpoint_names = checkpoint_positions[context.size].join(', ')
140
- puts "📍 [Checkpoint: #{checkpoint_names}]"
141
- puts "-" * 40
142
- end
143
-
144
- puts "=== End of Context ==="
145
- ''
146
- end
147
-
148
- def self.checkpoint(args, context_manager = nil)
149
- if context_manager.nil?
150
- return "Error: Context manager not available for //checkpoint directive."
151
- end
152
-
153
- name = args.empty? ? nil : args.join(' ').strip
154
- checkpoint_name = context_manager.create_checkpoint(name: name)
155
- puts "Checkpoint '#{checkpoint_name}' created."
156
- ""
157
- end
158
-
159
- def self.restore(args, context_manager = nil)
160
- if context_manager.nil?
161
- return "Error: Context manager not available for //restore directive."
162
- end
163
-
164
- name = args.empty? ? nil : args.join(' ').strip
165
-
166
- if context_manager.restore_checkpoint(name: name)
167
- restored_name = name || context_manager.checkpoint_names.last
168
- "Context restored to checkpoint '#{restored_name}'."
169
- else
170
- if name
171
- "Error: Checkpoint '#{name}' not found. Available checkpoints: #{context_manager.checkpoint_names.join(', ')}"
172
- else
173
- "Error: No checkpoints available to restore."
174
- end
175
- end
176
- end
105
+ # NOTE: clear, review, checkpoint, and restore directives have been moved to
106
+ # lib/aia/directives/checkpoint.rb which uses RubyLLM's Chat.@messages
107
+ # as the source of truth for conversation history.
177
108
 
178
109
  # Set up aliases - these work on the module's singleton class
179
110
  class << self
180
111
  alias_method :cfg, :config
181
112
  alias_method :temp, :temperature
182
113
  alias_method :topp, :top_p
183
- alias_method :context, :review
184
- alias_method :ckp, :checkpoint
185
114
  end
186
115
  end
187
116
  end
@@ -31,12 +31,11 @@ module AIA
31
31
 
32
32
  def self.available_models(args = nil, context_manager = nil)
33
33
  # Check if we're using a local provider
34
- current_models = AIA.config.model
35
- current_models = [current_models] if current_models.is_a?(String)
34
+ current_models = AIA.config.models
36
35
 
37
- # Extract model names (handles both String and Hash formats)
36
+ # Extract model names (handles ModelSpec objects)
38
37
  model_names = current_models.map do |m|
39
- m.is_a?(Hash) ? m[:model] : m
38
+ m.respond_to?(:name) ? m.name : m.to_s
40
39
  end
41
40
 
42
41
  using_local_provider = model_names.any? { |m| m.start_with?('ollama/', 'lms/') }
@@ -231,9 +230,10 @@ module AIA
231
230
  'review' => 'Display the current conversation context with checkpoint markers',
232
231
  'checkpoint' => 'Create a named checkpoint of the current context',
233
232
  'restore' => 'Restore context to a previous checkpoint',
233
+ 'checkpoints_list' => 'List all available checkpoints',
234
234
 
235
235
  # Utility directives
236
- 'tools' => 'List available tools',
236
+ 'tools' => 'List available tools (optional filter by name substring)',
237
237
  'next' => 'Set the next prompt in the sequence',
238
238
  'pipeline' => 'Set or view the prompt workflow sequence',
239
239
  'terse' => 'Add instruction for concise responses',
@@ -261,6 +261,7 @@ module AIA
261
261
  'temp' => nil, # alias for temperature
262
262
  'topp' => nil, # alias for top_p
263
263
  'context' => nil, # alias for review
264
+ 'ckp' => nil, # alias for checkpoint
264
265
  'cp' => nil, # alias for checkpoint
265
266
  'workflow' => nil, # alias for pipeline
266
267
  'rb' => nil, # alias for ruby
@@ -282,13 +283,18 @@ module AIA
282
283
  AIA::Directives::Utility,
283
284
  AIA::Directives::Configuration,
284
285
  AIA::Directives::Execution,
285
- AIA::Directives::Models
286
+ AIA::Directives::Models,
287
+ AIA::Directives::Checkpoint
286
288
  ]
287
289
 
288
290
  all_directives = {}
289
291
  excluded_methods = ['run', 'initialize', 'private?', 'descriptions', 'aliases', 'build_aliases',
290
292
  'desc', 'method_added', 'register_directive_module', 'process',
291
- 'directive?', 'prefix_size']
293
+ 'directive?', 'prefix_size', 'reset!', 'checkpoint_names',
294
+ 'checkpoint_positions', 'get_chats', 'deep_copy_message',
295
+ 'format_message_content', 'checkpoints', 'checkpoint_counter',
296
+ 'last_checkpoint_name', 'checkpoints=', 'checkpoint_counter=',
297
+ 'last_checkpoint_name=']
292
298
 
293
299
  # Collect directives from all modules
294
300
  all_modules.each do |mod|
@@ -314,7 +320,7 @@ module AIA
314
320
  'temperature' => ['temp'],
315
321
  'top_p' => ['topp'],
316
322
  'review' => ['context'],
317
- 'checkpoint' => ['cp'],
323
+ 'checkpoint' => ['ckp', 'cp'],
318
324
  'pipeline' => ['workflow'],
319
325
  'ruby' => ['rb'],
320
326
  'shell' => ['sh'],
@@ -333,7 +339,7 @@ module AIA
333
339
 
334
340
  # Sort and display directives by category
335
341
  categories = {
336
- 'Configuration' => ['config', 'model', 'temperature', 'top_p', 'clear', 'review', 'checkpoint', 'restore'],
342
+ 'Configuration' => ['config', 'model', 'temperature', 'top_p', 'clear', 'review', 'checkpoint', 'restore', 'checkpoints_list'],
337
343
  'Utility' => ['tools', 'next', 'pipeline', 'terse', 'robot', 'help'],
338
344
  'Execution' => ['ruby', 'shell', 'say'],
339
345
  'Web & Files' => ['webpage', 'include'],
@@ -5,6 +5,7 @@ require_relative 'utility'
5
5
  require_relative 'configuration'
6
6
  require_relative 'execution'
7
7
  require_relative 'models'
8
+ require_relative 'checkpoint'
8
9
 
9
10
  module AIA
10
11
  module Directives
@@ -115,6 +116,7 @@ module AIA
115
116
  register_directive_module(Configuration)
116
117
  register_directive_module(Execution)
117
118
  register_directive_module(Models)
119
+ register_directive_module(Checkpoint)
118
120
  end
119
121
  end
120
122
  end
@@ -12,19 +12,35 @@ module AIA
12
12
  indent = 4
13
13
  spaces = " " * indent
14
14
  width = TTY::Screen.width - indent - 2
15
+ filter = args.first&.downcase
15
16
 
16
- if AIA.config.tools.empty?
17
+ loaded_tools = AIA.config.loaded_tools || []
18
+ if loaded_tools.empty?
17
19
  puts "No tools are available"
18
20
  else
19
- puts
20
- puts "Available Tools"
21
- puts "==============="
21
+ tools_to_display = loaded_tools
22
22
 
23
- AIA.config.tools.each do |tool|
24
- name = tool.respond_to?(:name) ? tool.name : tool.class.name
25
- puts "\n#{name}"
26
- puts "-" * name.size
27
- puts WordWrapper::MinimumRaggedness.new(width, tool.description).wrap.split("\n").map { |s| spaces + s + "\n" }.join
23
+ if filter
24
+ tools_to_display = tools_to_display.select do |tool|
25
+ name = tool.respond_to?(:name) ? tool.name : tool.class.name
26
+ name.downcase.include?(filter)
27
+ end
28
+ end
29
+
30
+ if tools_to_display.empty?
31
+ puts "No tools match the filter: #{args.first}"
32
+ else
33
+ puts
34
+ header = filter ? "Available Tools (filtered by '#{args.first}')" : "Available Tools"
35
+ puts header
36
+ puts "=" * header.length
37
+
38
+ tools_to_display.each do |tool|
39
+ name = tool.respond_to?(:name) ? tool.name : tool.class.name
40
+ puts "\n#{name}"
41
+ puts "-" * name.size
42
+ puts WordWrapper::MinimumRaggedness.new(width, tool.description).wrap.split("\n").map { |s| spaces + s + "\n" }.join
43
+ end
28
44
  end
29
45
  end
30
46
  puts