ollama_chat 0.0.105 → 0.0.107

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbf0c7c0df08fa3ef8a15a9c62b7e092d035924c071302a4f37e01776dec2504
4
- data.tar.gz: '0996355cc3d0324936394a174aaeeee8b67b950d5306cf320fa22a70eef9ea0d'
3
+ metadata.gz: e2686defd34fe5cf4e0f61c980480578f6b2589bc429d7b724ed4f3cb478d7b8
4
+ data.tar.gz: 8bd9136e0e6f3e202c93f8c4aec0a4b482a447bfd1e29163b0dff1973e64f2fa
5
5
  SHA512:
6
- metadata.gz: 879adc039769558c938911909dbfdfc9625d6b4774cd398d8b39b4ebeb7055d7f7eb1fdaa1c26caf79d7fab3330c57c95b1169927e34d6909518e5f30f1c9d74
7
- data.tar.gz: 7fbe2ad8b0f887cab6d83ece7efe118fd0189e1957fdbd5a40725200729b7e4d93e9c018c1a41cc8fc933dd2cee453a3a7b9090a7e647422314175d567e5496c
6
+ metadata.gz: 1a177889fb72f197a05895b60c7e73f1493a119fcc7832354772097f121d53b150ea500fdf50ae81bdeb05161f0a3121af204496d54a708a13c427096e126159
7
+ data.tar.gz: 55c0270902a86e39e1704d62a4478daf0d04526312d47eb35f560b3b95218045d6699dd428b874fa9059ce3af8585bed97a5c2bd662751ef05885baa452a9c6b
data/CHANGES.md CHANGED
@@ -1,5 +1,90 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-08-09 v0.0.107
4
+
5
+ ### Added
6
+
7
+ - New `-e` option to `ollama_chat_send` to open input content in the configured
8
+ editor before sending.
9
+ - New `-F` option to `ollama_chat_send` to specify file extension for syntax
10
+ highlighting in the editor.
11
+ - Confirmation prompts to `write_file` when overwriting existing files in
12
+ `overwrite` mode or creating new files in `append` mode.
13
+ - Support for pluggable voice handler architecture via a new `handler` key in
14
+ the `voice` configuration.
15
+
16
+ ### Changed
17
+
18
+ - Refactored voice handling to use a dynamic `voice_handler` method in
19
+ `OllamaChat::Chat` to resolve configured classes (e.g., `OllamaChat::Say`).
20
+ - Externalized the suggestion prompt template by adding
21
+ `context_template_suggest` to `default_config.yml` under `prompts`, replacing
22
+ hardcoded heredocs in `prompt_management.rb`.
23
+ - Updated `StateSelectors` to delegate voice list retrieval via
24
+ `voice_handler.ask_and_send(:voices)`.
25
+ - Updated `FollowChat` to instantiate the resolved voice handler dynamically.
26
+ - Refactored `OllamaChat::Say.voices` to extract logic from
27
+ `default_config.yml` ERB and remove RSpec-specific branching.
28
+ - Updated `spec/ollama_chat/commands_spec.rb` to use explicit `.jsonl`
29
+ extensions for `/conversation save` and `/conversation load` tests.
30
+
31
+ ### Fixed
32
+
33
+ - Reopened `STDIN` from `/dev/tty` during editing to prevent editor warnings.
34
+ - Ensured `save_conversation` and `load_conversation` expectations match the
35
+ new file naming convention.
36
+
37
+ ### Security & Error Handling
38
+
39
+ - Raised `OllamaChat::ToolFunctionArgumentError` when a user declines a file
40
+ operation confirmation.
41
+ - Added `.to_s` call on `prompt` results to correctly handle `Sequel::Model`
42
+ return types.
43
+
44
+ ## 2026-08-06 v0.0.106
45
+
46
+ ### Added
47
+
48
+ - New `/model options delete` subcommand to allow removing saved profiles.
49
+ - Implementation of `delete_model_options_profile` in `model_handling.rb`.
50
+ - New `/prompt rename` subcommand for prompt templates.
51
+ - Implementation of `rename_prompt` method in `prompt_management.rb`.
52
+ - Support for shell-style quoting for file pattern arguments using
53
+ `Shellwords.split`.
54
+
55
+ ### Changed
56
+
57
+ - Tightened validation for the `/conversation` command to restrict filenames to
58
+ `.json` or `.jsonl` extensions.
59
+ - Updated `options` string documentation to include the optional `FILENAME`
60
+ argument and added format hints to `save` and `load` subcommands.
61
+ - Added a `suggest` parameter to `choose_profile_for_model` to allow specific
62
+ profiles to be inserted at the top of selection lists.
63
+ - Updated `copy_model_options_profile` to use the source profile as the
64
+ suggested destination.
65
+ - Refactored error logging in `OllamaChat::Chat` to use a standardized `log`
66
+ method with `:warn` level instead of direct `STDOUT.puts`.
67
+ - Centralized pattern parsing logic by replacing `arg.scan(/(\S+)/).flatten`
68
+ with `extract_patterns` in `commands.rb`.
69
+ - Updated context generation in `input_content.rb` to dynamically call
70
+ `to_json` or `to_toon` based on the selected format.
71
+
72
+ ### Fixed
73
+
74
+ - Resolved a scope limitation in `Prompt#after_destroy` by replacing
75
+ `models::Favourite` with `OllamaChat::Database::Models::Favourite`.
76
+ - Ensured the `default` profile remains valid by resetting it to an empty
77
+ options `{}` instead of deleting it during model option removal.
78
+
79
+ ### Refactored
80
+
81
+ - Improved readability in `model_handling.rb` by wrapping long `STDOUT.puts`
82
+ strings.
83
+ - Wrapped prompt operations in `switch_history(:prompt)` for improved
84
+ consistency.
85
+ - Updated YARD documentation for `extract_patterns` to reflect new shell
86
+ semantics.
87
+
3
88
  ## 2026-08-04 v0.0.105
4
89
 
5
90
  ### New Features
data/README.md CHANGED
@@ -344,6 +344,22 @@ functionality:
344
344
  $ echo "Hello world" | ollama_chat_send -d /tmp/my_working_dir -r
345
345
  ```
346
346
 
347
+ - **Edit Content (`-e`)**: Opens the input content in the configured editor
348
+ before sending. This is useful for refining the message or adding more
349
+ context.
350
+
351
+ ```bash
352
+ $ echo "Draft message..." | ollama_chat_send -e
353
+ ```
354
+
355
+ - **File Format (`-F`)**: When using edit mode, specify a file extension to
356
+ enable syntax highlighting in your editor (e.g., `rb` for Ruby, `md` for
357
+ Markdown).
358
+
359
+ ```bash
360
+ $ echo "def hello; end" | ollama_chat_send -e -F rb
361
+ ```
362
+
347
363
  - **Help (`-h` or `--help`)**: Displays usage information and available options.
348
364
 
349
365
  ```bash
data/bin/ollama_chat_send CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'ollama_chat'
4
+ include OllamaChat::FileEditing
4
5
  require 'tins/go'
5
6
  include Tins::GO
6
7
 
7
- opts = go 'f:d:rtph', ARGV
8
+ opts = go 'f:d:F:rteph', ARGV
8
9
 
9
10
  # Displays the usage information for the ollama_chat_send command.
10
11
  #
@@ -23,6 +24,8 @@ def usage(rc = 0)
23
24
  -p Send input with source parsing enabled (defaults to disabled)
24
25
  -f CONFIG file to read
25
26
  -d DIR the working directory to derive the socket file from
27
+ -e Edit content in editor before sending
28
+ -F FORMAT file extension for editor (e.g., rb, md)
26
29
  -h Show this help message
27
30
 
28
31
  Send data to a running Ollama Chat client via standard input.
@@ -38,8 +41,15 @@ begin
38
41
  else
39
42
  opts[?r] ? :socket_input_with_response : :socket_input
40
43
  end
44
+ content = STDIN.read
45
+ if opts[?e]
46
+ basename = %w[ text ]
47
+ basename << opts[?F]&.sub(/\A(?=[^.])/, ?.) || '.md'
48
+ STDIN.reopen('/dev/tty')
49
+ content = edit_text(content, basename:)
50
+ end
41
51
  response = OllamaChat::ServerSocket.send_to_server_socket(
42
- STDIN.read,
52
+ content,
43
53
  type:,
44
54
  config:,
45
55
  working_dir: opts[?d],
@@ -160,6 +160,21 @@ class OllamaChat::Chat
160
160
  # @return [OllamaChat::RedisCache, nil] the cache instance
161
161
  attr_reader :cache
162
162
 
163
+
164
+ # Returns the voice handler class based on the configuration.
165
+ #
166
+ # This method resolves the voice handler class specified in the config.
167
+ # If running within RSpec, it returns an empty array to avoid side effects.
168
+ #
169
+ # @return [Class, Array] the voice handler class or an empty array if in test mode
170
+ def voice_handler
171
+ defined?(RSpec) and return []
172
+ case config.voice.handler
173
+ when 'OllamaChat::Say'
174
+ OllamaChat::Say
175
+ end
176
+ end
177
+
163
178
  # The start method initializes the chat session by displaying information,
164
179
  # then prompts the user for input to begin interacting with the chat.
165
180
  def start
@@ -535,7 +550,8 @@ class OllamaChat::Chat
535
550
  store_messages_in_session
536
551
  rescue Ollama::Errors::BadRequestError
537
552
  if (think? || tools_support.on?) && !retried
538
- STDOUT.puts "#{bold('Error')}: in think mode/with tool support, switch both off and retry."
553
+ msg = "Error in think mode/with tool support, switch both off and retry."
554
+ log(:warn, msg, warn: true)
539
555
  sleep 1
540
556
  think_mode.selected = 'disabled'
541
557
  tools_support.set false
@@ -139,13 +139,14 @@ module OllamaChat::Commands
139
139
 
140
140
  command(
141
141
  name: :model,
142
- regexp: %r(^/model(?:\s+(change|options(?: copy)?|options from session|options to session))?((?:\s+(?:-m))*)$),
143
- complete: [ 'model', %w[ change options options\ copy options\ from\ session options\ to\ session ] ],
142
+ regexp: %r(^/model(?:\s+(change|options(?: copy| delete)?|options from session|options to session))?((?:\s+(?:-m))*)$),
143
+ complete: [ 'model', %w[ change options options\ copy options\ delete options\ from\ session options\ to\ session ] ],
144
144
  help: <<~EOT
145
145
  🤖 Manage AI models & profiles:
146
146
  - change: Switch active model
147
147
  - options: Edit saved profile config
148
148
  - options copy: Copy profile from another model
149
+ - options delete: Delete a saved profile
149
150
  - options from session: Save live → Saved
150
151
  - options to session: Apply Saved → Live
151
152
  -m interactively choose a model
@@ -170,6 +171,8 @@ module OllamaChat::Commands
170
171
  edit_model_options(model, profile:)
171
172
  when 'options copy'
172
173
  copy_model_options_profile(model)
174
+ when 'options delete'
175
+ delete_model_options_profile(model)
173
176
  when 'options from session'
174
177
  profile = choose_profile_for_model(model) || 'default'
175
178
  copy_model_options_from_session(model, profile:)
@@ -460,14 +463,14 @@ module OllamaChat::Commands
460
463
 
461
464
  command(
462
465
  name: :prompt,
463
- regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|-e))?(\s+(?:-[ef]|-c\s+(?:\w+|\?)))?(?:\s+([^-].*))?$),
464
- complete: [ 'prompt', %w[ edit info add delete list duplicate import export reset ] ],
466
+ regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|rename|-e))?(\s+(?:-[ef]|-c\s+(?:\w+|\?)))?(?:\s+([^-].*))?$),
467
+ complete: [ 'prompt', %w[ edit info add delete list duplicate import export reset rename ] ],
465
468
  optional: true,
466
469
  options: '[-c CONTEXT|-e|-f]',
467
470
  help: <<~EOT,
468
471
  📝 Manage prompt templates:
469
472
  Subcommands: edit, info, add, delete, list,
470
- duplicate, import, export, reset.
473
+ duplicate, import, export, reset, rename.
471
474
  Options: -c [context]
472
475
  (? for interactive in /prompt),
473
476
  -e (edit next)
@@ -499,6 +502,8 @@ module OllamaChat::Commands
499
502
  list_prompts(context:)
500
503
  when 'duplicate'
501
504
  duplicate_prompt(context:)
505
+ when 'rename'
506
+ rename_prompt(context:)
502
507
  when 'import'
503
508
  import_prompt(filename, context:)
504
509
  when 'export'
@@ -565,12 +570,13 @@ module OllamaChat::Commands
565
570
 
566
571
  command(
567
572
  name: :conversation,
568
- regexp: %r(^/conversation\s+(clean|save|load)(\s+-c)?(?:\s+([^-].*))?$),
573
+ regexp: %r(^/conversation\s+(clean|save|load)(\s+-c)?(?:\s+([^-].*\.jsonl?))?$),
569
574
  complete: [ 'conversation', %w[ save load clean ] ],
570
- options: '[-c]',
575
+ options: '[-c] [FILENAME]',
571
576
  help: <<~EOT
572
577
  💾 Save/Load conversation state
573
578
  (-c to clean before saving)
579
+ FILENAME ends with .json or .jsonl
574
580
  Or clean inplace (removes tool content, images, and thinking)
575
581
  EOT
576
582
  ) do |subcommand,opts,path|
@@ -826,7 +832,7 @@ module OllamaChat::Commands
826
832
  if opts[?p]
827
833
  words = opts.fetch(?w, 100)
828
834
  all = opts.fetch(?a, false)
829
- arg and patterns = arg.scan(/(\S+)/).flatten
835
+ patterns = extract_patterns(arg)
830
836
  next provide_file_set_content(patterns, all:, skip_blank: true) { summarize(_1, words:) } || :next
831
837
  elsif arg
832
838
  words = opts.fetch(?w, 100)
@@ -840,7 +846,7 @@ module OllamaChat::Commands
840
846
  opts = go_command('pa', opts)
841
847
  if opts[?p]
842
848
  all = opts.fetch(?a, false)
843
- patterns = arg&.scan(/(\S+)/)&.flatten.full? || [ '**/*' ]
849
+ patterns = extract_patterns(arg).full? || [ '**/*' ]
844
850
  next context_spook(patterns, all:) || :next
845
851
  elsif arg
846
852
  next context_spook(Array(arg.to_s), all: true) || :next
@@ -862,7 +868,7 @@ module OllamaChat::Commands
862
868
  tags = opts[?t].full?(:split, ?,)
863
869
  if opts[?p]
864
870
  all = opts.fetch(?a, false)
865
- arg and patterns = arg.scan(/(\S+)/).flatten
871
+ patterns = extract_patterns(arg)
866
872
  next provide_file_set_content(patterns, all:, skip_blank: true) { embed(_1, tags:) } || :next
867
873
  elsif arg
868
874
  next embed(arg, tags:) || :next
@@ -875,7 +881,7 @@ module OllamaChat::Commands
875
881
  opts = go_command('pae', opts)
876
882
  if opts[?p]
877
883
  all = opts.fetch(?a, false)
878
- arg and patterns = arg.scan(/(\S+)/).flatten
884
+ patterns = extract_patterns(arg)
879
885
  read = -> pathname {
880
886
  STDOUT.puts "Reading #{pathname.to_s.inspect}."
881
887
  pathname.read
@@ -895,7 +901,7 @@ module OllamaChat::Commands
895
901
  opts = go_command('pae', opts)
896
902
  if opts[?p]
897
903
  all = opts.fetch(?a, false)
898
- arg and patterns = arg.scan(/(\S+)/).flatten
904
+ patterns = extract_patterns(arg)
899
905
  next provide_file_set_content(patterns, all:, skip_blank: true) { import(_1) } || :next
900
906
  elsif arg
901
907
  source = arg
@@ -52,7 +52,7 @@ class OllamaChat::Database::Models::Prompt < Sequel::Model(OllamaChat::DB)
52
52
  # database when the underlying prompt is removed.
53
53
  def after_destroy
54
54
  super
55
- models::Favourite.where(context:, name:).destroy
55
+ OllamaChat::Database::Models::Favourite.where(context:, name:).destroy
56
56
  end
57
57
 
58
58
  # Seeds the prompt table from the provided chat configuration.
@@ -29,10 +29,14 @@ class OllamaChat::FollowChat
29
29
  super(output:)
30
30
  @chat = chat
31
31
  @output.sync = true
32
- @say = voice ? Handlers::Say.new(voice:) : NOP
33
32
  @messages = messages
34
33
  @group_uuid = group_uuid
35
34
  @sender = nil
35
+ @say = if voice && @chat.voice_handler.respond_to?(:new)
36
+ @chat.voice_handler.new(chat:, voice:)
37
+ else
38
+ NOP
39
+ end
36
40
  end
37
41
 
38
42
  attr_reader :chat
@@ -102,7 +102,7 @@ module OllamaChat::InputContent
102
102
  file filename.to_path
103
103
  end
104
104
  end
105
- end.to_json
105
+ end.send("to_#{format.downcase}")
106
106
  else
107
107
  if context_filename = choose_filename('.contexts/*.rb')
108
108
  ContextSpook.generate_context(context_filename, verbose: true, format:).
@@ -156,7 +156,8 @@ module OllamaChat::ModelHandling
156
156
  model_name ||= @model
157
157
  model_options = get_session_model_options
158
158
  store_model_options(model_name, model_options, profile:)
159
- STDOUT.puts "Model options #{italic{profile}} for #{bold{model_name}} were copied from session model options."
159
+ STDOUT.puts "Model options #{italic{profile}} for #{bold{model_name}} "\
160
+ "were copied from session model options."
160
161
  end
161
162
 
162
163
  # Resets the session's model options to match the stored defaults for the
@@ -169,7 +170,8 @@ module OllamaChat::ModelHandling
169
170
  model_name ||= @model
170
171
  stored_model_options = get_stored_model_options(model_name, profile:)
171
172
  session.update(model_options: stored_model_options)
172
- STDOUT.puts "Model options #{italic{profile}} of #{bold{model_name}} were copied to session model options."
173
+ STDOUT.puts "Model options #{italic{profile}} of #{bold{model_name}} "\
174
+ "were copied to session model options."
173
175
  end
174
176
 
175
177
  # Interactively copies model options from a source model/profile to the
@@ -181,7 +183,7 @@ module OllamaChat::ModelHandling
181
183
  src_model = choose_model('', model)
182
184
  src_profile = choose_profile_for_model(src_model) || 'default'
183
185
  dst_model = model
184
- dst_profile = choose_profile_for_model(dst_model, allow_new: true) || src_profile
186
+ dst_profile = choose_profile_for_model(dst_model, allow_new: true, suggest: src_profile)
185
187
 
186
188
  src_opts = get_stored_model_options(src_model, profile: src_profile).full? or return
187
189
 
@@ -200,7 +202,31 @@ module OllamaChat::ModelHandling
200
202
  end
201
203
 
202
204
  store_model_options(dst_model, src_opts, profile: dst_profile)
203
- STDOUT.puts "✅ Copied options from #{italic{src_model}}/#{italic{src_profile}} to #{bold{dst_model}}/#{italic{dst_profile}}."
205
+ STDOUT.puts "✅ Copied options from #{italic{src_model}}/#{italic{src_profile}} "\
206
+ "to #{bold{dst_model}}/#{italic{dst_profile}}."
207
+ end
208
+
209
+ # Interactively deletes a stored model options profile.
210
+ #
211
+ # If the deleted profile is the default one, it is replaced with an empty
212
+ # options hash `{}` to ensure the model always has a valid default profile.
213
+ #
214
+ # @param model [String] the model whose profile should be deleted
215
+ def delete_model_options_profile(model)
216
+ profile = choose_profile_for_model(model) or return
217
+
218
+ if confirm?(prompt: "🔔 Really delete profile #{bold{profile}} for #{bold{model}}? (y/n) ", yes: /\Ay/i)
219
+ if profile == 'default'
220
+ store_model_options(model, {}, profile:)
221
+ STDOUT.puts "Default profile #{italic{profile}} for #{bold{model}} has been reset to empty options."
222
+ else
223
+ models::ModelOptions.where(model_name: model, profile:).destroy
224
+ STDOUT.puts "Profile #{italic{profile}} for #{bold{model}} deleted."
225
+ end
226
+ log(:info, "Model options profile deleted", data: { model:, profile: })
227
+ else
228
+ STDOUT.puts "Cancelled."
229
+ end
204
230
  end
205
231
 
206
232
  # Presents an interactive list of stored configuration profiles for the
@@ -212,12 +238,14 @@ module OllamaChat::ModelHandling
212
238
  #
213
239
  # @param model_name [String] the name of the model whose profiles are to be listed
214
240
  # @return [String, nil] the selected profile name, or `nil` if none was chosen
215
- def choose_profile_for_model(model_name, allow_new: false)
241
+ def choose_profile_for_model(model_name, allow_new: false, suggest: nil)
216
242
  profiles = models::ModelOptions.where(model_name:).order(:profile).map(&:profile)
217
243
 
218
244
  if allow_new
245
+ profiles.unshift(suggest) if suggest && !profiles.member?(suggest)
219
246
  profiles = [ '[EXIT]', '[NEW]' ] + profiles
220
247
  else
248
+ profiles.unshift(suggest) if suggest && !profiles.member?(suggest)
221
249
  profiles.size < 2 and return profiles.first
222
250
  profiles = [ '[EXIT]' ] + profiles
223
251
  end
@@ -173,6 +173,12 @@ prompts:
173
173
  conversation. Output only the title and nothing else:
174
174
 
175
175
  %{content}
176
+ context_template_suggest: |
177
+ Conversation History:
178
+ %{history}
179
+
180
+ Create and output text following this instruction:
181
+ %{instruction}
176
182
  suggest:
177
183
  coding: |
178
184
  ## Context
@@ -343,9 +349,9 @@ prompts:
343
349
  %{runtime_info}
344
350
  assistant: You are a helpful assistant.
345
351
  voice:
352
+ handler: OllamaChat::Say
346
353
  enabled: false
347
354
  default: Samantha
348
- list: <%= defined?(RSpec) ? '[]' : `say -v '?' 2>/dev/null`.lines.map { |l| l.force_encoding('ASCII-8BIT'); l[/^(.+?)\s+[a-z]{2}_[a-zA-Z0-9]{2,}/, 1] }.uniq.sort.to_s %>
349
355
  roles:
350
356
  user: 👨 %{sender_name}
351
357
  assistant: 🤖 %{sender_name}
@@ -88,11 +88,11 @@ module OllamaChat::PromptManagement
88
88
  # Interactively prompts the user for a name and content (optionally loading
89
89
  # from a file) to create a new prompt template.
90
90
  #
91
- # @return [Boolean, nil] true if the prompt was added, nil if the process was
91
+ # @return [self, nil] self if the prompt was added, nil if the process was
92
92
  # cancelled
93
93
  def add_new_prompt(context: nil)
94
94
  context ||= 'prompt'
95
- switch_history(:add_prompt) do
95
+ switch_history(:prompt) do
96
96
  name = determine_valid_new_name_for_prompt('to add', context:) or return
97
97
 
98
98
  sources = %w[ [CLIPBOARD] [FILES] [EMPTY/MANUAL] ]
@@ -115,7 +115,7 @@ module OllamaChat::PromptManagement
115
115
  prompt_content = edit_text(content)
116
116
  store_prompt(name, prompt_content, context:)
117
117
  log(:info, "Prompt added", data: { name:, context: })
118
- true
118
+ self
119
119
  end
120
120
  end
121
121
 
@@ -165,31 +165,75 @@ module OllamaChat::PromptManagement
165
165
  # cancelled the operation or no prompt was selected.
166
166
  def duplicate_prompt(context: nil)
167
167
  context ||= 'prompt'
168
- selected_prompt = choose_prompt(context:, prompt: 'Which prompt shall be the basis for a new one? %s') or return
169
- STDOUT.puts kramdown_ansi_parse(
170
- selected_prompt.to_s + "\n---"
171
- )
172
- name = nil
173
- loop do
174
- name = ask?(
175
- prompt: "❓ Enter new prompt name to duplicate as, C-c ⇒ cancel: "
168
+ switch_history(:prompt) do
169
+ selected_prompt = choose_prompt(context:, prompt: 'Which prompt shall be the basis for a new one? %s') or return
170
+ STDOUT.puts kramdown_ansi_parse(
171
+ selected_prompt.to_s + "\n---"
176
172
  )
177
- if name.nil?
178
- STDOUT.puts "Cancelled."
179
- return nil
173
+ name = nil
174
+ loop do
175
+ name = ask?(
176
+ prompt: "❓ Enter new prompt name to duplicate as, C-c ⇒ cancel: "
177
+ )
178
+ if name.nil?
179
+ STDOUT.puts "Cancelled."
180
+ return nil
181
+ end
182
+ if prompt(name, context:)
183
+ STDOUT.puts "Prompt named #{bold{name}} already exists."
184
+ else
185
+ break
186
+ end
180
187
  end
181
- if prompt(name, context:)
182
- STDOUT.puts "Prompt named #{bold{name}} already exists."
183
- else
184
- break
188
+ duplicated_prompt = selected_prompt.duplicate
189
+ duplicated_prompt.name = name
190
+ duplicated_prompt.metadata['default'] = false
191
+ duplicated_prompt.save
192
+ log(:info, "Prompt duplicated", data: { name:, old_name: selected_prompt.name, context: })
193
+ self
194
+ end
195
+ end
196
+
197
+ # Interactively selects an existing prompt and renames it.
198
+ #
199
+ # @return [self, nil] the current context on success, or nil if the user
200
+ # cancelled the operation or no prompt was selected.
201
+ def rename_prompt(context: nil)
202
+ context ||= 'prompt'
203
+ switch_history(:prompt) do
204
+ selected_prompt = choose_prompt(
205
+ prompt: 'Which prompt would you like to rename? %s',
206
+ context:
207
+ ) or return
208
+
209
+ STDOUT.puts kramdown_ansi_parse(
210
+ selected_prompt.to_s + "\n---"
211
+ )
212
+
213
+ name = nil
214
+ loop do
215
+ name = ask?(
216
+ prompt: "❓ Enter new prompt name, C-c ⇒ cancel: "
217
+ )
218
+ if name.nil?
219
+ STDOUT.puts "Cancelled."
220
+ return nil
221
+ end
222
+ if name == selected_prompt.name
223
+ STDOUT.puts "That is the current name."
224
+ elsif prompt(name, context:)
225
+ STDOUT.puts "Prompt named #{bold{name}} already exists."
226
+ else
227
+ break
228
+ end
185
229
  end
230
+
231
+ old_name = selected_prompt.name
232
+ selected_prompt.name = name
233
+ selected_prompt.save
234
+ log(:info, "Prompt renamed", data: { old_name:, new_name: name, context: })
235
+ self
186
236
  end
187
- duplicated_prompt = selected_prompt.duplicate
188
- duplicated_prompt.name = name
189
- duplicated_prompt.metadata['default'] = false
190
- duplicated_prompt.save
191
- log(:info, "Prompt duplicated", data: { name:, old_name: selected_prompt.name, context: })
192
- self
193
237
  end
194
238
 
195
239
  # Interactively imports a prompt from a file.
@@ -294,16 +338,11 @@ module OllamaChat::PromptManagement
294
338
 
295
339
  # Build the context by gathering all current conversation messages
296
340
  history = prepare_conversation_history
297
- full_prompt = <<~EOT
298
- Conversation History:
299
- #{history}
300
-
301
- Instruction:
302
- #{instruction}
303
- EOT
341
+ template = prompt('context_template_suggest', context: 'prompt').to_s
342
+ full_prompt = template % { history:, instruction: }
304
343
 
305
344
  # Execute a silent chat oneshot call (doesn't add to history)
306
- suggestions = generate(prompt: full_prompt).full? or return
345
+ suggestions = generate(prompt: full_prompt).full? or return
307
346
 
308
347
  # Pass the AI's suggestions through the editor for final refinement
309
348
  edit_text(suggestions)
@@ -366,21 +405,23 @@ module OllamaChat::PromptManagement
366
405
  # the operation was cancelled.
367
406
  def determine_valid_new_name_for_prompt(action, context: nil)
368
407
  context ||= 'prompt'
369
- prompt_name = nil
370
- loop do
371
- prompt_name = ask?(
372
- prompt: "❓ Enter new prompt name #{action}, C-c ⇒ cancel: "
373
- )
374
- if prompt_name.nil?
375
- STDOUT.puts "Cancelled."
376
- return nil
377
- end
378
- if prompt(prompt_name, context:)
379
- STDOUT.puts "Prompt named #{bold{prompt_name}} already exists."
380
- else
381
- break
408
+ switch_history(:prompt) do
409
+ prompt_name = nil
410
+ loop do
411
+ prompt_name = ask?(
412
+ prompt: "❓ Enter new prompt name #{action}, C-c ⇒ cancel: "
413
+ )
414
+ if prompt_name.nil?
415
+ STDOUT.puts "Cancelled."
416
+ return nil
417
+ end
418
+ if prompt(prompt_name, context:)
419
+ STDOUT.puts "Prompt named #{bold{prompt_name}} already exists."
420
+ else
421
+ break
422
+ end
382
423
  end
424
+ prompt_name
383
425
  end
384
- prompt_name
385
426
  end
386
427
  end
@@ -219,15 +219,16 @@ module OllamaChat::RAGHandling
219
219
  end
220
220
  end
221
221
 
222
- # Extracts and normalizes file patterns from a space-separated string.
222
+ # Extracts and normalizes file patterns from a shell-style string.
223
223
  #
224
- # Splits the input string by whitespace, strips each pattern, and expands
225
- # them to absolute paths. Returns an empty array if the input is blank.
224
+ # Splits the input string using shell semantics, correctly handling
225
+ # spaces enclosed in single/double quotes or escaped with backslashes.
226
+ # Each resulting pattern is expanded to an absolute path.
226
227
  #
227
- # @param patterns_str [String, nil] the space-separated glob patterns
228
+ # @param patterns_str [String, nil] the shell-style glob patterns
228
229
  # @return [Array<String>] an array of expanded absolute path patterns
229
230
  def extract_patterns(patterns_str)
230
- patterns = patterns_str.full? ? patterns_str.split(/\s+/).map(&:strip) : []
231
+ patterns = Shellwords.split(patterns_str.to_s)
231
232
  patterns.map { File.expand_path(_1) }
232
233
  end
233
234
 
@@ -0,0 +1,30 @@
1
+ # A handler that uses the system's say command to speak response content.
2
+ #
3
+ # This class extends Ollama::Handlers::Say to provide voice validation
4
+ # against the system's available voices. It ensures that only valid voices
5
+ # are used, falling back to nil if an invalid voice is specified.
6
+ #
7
+ # @example Using the Say handler
8
+ # OllamaChat::Say.new(chat: chat, voice: 'Samantha')
9
+ class OllamaChat::Say < Ollama::Handlers::Say
10
+ # Returns a list of available system voices.
11
+ #
12
+ # This method queries the system's `say` command to retrieve the list of
13
+ # available voices. If running within RSpec, it returns an empty array to
14
+ # avoid side effects during testing.
15
+ #
16
+ # @return [Array<String>] a sorted list of unique voice identifiers
17
+ def self.voices
18
+ `say -v '?' 2>/dev/null`.lines.map { |l| l[/^(.+?)\s+[a-z]{2}_[a-zA-Z0-9]{2,}/, 1] }.uniq.sort
19
+ end
20
+
21
+ # Initializes a new Say handler.
22
+ #
23
+ # @param chat [OllamaChat::Chat] the chat instance
24
+ # @param voice [String, nil] the voice to use (must be in the list of available voices)
25
+ def initialize(chat:, voice: nil)
26
+ voice && self.class.voices.member?(voice) or voice = nil
27
+ @chat = chat
28
+ super(voice:)
29
+ end
30
+ end
@@ -269,11 +269,12 @@ module OllamaChat::StateSelectors
269
269
  states: OllamaChat::ThinkControl::THINK_MODE_STATES,
270
270
  off: OllamaChat::ThinkControl::THINK_MODE_STATES[0, 1],
271
271
  )
272
+ list = voice_handler.ask_and_send(:voices) || []
272
273
  @voices = DatabaseStateSelector.new(
273
274
  chat: self,
274
275
  attribute: :current_voice,
275
276
  name: 'Voice',
276
- states: config.voice.list,
277
+ states: list,
277
278
  allow_empty: true
278
279
  )
279
280
  @context_format = DatabaseStateSelector.new(
@@ -76,6 +76,22 @@ class OllamaChat::Tools::WriteFile
76
76
 
77
77
  es = OllamaChat::TokenEstimator.estimate(args.content)
78
78
 
79
+ # Confirm overwrite if file already exists
80
+ if path.exist? && (args.mode == 'overwrite' || args.mode.nil?)
81
+ unless chat.confirm?(prompt: "File #{path.to_s.inspect} already exists. Overwrite? (y/N): ", yes: /\Ay/i)
82
+ raise OllamaChat::ToolFunctionArgumentError,
83
+ "Write rejected: File #{path.to_s.inspect} already exists and was not overwritten."
84
+ end
85
+ end
86
+
87
+ # Confirm creation if file doesn't exist in append mode
88
+ if !path.exist? && args.mode == 'append'
89
+ unless chat.confirm?(prompt: "File #{path.to_s.inspect} does not exist. Create it? (y/N): ", yes: /\Ay/i)
90
+ raise OllamaChat::ToolFunctionArgumentError,
91
+ "Write rejected: File #{path.to_s.inspect} does not exist and was not created."
92
+ end
93
+ end
94
+
79
95
  backup_path = nil
80
96
 
81
97
  content = args.content
@@ -1,6 +1,6 @@
1
1
  module OllamaChat
2
2
  # OllamaChat version
3
- VERSION = '0.0.105'
3
+ VERSION = '0.0.107'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/ollama_chat.rb CHANGED
@@ -120,4 +120,5 @@ require 'ollama_chat/prompt_handling'
120
120
  require 'ollama_chat/system_prompt_management'
121
121
  require 'ollama_chat/prompt_management'
122
122
  require 'ollama_chat/token_estimator'
123
+ require 'ollama_chat/say'
123
124
  require 'ollama_chat/chat'
data/ollama_chat.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: ollama_chat 0.0.105 ruby lib
2
+ # stub: ollama_chat 0.0.107 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "ollama_chat".freeze
6
- s.version = "0.0.105".freeze
6
+ s.version = "0.0.107".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
12
12
  s.description = "The app provides a command-line interface (CLI) to an Ollama AI model,\nallowing users to engage in text-based conversations and generate\nhuman-like responses. Users can import data from local files or web pages,\nwhich are then processed through three different modes: fully importing the\ncontent into the conversation context, summarizing the information for\nconcise reference, or storing it in an embedding vector database for later\nretrieval based on the conversation.\n".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["ollama_chat".freeze, "ollama_chat_send".freeze, "ollama_chat_log".freeze]
15
- s.extra_rdoc_files = ["README.md".freeze, "lib/ollama_chat.rb".freeze, "lib/ollama_chat/chat.rb".freeze, "lib/ollama_chat/clipboard.rb".freeze, "lib/ollama_chat/command_concern.rb".freeze, "lib/ollama_chat/commands.rb".freeze, "lib/ollama_chat/config_handling.rb".freeze, "lib/ollama_chat/conversation.rb".freeze, "lib/ollama_chat/database.rb".freeze, "lib/ollama_chat/database/duplicatable.rb".freeze, "lib/ollama_chat/database/migrations.rb".freeze, "lib/ollama_chat/database/migrations/001_initial_schema.rb".freeze, "lib/ollama_chat/database/migrations/002_add_links_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/003_add_history_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/004_add_profile_to_model_options.rb".freeze, "lib/ollama_chat/database/migrations/005_add_context_format_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/006_rename_system_prompt_context_to_system.rb".freeze, "lib/ollama_chat/database/migrations/007_add_collections_table.rb".freeze, "lib/ollama_chat/database/models/collection.rb".freeze, "lib/ollama_chat/database/models/favourite.rb".freeze, "lib/ollama_chat/database/models/model_options.rb".freeze, "lib/ollama_chat/database/models/prompt.rb".freeze, "lib/ollama_chat/database/models/session.rb".freeze, "lib/ollama_chat/database/session_locking.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/favourites_management.rb".freeze, "lib/ollama_chat/file_editing.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.rb".freeze, "lib/ollama_chat/http_handling.rb".freeze, "lib/ollama_chat/information.rb".freeze, "lib/ollama_chat/input_content.rb".freeze, "lib/ollama_chat/kramdown_ansi.rb".freeze, "lib/ollama_chat/links_set.rb".freeze, "lib/ollama_chat/location_handling.rb".freeze, "lib/ollama_chat/logging.rb".freeze, "lib/ollama_chat/message.rb".freeze, "lib/ollama_chat/message_editing.rb".freeze, "lib/ollama_chat/message_format.rb".freeze, "lib/ollama_chat/message_list.rb".freeze, "lib/ollama_chat/message_output.rb".freeze, "lib/ollama_chat/model_handling.rb".freeze, "lib/ollama_chat/oc.rb".freeze, "lib/ollama_chat/ollama_chat_config.rb".freeze, "lib/ollama_chat/pager.rb".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/personae_management.rb".freeze, "lib/ollama_chat/prompt_handling.rb".freeze, "lib/ollama_chat/prompt_management.rb".freeze, "lib/ollama_chat/rag_handling.rb".freeze, "lib/ollama_chat/redis_cache.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/session_management.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/state_selectors.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/system_prompt_management.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/token_estimator.rb".freeze, "lib/ollama_chat/token_estimator/crude.rb".freeze, "lib/ollama_chat/tool_calling.rb".freeze, "lib/ollama_chat/tools.rb".freeze, "lib/ollama_chat/tools/browse.rb".freeze, "lib/ollama_chat/tools/compute_bmi.rb".freeze, "lib/ollama_chat/tools/concern.rb".freeze, "lib/ollama_chat/tools/copy_to_clipboard.rb".freeze, "lib/ollama_chat/tools/delete_file.rb".freeze, "lib/ollama_chat/tools/directory_structure.rb".freeze, "lib/ollama_chat/tools/eval_ruby.rb".freeze, "lib/ollama_chat/tools/execute_grep.rb".freeze, "lib/ollama_chat/tools/execute_ri.rb".freeze, "lib/ollama_chat/tools/file_context.rb".freeze, "lib/ollama_chat/tools/gem_path_lookup.rb".freeze, "lib/ollama_chat/tools/generate_image.rb".freeze, "lib/ollama_chat/tools/generate_password.rb".freeze, "lib/ollama_chat/tools/get_current_weather.rb".freeze, "lib/ollama_chat/tools/get_cve.rb".freeze, "lib/ollama_chat/tools/get_endoflife.rb".freeze, "lib/ollama_chat/tools/get_ghr.rb".freeze, "lib/ollama_chat/tools/get_jira_issue.rb".freeze, "lib/ollama_chat/tools/get_location.rb".freeze, "lib/ollama_chat/tools/get_rfc.rb".freeze, "lib/ollama_chat/tools/get_time.rb".freeze, "lib/ollama_chat/tools/get_url.rb".freeze, "lib/ollama_chat/tools/move_file.rb".freeze, "lib/ollama_chat/tools/open_file_in_editor.rb".freeze, "lib/ollama_chat/tools/paste_from_clipboard.rb".freeze, "lib/ollama_chat/tools/paste_into_editor.rb".freeze, "lib/ollama_chat/tools/patch_file.rb".freeze, "lib/ollama_chat/tools/read_file.rb".freeze, "lib/ollama_chat/tools/resolve_tag.rb".freeze, "lib/ollama_chat/tools/roll_dice.rb".freeze, "lib/ollama_chat/tools/run_tests.rb".freeze, "lib/ollama_chat/tools/search_knowledge.rb".freeze, "lib/ollama_chat/tools/search_web.rb".freeze, "lib/ollama_chat/tools/write_file.rb".freeze, "lib/ollama_chat/utils.rb".freeze, "lib/ollama_chat/utils/analyze_directory.rb".freeze, "lib/ollama_chat/utils/backup.rb".freeze, "lib/ollama_chat/utils/cache_fetcher.rb".freeze, "lib/ollama_chat/utils/chooser.rb".freeze, "lib/ollama_chat/utils/fetcher.rb".freeze, "lib/ollama_chat/utils/json_jsonl_io.rb".freeze, "lib/ollama_chat/utils/log_viewer.rb".freeze, "lib/ollama_chat/utils/path_completer.rb".freeze, "lib/ollama_chat/utils/path_validator.rb".freeze, "lib/ollama_chat/utils/png_metadata_extractor.rb".freeze, "lib/ollama_chat/utils/tag_resolver.rb".freeze, "lib/ollama_chat/utils/utf8_converter.rb".freeze, "lib/ollama_chat/utils/value_formatter.rb".freeze, "lib/ollama_chat/uuid_v7.rb".freeze, "lib/ollama_chat/version.rb".freeze, "lib/ollama_chat/vim.rb".freeze, "lib/ollama_chat/web_searching.rb".freeze]
16
- s.files = [".utilsrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_chat_log".freeze, "bin/ollama_chat_send".freeze, "config/searxng/settings.yml".freeze, "config/sherlock.md".freeze, "docker-compose.yml".freeze, "lib/ollama_chat.rb".freeze, "lib/ollama_chat/chat.rb".freeze, "lib/ollama_chat/clipboard.rb".freeze, "lib/ollama_chat/command_concern.rb".freeze, "lib/ollama_chat/commands.rb".freeze, "lib/ollama_chat/config_handling.rb".freeze, "lib/ollama_chat/conversation.rb".freeze, "lib/ollama_chat/database.rb".freeze, "lib/ollama_chat/database/duplicatable.rb".freeze, "lib/ollama_chat/database/migrations.rb".freeze, "lib/ollama_chat/database/migrations/001_initial_schema.rb".freeze, "lib/ollama_chat/database/migrations/002_add_links_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/003_add_history_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/004_add_profile_to_model_options.rb".freeze, "lib/ollama_chat/database/migrations/005_add_context_format_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/006_rename_system_prompt_context_to_system.rb".freeze, "lib/ollama_chat/database/migrations/007_add_collections_table.rb".freeze, "lib/ollama_chat/database/models/collection.rb".freeze, "lib/ollama_chat/database/models/favourite.rb".freeze, "lib/ollama_chat/database/models/model_options.rb".freeze, "lib/ollama_chat/database/models/prompt.rb".freeze, "lib/ollama_chat/database/models/session.rb".freeze, "lib/ollama_chat/database/session_locking.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/favourites_management.rb".freeze, "lib/ollama_chat/file_editing.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.rb".freeze, "lib/ollama_chat/http_handling.rb".freeze, "lib/ollama_chat/information.rb".freeze, "lib/ollama_chat/input_content.rb".freeze, "lib/ollama_chat/kramdown_ansi.rb".freeze, "lib/ollama_chat/links_set.rb".freeze, "lib/ollama_chat/location_handling.rb".freeze, "lib/ollama_chat/logging.rb".freeze, "lib/ollama_chat/message.rb".freeze, "lib/ollama_chat/message_editing.rb".freeze, "lib/ollama_chat/message_format.rb".freeze, "lib/ollama_chat/message_list.rb".freeze, "lib/ollama_chat/message_output.rb".freeze, "lib/ollama_chat/model_handling.rb".freeze, "lib/ollama_chat/oc.rb".freeze, "lib/ollama_chat/ollama_chat_config.rb".freeze, "lib/ollama_chat/ollama_chat_config/default_config.yml".freeze, "lib/ollama_chat/pager.rb".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/personae_management.rb".freeze, "lib/ollama_chat/prompt_handling.rb".freeze, "lib/ollama_chat/prompt_management.rb".freeze, "lib/ollama_chat/rag_handling.rb".freeze, "lib/ollama_chat/redis_cache.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/session_management.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/state_selectors.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/system_prompt_management.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/token_estimator.rb".freeze, "lib/ollama_chat/token_estimator/crude.rb".freeze, "lib/ollama_chat/tool_calling.rb".freeze, "lib/ollama_chat/tools.rb".freeze, "lib/ollama_chat/tools/browse.rb".freeze, "lib/ollama_chat/tools/compute_bmi.rb".freeze, "lib/ollama_chat/tools/concern.rb".freeze, "lib/ollama_chat/tools/copy_to_clipboard.rb".freeze, "lib/ollama_chat/tools/delete_file.rb".freeze, "lib/ollama_chat/tools/directory_structure.rb".freeze, "lib/ollama_chat/tools/eval_ruby.rb".freeze, "lib/ollama_chat/tools/execute_grep.rb".freeze, "lib/ollama_chat/tools/execute_ri.rb".freeze, "lib/ollama_chat/tools/file_context.rb".freeze, "lib/ollama_chat/tools/gem_path_lookup.rb".freeze, "lib/ollama_chat/tools/generate_image.rb".freeze, "lib/ollama_chat/tools/generate_password.rb".freeze, "lib/ollama_chat/tools/get_current_weather.rb".freeze, "lib/ollama_chat/tools/get_cve.rb".freeze, "lib/ollama_chat/tools/get_endoflife.rb".freeze, "lib/ollama_chat/tools/get_ghr.rb".freeze, "lib/ollama_chat/tools/get_jira_issue.rb".freeze, "lib/ollama_chat/tools/get_location.rb".freeze, "lib/ollama_chat/tools/get_rfc.rb".freeze, "lib/ollama_chat/tools/get_time.rb".freeze, "lib/ollama_chat/tools/get_url.rb".freeze, "lib/ollama_chat/tools/move_file.rb".freeze, "lib/ollama_chat/tools/open_file_in_editor.rb".freeze, "lib/ollama_chat/tools/paste_from_clipboard.rb".freeze, "lib/ollama_chat/tools/paste_into_editor.rb".freeze, "lib/ollama_chat/tools/patch_file.rb".freeze, "lib/ollama_chat/tools/read_file.rb".freeze, "lib/ollama_chat/tools/resolve_tag.rb".freeze, "lib/ollama_chat/tools/roll_dice.rb".freeze, "lib/ollama_chat/tools/run_tests.rb".freeze, "lib/ollama_chat/tools/search_knowledge.rb".freeze, "lib/ollama_chat/tools/search_web.rb".freeze, "lib/ollama_chat/tools/write_file.rb".freeze, "lib/ollama_chat/utils.rb".freeze, "lib/ollama_chat/utils/analyze_directory.rb".freeze, "lib/ollama_chat/utils/backup.rb".freeze, "lib/ollama_chat/utils/cache_fetcher.rb".freeze, "lib/ollama_chat/utils/chooser.rb".freeze, "lib/ollama_chat/utils/fetcher.rb".freeze, "lib/ollama_chat/utils/json_jsonl_io.rb".freeze, "lib/ollama_chat/utils/log_viewer.rb".freeze, "lib/ollama_chat/utils/path_completer.rb".freeze, "lib/ollama_chat/utils/path_validator.rb".freeze, "lib/ollama_chat/utils/png_metadata_extractor.rb".freeze, "lib/ollama_chat/utils/tag_resolver.rb".freeze, "lib/ollama_chat/utils/utf8_converter.rb".freeze, "lib/ollama_chat/utils/value_formatter.rb".freeze, "lib/ollama_chat/uuid_v7.rb".freeze, "lib/ollama_chat/version.rb".freeze, "lib/ollama_chat/vim.rb".freeze, "lib/ollama_chat/web_searching.rb".freeze, "ollama_chat.gemspec".freeze, "redis/redis.conf".freeze, "spec/assets/api_show.json".freeze, "spec/assets/api_tags.json".freeze, "spec/assets/api_version.json".freeze, "spec/assets/conversation.json".freeze, "spec/assets/conversation.jsonl".freeze, "spec/assets/deep/deeper/empty.txt".freeze, "spec/assets/deep/deeper/not-empty.txt".freeze, "spec/assets/deep/empty.txt".freeze, "spec/assets/duckduckgo.html".freeze, "spec/assets/example.atom".freeze, "spec/assets/example.csv".freeze, "spec/assets/example.epub".freeze, "spec/assets/example.html".freeze, "spec/assets/example.pdf".freeze, "spec/assets/example.ps".freeze, "spec/assets/example.rb".freeze, "spec/assets/example.rss".freeze, "spec/assets/example.xml".freeze, "spec/assets/example_with_quote.html".freeze, "spec/assets/fluffy.png".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/miyu.png".freeze, "spec/assets/pirateweather.json".freeze, "spec/assets/prompt.txt".freeze, "spec/assets/searxng.json".freeze, "spec/ollama_chat/chat_spec.rb".freeze, "spec/ollama_chat/clipboard_spec.rb".freeze, "spec/ollama_chat/commands_spec.rb".freeze, "spec/ollama_chat/database/models/favourite_spec.rb".freeze, "spec/ollama_chat/file_editing_spec.rb".freeze, "spec/ollama_chat/follow_chat_spec.rb".freeze, "spec/ollama_chat/history_spec.rb".freeze, "spec/ollama_chat/information_spec.rb".freeze, "spec/ollama_chat/input_content_spec.rb".freeze, "spec/ollama_chat/kramdown_ansi_spec.rb".freeze, "spec/ollama_chat/message_editing_spec.rb".freeze, "spec/ollama_chat/message_list_spec.rb".freeze, "spec/ollama_chat/message_output_spec.rb".freeze, "spec/ollama_chat/model_handling_spec.rb".freeze, "spec/ollama_chat/parsing_spec.rb".freeze, "spec/ollama_chat/redis_cache_spec.rb".freeze, "spec/ollama_chat/server_socket_spec.rb".freeze, "spec/ollama_chat/session_management_spec.rb".freeze, "spec/ollama_chat/source_fetching_spec.rb".freeze, "spec/ollama_chat/state_selectors_spec.rb".freeze, "spec/ollama_chat/switches_spec.rb".freeze, "spec/ollama_chat/think_control_spec.rb".freeze, "spec/ollama_chat/token_estimator_spec.rb".freeze, "spec/ollama_chat/tool_calling_spec.rb".freeze, "spec/ollama_chat/tools/browse_spec.rb".freeze, "spec/ollama_chat/tools/compute_bmi_spec.rb".freeze, "spec/ollama_chat/tools/copy_to_clipboard_spec.rb".freeze, "spec/ollama_chat/tools/delete_file_spec.rb".freeze, "spec/ollama_chat/tools/directory_structure_spec.rb".freeze, "spec/ollama_chat/tools/execute_grep_spec.rb".freeze, "spec/ollama_chat/tools/execute_ri_spec.rb".freeze, "spec/ollama_chat/tools/file_context_spec.rb".freeze, "spec/ollama_chat/tools/gem_path_lookup_spec.rb".freeze, "spec/ollama_chat/tools/generate_image_spec.rb".freeze, "spec/ollama_chat/tools/generate_password_spec.rb".freeze, "spec/ollama_chat/tools/get_current_weather_spec.rb".freeze, "spec/ollama_chat/tools/get_cve_spec.rb".freeze, "spec/ollama_chat/tools/get_endoflife_spec.rb".freeze, "spec/ollama_chat/tools/get_ghr_spec.rb".freeze, "spec/ollama_chat/tools/get_jira_issue_spec.rb".freeze, "spec/ollama_chat/tools/get_location_spec.rb".freeze, "spec/ollama_chat/tools/get_rfc_spec.rb".freeze, "spec/ollama_chat/tools/get_time_spec.rb".freeze, "spec/ollama_chat/tools/get_url_spec.rb".freeze, "spec/ollama_chat/tools/move_file_spec.rb".freeze, "spec/ollama_chat/tools/open_file_in_editor_spec.rb".freeze, "spec/ollama_chat/tools/paste_from_clipboard_spec.rb".freeze, "spec/ollama_chat/tools/paste_into_editor_spec.rb".freeze, "spec/ollama_chat/tools/patch_file_spec.rb".freeze, "spec/ollama_chat/tools/read_file_spec.rb".freeze, "spec/ollama_chat/tools/resolve_tag_spec.rb".freeze, "spec/ollama_chat/tools/roll_dice_spec.rb".freeze, "spec/ollama_chat/tools/run_tests_spec.rb".freeze, "spec/ollama_chat/tools/search_knowledge_spec.rb".freeze, "spec/ollama_chat/tools/search_web_spec.rb".freeze, "spec/ollama_chat/tools/write_file_spec.rb".freeze, "spec/ollama_chat/utils/analyze_directory_spec.rb".freeze, "spec/ollama_chat/utils/cache_fetcher_spec.rb".freeze, "spec/ollama_chat/utils/fetcher_spec.rb".freeze, "spec/ollama_chat/utils/log_viewer_spec.rb".freeze, "spec/ollama_chat/utils/path_completer_spec.rb".freeze, "spec/ollama_chat/utils/png_metadata_extractor_spec.rb".freeze, "spec/ollama_chat/vim_spec.rb".freeze, "spec/ollama_chat/web_searching_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
15
+ s.extra_rdoc_files = ["README.md".freeze, "lib/ollama_chat.rb".freeze, "lib/ollama_chat/chat.rb".freeze, "lib/ollama_chat/clipboard.rb".freeze, "lib/ollama_chat/command_concern.rb".freeze, "lib/ollama_chat/commands.rb".freeze, "lib/ollama_chat/config_handling.rb".freeze, "lib/ollama_chat/conversation.rb".freeze, "lib/ollama_chat/database.rb".freeze, "lib/ollama_chat/database/duplicatable.rb".freeze, "lib/ollama_chat/database/migrations.rb".freeze, "lib/ollama_chat/database/migrations/001_initial_schema.rb".freeze, "lib/ollama_chat/database/migrations/002_add_links_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/003_add_history_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/004_add_profile_to_model_options.rb".freeze, "lib/ollama_chat/database/migrations/005_add_context_format_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/006_rename_system_prompt_context_to_system.rb".freeze, "lib/ollama_chat/database/migrations/007_add_collections_table.rb".freeze, "lib/ollama_chat/database/models/collection.rb".freeze, "lib/ollama_chat/database/models/favourite.rb".freeze, "lib/ollama_chat/database/models/model_options.rb".freeze, "lib/ollama_chat/database/models/prompt.rb".freeze, "lib/ollama_chat/database/models/session.rb".freeze, "lib/ollama_chat/database/session_locking.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/favourites_management.rb".freeze, "lib/ollama_chat/file_editing.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.rb".freeze, "lib/ollama_chat/http_handling.rb".freeze, "lib/ollama_chat/information.rb".freeze, "lib/ollama_chat/input_content.rb".freeze, "lib/ollama_chat/kramdown_ansi.rb".freeze, "lib/ollama_chat/links_set.rb".freeze, "lib/ollama_chat/location_handling.rb".freeze, "lib/ollama_chat/logging.rb".freeze, "lib/ollama_chat/message.rb".freeze, "lib/ollama_chat/message_editing.rb".freeze, "lib/ollama_chat/message_format.rb".freeze, "lib/ollama_chat/message_list.rb".freeze, "lib/ollama_chat/message_output.rb".freeze, "lib/ollama_chat/model_handling.rb".freeze, "lib/ollama_chat/oc.rb".freeze, "lib/ollama_chat/ollama_chat_config.rb".freeze, "lib/ollama_chat/pager.rb".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/personae_management.rb".freeze, "lib/ollama_chat/prompt_handling.rb".freeze, "lib/ollama_chat/prompt_management.rb".freeze, "lib/ollama_chat/rag_handling.rb".freeze, "lib/ollama_chat/redis_cache.rb".freeze, "lib/ollama_chat/say.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/session_management.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/state_selectors.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/system_prompt_management.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/token_estimator.rb".freeze, "lib/ollama_chat/token_estimator/crude.rb".freeze, "lib/ollama_chat/tool_calling.rb".freeze, "lib/ollama_chat/tools.rb".freeze, "lib/ollama_chat/tools/browse.rb".freeze, "lib/ollama_chat/tools/compute_bmi.rb".freeze, "lib/ollama_chat/tools/concern.rb".freeze, "lib/ollama_chat/tools/copy_to_clipboard.rb".freeze, "lib/ollama_chat/tools/delete_file.rb".freeze, "lib/ollama_chat/tools/directory_structure.rb".freeze, "lib/ollama_chat/tools/eval_ruby.rb".freeze, "lib/ollama_chat/tools/execute_grep.rb".freeze, "lib/ollama_chat/tools/execute_ri.rb".freeze, "lib/ollama_chat/tools/file_context.rb".freeze, "lib/ollama_chat/tools/gem_path_lookup.rb".freeze, "lib/ollama_chat/tools/generate_image.rb".freeze, "lib/ollama_chat/tools/generate_password.rb".freeze, "lib/ollama_chat/tools/get_current_weather.rb".freeze, "lib/ollama_chat/tools/get_cve.rb".freeze, "lib/ollama_chat/tools/get_endoflife.rb".freeze, "lib/ollama_chat/tools/get_ghr.rb".freeze, "lib/ollama_chat/tools/get_jira_issue.rb".freeze, "lib/ollama_chat/tools/get_location.rb".freeze, "lib/ollama_chat/tools/get_rfc.rb".freeze, "lib/ollama_chat/tools/get_time.rb".freeze, "lib/ollama_chat/tools/get_url.rb".freeze, "lib/ollama_chat/tools/move_file.rb".freeze, "lib/ollama_chat/tools/open_file_in_editor.rb".freeze, "lib/ollama_chat/tools/paste_from_clipboard.rb".freeze, "lib/ollama_chat/tools/paste_into_editor.rb".freeze, "lib/ollama_chat/tools/patch_file.rb".freeze, "lib/ollama_chat/tools/read_file.rb".freeze, "lib/ollama_chat/tools/resolve_tag.rb".freeze, "lib/ollama_chat/tools/roll_dice.rb".freeze, "lib/ollama_chat/tools/run_tests.rb".freeze, "lib/ollama_chat/tools/search_knowledge.rb".freeze, "lib/ollama_chat/tools/search_web.rb".freeze, "lib/ollama_chat/tools/write_file.rb".freeze, "lib/ollama_chat/utils.rb".freeze, "lib/ollama_chat/utils/analyze_directory.rb".freeze, "lib/ollama_chat/utils/backup.rb".freeze, "lib/ollama_chat/utils/cache_fetcher.rb".freeze, "lib/ollama_chat/utils/chooser.rb".freeze, "lib/ollama_chat/utils/fetcher.rb".freeze, "lib/ollama_chat/utils/json_jsonl_io.rb".freeze, "lib/ollama_chat/utils/log_viewer.rb".freeze, "lib/ollama_chat/utils/path_completer.rb".freeze, "lib/ollama_chat/utils/path_validator.rb".freeze, "lib/ollama_chat/utils/png_metadata_extractor.rb".freeze, "lib/ollama_chat/utils/tag_resolver.rb".freeze, "lib/ollama_chat/utils/utf8_converter.rb".freeze, "lib/ollama_chat/utils/value_formatter.rb".freeze, "lib/ollama_chat/uuid_v7.rb".freeze, "lib/ollama_chat/version.rb".freeze, "lib/ollama_chat/vim.rb".freeze, "lib/ollama_chat/web_searching.rb".freeze]
16
+ s.files = [".utilsrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_chat_log".freeze, "bin/ollama_chat_send".freeze, "config/searxng/settings.yml".freeze, "config/sherlock.md".freeze, "docker-compose.yml".freeze, "lib/ollama_chat.rb".freeze, "lib/ollama_chat/chat.rb".freeze, "lib/ollama_chat/clipboard.rb".freeze, "lib/ollama_chat/command_concern.rb".freeze, "lib/ollama_chat/commands.rb".freeze, "lib/ollama_chat/config_handling.rb".freeze, "lib/ollama_chat/conversation.rb".freeze, "lib/ollama_chat/database.rb".freeze, "lib/ollama_chat/database/duplicatable.rb".freeze, "lib/ollama_chat/database/migrations.rb".freeze, "lib/ollama_chat/database/migrations/001_initial_schema.rb".freeze, "lib/ollama_chat/database/migrations/002_add_links_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/003_add_history_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/004_add_profile_to_model_options.rb".freeze, "lib/ollama_chat/database/migrations/005_add_context_format_to_sessions.rb".freeze, "lib/ollama_chat/database/migrations/006_rename_system_prompt_context_to_system.rb".freeze, "lib/ollama_chat/database/migrations/007_add_collections_table.rb".freeze, "lib/ollama_chat/database/models/collection.rb".freeze, "lib/ollama_chat/database/models/favourite.rb".freeze, "lib/ollama_chat/database/models/model_options.rb".freeze, "lib/ollama_chat/database/models/prompt.rb".freeze, "lib/ollama_chat/database/models/session.rb".freeze, "lib/ollama_chat/database/session_locking.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/favourites_management.rb".freeze, "lib/ollama_chat/file_editing.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.rb".freeze, "lib/ollama_chat/http_handling.rb".freeze, "lib/ollama_chat/information.rb".freeze, "lib/ollama_chat/input_content.rb".freeze, "lib/ollama_chat/kramdown_ansi.rb".freeze, "lib/ollama_chat/links_set.rb".freeze, "lib/ollama_chat/location_handling.rb".freeze, "lib/ollama_chat/logging.rb".freeze, "lib/ollama_chat/message.rb".freeze, "lib/ollama_chat/message_editing.rb".freeze, "lib/ollama_chat/message_format.rb".freeze, "lib/ollama_chat/message_list.rb".freeze, "lib/ollama_chat/message_output.rb".freeze, "lib/ollama_chat/model_handling.rb".freeze, "lib/ollama_chat/oc.rb".freeze, "lib/ollama_chat/ollama_chat_config.rb".freeze, "lib/ollama_chat/ollama_chat_config/default_config.yml".freeze, "lib/ollama_chat/pager.rb".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/personae_management.rb".freeze, "lib/ollama_chat/prompt_handling.rb".freeze, "lib/ollama_chat/prompt_management.rb".freeze, "lib/ollama_chat/rag_handling.rb".freeze, "lib/ollama_chat/redis_cache.rb".freeze, "lib/ollama_chat/say.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/session_management.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/state_selectors.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/system_prompt_management.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/token_estimator.rb".freeze, "lib/ollama_chat/token_estimator/crude.rb".freeze, "lib/ollama_chat/tool_calling.rb".freeze, "lib/ollama_chat/tools.rb".freeze, "lib/ollama_chat/tools/browse.rb".freeze, "lib/ollama_chat/tools/compute_bmi.rb".freeze, "lib/ollama_chat/tools/concern.rb".freeze, "lib/ollama_chat/tools/copy_to_clipboard.rb".freeze, "lib/ollama_chat/tools/delete_file.rb".freeze, "lib/ollama_chat/tools/directory_structure.rb".freeze, "lib/ollama_chat/tools/eval_ruby.rb".freeze, "lib/ollama_chat/tools/execute_grep.rb".freeze, "lib/ollama_chat/tools/execute_ri.rb".freeze, "lib/ollama_chat/tools/file_context.rb".freeze, "lib/ollama_chat/tools/gem_path_lookup.rb".freeze, "lib/ollama_chat/tools/generate_image.rb".freeze, "lib/ollama_chat/tools/generate_password.rb".freeze, "lib/ollama_chat/tools/get_current_weather.rb".freeze, "lib/ollama_chat/tools/get_cve.rb".freeze, "lib/ollama_chat/tools/get_endoflife.rb".freeze, "lib/ollama_chat/tools/get_ghr.rb".freeze, "lib/ollama_chat/tools/get_jira_issue.rb".freeze, "lib/ollama_chat/tools/get_location.rb".freeze, "lib/ollama_chat/tools/get_rfc.rb".freeze, "lib/ollama_chat/tools/get_time.rb".freeze, "lib/ollama_chat/tools/get_url.rb".freeze, "lib/ollama_chat/tools/move_file.rb".freeze, "lib/ollama_chat/tools/open_file_in_editor.rb".freeze, "lib/ollama_chat/tools/paste_from_clipboard.rb".freeze, "lib/ollama_chat/tools/paste_into_editor.rb".freeze, "lib/ollama_chat/tools/patch_file.rb".freeze, "lib/ollama_chat/tools/read_file.rb".freeze, "lib/ollama_chat/tools/resolve_tag.rb".freeze, "lib/ollama_chat/tools/roll_dice.rb".freeze, "lib/ollama_chat/tools/run_tests.rb".freeze, "lib/ollama_chat/tools/search_knowledge.rb".freeze, "lib/ollama_chat/tools/search_web.rb".freeze, "lib/ollama_chat/tools/write_file.rb".freeze, "lib/ollama_chat/utils.rb".freeze, "lib/ollama_chat/utils/analyze_directory.rb".freeze, "lib/ollama_chat/utils/backup.rb".freeze, "lib/ollama_chat/utils/cache_fetcher.rb".freeze, "lib/ollama_chat/utils/chooser.rb".freeze, "lib/ollama_chat/utils/fetcher.rb".freeze, "lib/ollama_chat/utils/json_jsonl_io.rb".freeze, "lib/ollama_chat/utils/log_viewer.rb".freeze, "lib/ollama_chat/utils/path_completer.rb".freeze, "lib/ollama_chat/utils/path_validator.rb".freeze, "lib/ollama_chat/utils/png_metadata_extractor.rb".freeze, "lib/ollama_chat/utils/tag_resolver.rb".freeze, "lib/ollama_chat/utils/utf8_converter.rb".freeze, "lib/ollama_chat/utils/value_formatter.rb".freeze, "lib/ollama_chat/uuid_v7.rb".freeze, "lib/ollama_chat/version.rb".freeze, "lib/ollama_chat/vim.rb".freeze, "lib/ollama_chat/web_searching.rb".freeze, "ollama_chat.gemspec".freeze, "redis/redis.conf".freeze, "spec/assets/api_show.json".freeze, "spec/assets/api_tags.json".freeze, "spec/assets/api_version.json".freeze, "spec/assets/conversation.json".freeze, "spec/assets/conversation.jsonl".freeze, "spec/assets/deep/deeper/empty.txt".freeze, "spec/assets/deep/deeper/not-empty.txt".freeze, "spec/assets/deep/empty.txt".freeze, "spec/assets/duckduckgo.html".freeze, "spec/assets/example.atom".freeze, "spec/assets/example.csv".freeze, "spec/assets/example.epub".freeze, "spec/assets/example.html".freeze, "spec/assets/example.pdf".freeze, "spec/assets/example.ps".freeze, "spec/assets/example.rb".freeze, "spec/assets/example.rss".freeze, "spec/assets/example.xml".freeze, "spec/assets/example_with_quote.html".freeze, "spec/assets/fluffy.png".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/miyu.png".freeze, "spec/assets/pirateweather.json".freeze, "spec/assets/prompt.txt".freeze, "spec/assets/searxng.json".freeze, "spec/ollama_chat/chat_spec.rb".freeze, "spec/ollama_chat/clipboard_spec.rb".freeze, "spec/ollama_chat/commands_spec.rb".freeze, "spec/ollama_chat/database/models/favourite_spec.rb".freeze, "spec/ollama_chat/file_editing_spec.rb".freeze, "spec/ollama_chat/follow_chat_spec.rb".freeze, "spec/ollama_chat/history_spec.rb".freeze, "spec/ollama_chat/information_spec.rb".freeze, "spec/ollama_chat/input_content_spec.rb".freeze, "spec/ollama_chat/kramdown_ansi_spec.rb".freeze, "spec/ollama_chat/message_editing_spec.rb".freeze, "spec/ollama_chat/message_list_spec.rb".freeze, "spec/ollama_chat/message_output_spec.rb".freeze, "spec/ollama_chat/model_handling_spec.rb".freeze, "spec/ollama_chat/parsing_spec.rb".freeze, "spec/ollama_chat/redis_cache_spec.rb".freeze, "spec/ollama_chat/server_socket_spec.rb".freeze, "spec/ollama_chat/session_management_spec.rb".freeze, "spec/ollama_chat/source_fetching_spec.rb".freeze, "spec/ollama_chat/state_selectors_spec.rb".freeze, "spec/ollama_chat/switches_spec.rb".freeze, "spec/ollama_chat/think_control_spec.rb".freeze, "spec/ollama_chat/token_estimator_spec.rb".freeze, "spec/ollama_chat/tool_calling_spec.rb".freeze, "spec/ollama_chat/tools/browse_spec.rb".freeze, "spec/ollama_chat/tools/compute_bmi_spec.rb".freeze, "spec/ollama_chat/tools/copy_to_clipboard_spec.rb".freeze, "spec/ollama_chat/tools/delete_file_spec.rb".freeze, "spec/ollama_chat/tools/directory_structure_spec.rb".freeze, "spec/ollama_chat/tools/execute_grep_spec.rb".freeze, "spec/ollama_chat/tools/execute_ri_spec.rb".freeze, "spec/ollama_chat/tools/file_context_spec.rb".freeze, "spec/ollama_chat/tools/gem_path_lookup_spec.rb".freeze, "spec/ollama_chat/tools/generate_image_spec.rb".freeze, "spec/ollama_chat/tools/generate_password_spec.rb".freeze, "spec/ollama_chat/tools/get_current_weather_spec.rb".freeze, "spec/ollama_chat/tools/get_cve_spec.rb".freeze, "spec/ollama_chat/tools/get_endoflife_spec.rb".freeze, "spec/ollama_chat/tools/get_ghr_spec.rb".freeze, "spec/ollama_chat/tools/get_jira_issue_spec.rb".freeze, "spec/ollama_chat/tools/get_location_spec.rb".freeze, "spec/ollama_chat/tools/get_rfc_spec.rb".freeze, "spec/ollama_chat/tools/get_time_spec.rb".freeze, "spec/ollama_chat/tools/get_url_spec.rb".freeze, "spec/ollama_chat/tools/move_file_spec.rb".freeze, "spec/ollama_chat/tools/open_file_in_editor_spec.rb".freeze, "spec/ollama_chat/tools/paste_from_clipboard_spec.rb".freeze, "spec/ollama_chat/tools/paste_into_editor_spec.rb".freeze, "spec/ollama_chat/tools/patch_file_spec.rb".freeze, "spec/ollama_chat/tools/read_file_spec.rb".freeze, "spec/ollama_chat/tools/resolve_tag_spec.rb".freeze, "spec/ollama_chat/tools/roll_dice_spec.rb".freeze, "spec/ollama_chat/tools/run_tests_spec.rb".freeze, "spec/ollama_chat/tools/search_knowledge_spec.rb".freeze, "spec/ollama_chat/tools/search_web_spec.rb".freeze, "spec/ollama_chat/tools/write_file_spec.rb".freeze, "spec/ollama_chat/utils/analyze_directory_spec.rb".freeze, "spec/ollama_chat/utils/cache_fetcher_spec.rb".freeze, "spec/ollama_chat/utils/fetcher_spec.rb".freeze, "spec/ollama_chat/utils/log_viewer_spec.rb".freeze, "spec/ollama_chat/utils/path_completer_spec.rb".freeze, "spec/ollama_chat/utils/png_metadata_extractor_spec.rb".freeze, "spec/ollama_chat/vim_spec.rb".freeze, "spec/ollama_chat/web_searching_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
17
17
  s.homepage = "https://github.com/flori/ollama_chat".freeze
18
18
  s.licenses = ["MIT".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "OllamaChat - A command-line interface (CLI) for interacting with an Ollama AI model.".freeze, "--main".freeze, "README.md".freeze]
@@ -475,13 +475,13 @@ describe OllamaChat::Commands, protect_env: true do
475
475
 
476
476
  describe 'conversation' do
477
477
  it 'returns :next when input is "/conversation save\\s+(.+)$"' do
478
- expect(chat).to receive(:save_conversation).with('./some_file', clean: false)
479
- expect(chat.handle_input("/conversation save ./some_file")).to eq :next
478
+ expect(chat).to receive(:save_conversation).with('./some_file.jsonl', clean: false)
479
+ expect(chat.handle_input("/conversation save ./some_file.jsonl")).to eq :next
480
480
  end
481
481
 
482
- it 'returns :next when input is "/conversation save -c ./some_file"' do
483
- expect(chat).to receive(:save_conversation).with('./some_file', clean: 1)
484
- expect(chat.handle_input("/conversation save -c ./some_file")).to eq :next
482
+ it 'returns :next when input is "/conversation save -c ./some_file.jsonl"' do
483
+ expect(chat).to receive(:save_conversation).with('./some_file.jsonl', clean: 1)
484
+ expect(chat.handle_input("/conversation save -c ./some_file.jsonl")).to eq :next
485
485
  end
486
486
 
487
487
  it 'returns :next when input is "/conversation save" without path' do
@@ -490,8 +490,8 @@ describe OllamaChat::Commands, protect_env: true do
490
490
  end
491
491
 
492
492
  it 'returns :next when input is "/conversation load\\s+(.+)$"' do
493
- expect(chat).to receive(:load_conversation).with('./some_file')
494
- expect(chat.handle_input("/conversation load ./some_file")).to eq :next
493
+ expect(chat).to receive(:load_conversation).with('./some_file.jsonl')
494
+ expect(chat.handle_input("/conversation load ./some_file.jsonl")).to eq :next
495
495
  end
496
496
 
497
497
  it 'returns :next when input is "/conversation load" without path' do
@@ -21,7 +21,7 @@ describe OllamaChat::Tools::WriteFile do
21
21
  "./tmp/test_write_file_#{Tins::Token.new(bits: 128)}.txt"
22
22
  end
23
23
 
24
- it 'can be executed successfully with overwrite mode' do
24
+ it 'can be executed successfully with overwrite mode on new file' do
25
25
  tool_call = double(
26
26
  'ToolCall',
27
27
  function: double(
@@ -51,7 +51,62 @@ describe OllamaChat::Tools::WriteFile do
51
51
  File.delete(test_write_file) if File.exist?(test_write_file)
52
52
  end
53
53
 
54
- it 'can be executed successfully with append mode' do
54
+ it 'can be executed successfully with overwrite mode on existing file with confirmation' do
55
+ # Pre-create the file
56
+ File.write(test_write_file, 'Old content')
57
+
58
+ expect(chat).to receive(:confirm?).and_return(true)
59
+
60
+ tool_call = double(
61
+ 'ToolCall',
62
+ function: double(
63
+ name: 'write_file',
64
+ arguments: double(
65
+ path: test_write_file,
66
+ content: 'New content',
67
+ mode: 'overwrite'
68
+ )
69
+ )
70
+ )
71
+
72
+ result = described_class.new.execute(tool_call, chat:)
73
+
74
+ json = json_object(result)
75
+ expect(json.success).to eq true
76
+ expect(File.read(test_write_file)).to eq 'New content'
77
+ ensure
78
+ File.delete(test_write_file) if File.exist?(test_write_file)
79
+ end
80
+
81
+ it 'rejects overwrite when user declines confirmation' do
82
+ # Pre-create the file
83
+ File.write(test_write_file, 'Old content')
84
+
85
+ expect(chat).to receive(:confirm?).and_return(false)
86
+
87
+ tool_call = double(
88
+ 'ToolCall',
89
+ function: double(
90
+ name: 'write_file',
91
+ arguments: double(
92
+ path: test_write_file,
93
+ content: 'New content',
94
+ mode: 'overwrite'
95
+ )
96
+ )
97
+ )
98
+
99
+ result = described_class.new.execute(tool_call, chat:)
100
+
101
+ json = json_object(result)
102
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
103
+ expect(json.message).to include('Write rejected')
104
+ expect(File.read(test_write_file)).to eq 'Old content'
105
+ ensure
106
+ File.delete(test_write_file) if File.exist?(test_write_file)
107
+ end
108
+
109
+ it 'can be executed successfully with append mode on existing file' do
55
110
  # First write some initial content
56
111
  initial_content = 'Initial content\n'
57
112
  File.secure_write(test_write_file, initial_content)
@@ -87,6 +142,53 @@ describe OllamaChat::Tools::WriteFile do
87
142
  File.delete(test_write_file) if File.exist?(test_write_file)
88
143
  end
89
144
 
145
+ it 'can be executed successfully with append mode on new file with confirmation' do
146
+ expect(chat).to receive(:confirm?).and_return(true)
147
+
148
+ tool_call = double(
149
+ 'ToolCall',
150
+ function: double(
151
+ name: 'write_file',
152
+ arguments: double(
153
+ path: test_write_file,
154
+ content: 'First content\n',
155
+ mode: 'append'
156
+ )
157
+ )
158
+ )
159
+
160
+ result = described_class.new.execute(tool_call, chat:)
161
+
162
+ json = json_object(result)
163
+ expect(json.success).to eq true
164
+ expect(File.read(test_write_file)).to eq 'First content\n'
165
+ ensure
166
+ File.delete(test_write_file) if File.exist?(test_write_file)
167
+ end
168
+
169
+ it 'rejects append when user declines confirmation for new file' do
170
+ expect(chat).to receive(:confirm?).and_return(false)
171
+
172
+ tool_call = double(
173
+ 'ToolCall',
174
+ function: double(
175
+ name: 'write_file',
176
+ arguments: double(
177
+ path: test_write_file,
178
+ content: 'First content\n',
179
+ mode: 'append'
180
+ )
181
+ )
182
+ )
183
+
184
+ result = described_class.new.execute(tool_call, chat:)
185
+
186
+ json = json_object(result)
187
+ expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
188
+ expect(json.message).to include('Write rejected')
189
+ expect(File.exist?(test_write_file)).to be false
190
+ end
191
+
90
192
  it 'can handle execution errors gracefully when path is not allowed' do
91
193
  tool_call = double(
92
194
  'ToolCall',
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ollama_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.105
4
+ version: 0.0.107
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -530,6 +530,7 @@ extra_rdoc_files:
530
530
  - lib/ollama_chat/prompt_management.rb
531
531
  - lib/ollama_chat/rag_handling.rb
532
532
  - lib/ollama_chat/redis_cache.rb
533
+ - lib/ollama_chat/say.rb
533
534
  - lib/ollama_chat/server_socket.rb
534
535
  - lib/ollama_chat/session_management.rb
535
536
  - lib/ollama_chat/source_fetching.rb
@@ -658,6 +659,7 @@ files:
658
659
  - lib/ollama_chat/prompt_management.rb
659
660
  - lib/ollama_chat/rag_handling.rb
660
661
  - lib/ollama_chat/redis_cache.rb
662
+ - lib/ollama_chat/say.rb
661
663
  - lib/ollama_chat/server_socket.rb
662
664
  - lib/ollama_chat/session_management.rb
663
665
  - lib/ollama_chat/source_fetching.rb