ollama_chat 0.0.136 → 0.0.138

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: e22822d0b02f1f0dad3fe7e9b2902fc727a6dc2d13ffcd192da3fda3980fdefc
4
- data.tar.gz: 2bb58b895a48bceefb16600def20f1ea8bb16deeeabd4a307f7fea27749a9139
3
+ metadata.gz: 170c9e2778ed0d581bda082eebc43d07a6c6e6a6cd99a0d1e0d3634a5281ca9f
4
+ data.tar.gz: e57e2eb4d990f10617e1574ae1f70d22741dd814cf945575cc2cb7fb882dd4be
5
5
  SHA512:
6
- metadata.gz: 41e74251f4318137993e985f97beba5626b9d200b460d827ce04bddfb6dc316df6dae2d78024dd1c7d4486f3b7f4f7febd4e38d666fdf1e5f00f0e3e246b7c70
7
- data.tar.gz: 1cd96acf7bca275337aaaad85c41d65c2bd75101ccfa1f0924d72eda254f4fffe04c019c9c2ee12285df6ea2e91da96cf702753059c0a7e7d7aa4bde8c13b0f4
6
+ metadata.gz: b5e6781664a38632da9d7a4f11900864b86b9e0049fcb66d6084777d0f8b4a85e762a57217c23817c77f0fb365592d5d659e3eab874aa270fa97b52a3e727c45
7
+ data.tar.gz: 6aa806be34d04830f4788dd452d6e8a078e848767978d6bf49d6fef39f3dbd92b3f61fd4dc203c9914491c1ab2d8fa432ff75bc3543bf385dffcda2333acbd83
data/CHANGES.md CHANGED
@@ -1,5 +1,103 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-10-28 v0.0.138
4
+
5
+ ### Features
6
+
7
+ - Added `OllamaChat::Utils::Redact` module with `redact_password` to mask
8
+ credentials in URLs for user-facing display.
9
+ - `memory_restore` now deduplicates entries by checking existing tags before
10
+ adding records, reporting the number of skipped duplicates.
11
+ - `OllamaChat::Utils::AnalyzeDirectory` now returns a `Hash` with `:height` and
12
+ `:entries` keys instead of a bare `Array`. Entry hashes no longer include
13
+ `:type` or `:name`; directories are identified by a trailing `/` in `:path`.
14
+
15
+ ### Fixes
16
+
17
+ - `use_model` now raises `OllamaChat::UnknownModelError` if the previous model
18
+ is missing or blank after a cancelled pull, preventing stale metadata from
19
+ being returned.
20
+ - Fixed `update_collection` to use `Set` for the `seen` variable, resolving a
21
+ latent `NoMethodError` when upstream yields a `Set`.
22
+ - Corrected the error message in `pull_model_unless_present` to reference the
23
+ `model` parameter instead of the potentially stale `@model` instance
24
+ variable.
25
+
26
+ ### Improvements
27
+
28
+ - Applied password redaction to all user-visible URL displays, including
29
+ `Chat#inspect`, `Dialog#connect_message`, `Information#info_rag`,
30
+ `Information#print_welcome`, `Tools::Browse#execute`,
31
+ `Tools::GenerateImage#execute`, and `Fetcher.get`.
32
+ - Standardized `Set` insertion to use the `<<` idiom across `conversation.rb`,
33
+ `http_handling.rb`, `rag_handling.rb`, `generate_image.rb`, and
34
+ `web_searching.rb`.
35
+ - Added `<<` alias to `LinksSet` to support the standard `Set` insertion idiom.
36
+ - `AnalyzeDirectory` now omits the `:children` key from directory entries when
37
+ the array is empty, both during build and pruning.
38
+
39
+ ### Build & CI
40
+
41
+ - Added `lib/ollama_chat/utils/redact.rb` to the gemspec file list.
42
+
43
+ ### Testing
44
+
45
+ - Updated `analyze_directory_spec.rb`, `parsing_spec.rb`, and
46
+ `list_directory_spec.rb` to assert the new `AnalyzeDirectory` return
47
+ structure.
48
+ - Added specs for `redact_password` covering URI/String inputs, passthrough for
49
+ URLs without passwords, and non-mutation of the original object.
50
+ - Added specs for `memory_restore` deduplication logic, covering partial and
51
+ full skip scenarios.
52
+ - Updated `web_searching_spec.rb` to expect `<<` instead of `add` for link
53
+ insertion.
54
+
55
+ ### Documentation
56
+
57
+ - Updated `execute_grep` tool description to explicitly state that the regex
58
+ runs against file contents.
59
+ - Updated YARD documentation for `AnalyzeDirectory` to reflect the new return
60
+ value structure.
61
+
62
+ ## 2026-10-23 v0.0.137
63
+
64
+ ### Fixes
65
+
66
+ - Fixed a double-scheme bug in `OC::URL` default generation by delegating to
67
+ `Ollama.base_url`, ensuring correct handling of `HOST` values that already
68
+ include `http://` or `https://`.
69
+ - Prevented deletion of the `default` collection in `delete_collection`,
70
+ returning a warning feedback instead.
71
+ - Reset `session.current_collection` to `'default'` when the active collection
72
+ is deleted.
73
+
74
+ ### Improvements
75
+
76
+ - Ensured the `default` collection row always exists in the database by
77
+ unioning it with `chat.documents.collections` during `Collection.sync`.
78
+ - Removed four vestigial `refresh_system_prompt` calls from
79
+ `create_collection`, `edit_collection`, `rename_collection`, and
80
+ `delete_collection`, as collection data no longer interpolates into the
81
+ system prompt.
82
+
83
+ ### Build & CI
84
+
85
+ - Updated `ollama-ruby` dependency from **1.25** to **1.26**.
86
+ - Updated `kramdown-ansi` dependency from **~> 0.6** to **~> 0.7**.
87
+ - Updated `gem_hadar` development dependency from **2.20.1** to **2.21.0**.
88
+
89
+ ### Testing
90
+
91
+ - Added specs for `delete_collection` to verify refusal to delete the `default`
92
+ collection, resetting `current_collection` when the active collection is
93
+ deleted, and leaving `current_collection` unchanged for non-active
94
+ collections.
95
+
96
+ ### Documentation
97
+
98
+ - Updated YARD documentation for `refresh_system_prompt` to accurately reflect
99
+ the placeholders used (`persona`, `persona_name`, `runtime_info`).
100
+
3
101
  ## 2026-10-21 v0.0.136
4
102
 
5
103
  ### Features
data/Rakefile CHANGED
@@ -39,7 +39,7 @@ GemHadar do
39
39
  )
40
40
 
41
41
  dependency 'excon', '~> 1.0'
42
- dependency 'ollama-ruby', '~> 1.25'
42
+ dependency 'ollama-ruby', '~> 1.26'
43
43
  dependency 'documentrix', '>= 0.8.0'
44
44
  dependency 'unix_socks', '>= 0.4'
45
45
  dependency 'rss', '~> 0.3'
@@ -47,7 +47,7 @@ GemHadar do
47
47
  dependency 'redis', '~> 5.0'
48
48
  dependency 'mime-types', '~> 3.0'
49
49
  dependency 'reverse_markdown', '~> 3.0'
50
- dependency 'kramdown-ansi', '~> 0.6'
50
+ dependency 'kramdown-ansi', '~> 0.7'
51
51
  dependency 'complex_config', '~> 0.22', '>= 0.22.2'
52
52
  dependency 'tins', '~> 1.61'
53
53
  dependency 'search_ui', '>= 0.2'
@@ -344,7 +344,7 @@ class OllamaChat::Chat
344
344
  #
345
345
  # @return [String] the inspected string
346
346
  def inspect
347
- "#<#{self.class}: #{base_url.to_s}>"
347
+ "#<#{self.class}: #{redact_password(base_url)}>"
348
348
  end
349
349
 
350
350
  # Returns the tool call processor instance.
@@ -86,7 +86,7 @@ module OllamaChat::Conversation
86
86
  selected.merge(remaining)
87
87
  break
88
88
  else
89
- selected.add(choice)
89
+ selected << choice
90
90
  end
91
91
  end
92
92
  end
@@ -51,7 +51,8 @@ class OllamaChat::Database::Models::Collection < Sequel::Model(OllamaChat::DB)
51
51
  #
52
52
  # @param chat [OllamaChat::Chat] the chat instance providing the configuration
53
53
  def self.sync(chat)
54
- chat.documents.collections.each do |name|
54
+ all_collections = (%w[ default ] + chat.documents.collections).to_set
55
+ all_collections.each do |name|
55
56
  where(name: name.to_s).first and next
56
57
  create(
57
58
  name: name.to_s,
@@ -142,7 +142,7 @@ module OllamaChat::Dialog
142
142
  # distinguish the connection type, e.g. `"embedding "` for a dedicated
143
143
  # embedding host (default: `""`)
144
144
  def connect_message(base_url:, prefix: '')
145
- feedback green { "Connecting to #{prefix}ollama #{base_url.to_s.inspect}…" }, type: :info
145
+ feedback green { "Connecting to #{prefix}ollama #{redact_password(base_url).inspect}…" }, type: :info
146
146
  end
147
147
 
148
148
  # Displays a success-level feedback message confirming that a
@@ -61,7 +61,7 @@ module OllamaChat::HTTPHandling
61
61
  # @yield [IO] yields a temporary file handle for the caller to consume.
62
62
  # @return [Object] the result of the block or the fetcher.
63
63
  def get_url(url, headers: nil, cache: nil, remember: true, &block)
64
- remember and links.add(url.to_s)
64
+ remember and links << url.to_s
65
65
  headers = config.request_headers?.to_h | headers
66
66
  OllamaChat::Utils::Fetcher.get(
67
67
  url,
@@ -16,6 +16,7 @@
16
16
  # chat.usage
17
17
  module OllamaChat::Information
18
18
  extend Tins::Concern
19
+ include OllamaChat::Utils::Redact
19
20
 
20
21
  included do
21
22
  include UserAgent
@@ -190,7 +191,7 @@ module OllamaChat::Information
190
191
  def info_rag(output: STDOUT)
191
192
  if embedding.on?
192
193
  if base_url = OC::OLLAMA::EMBEDDING_URL?
193
- output.puts "🔌 Connected to embedding ollama server on: #{base_url.to_s.inspect}"
194
+ output.puts "🔌 Connected to embedding ollama server on: #{redact_password(base_url)}"
194
195
  end
195
196
  output.puts "🗄️ Current RAG model is #{bold{@embedding_model}}"
196
197
  if @embedding_model_options.present?
@@ -220,7 +221,7 @@ module OllamaChat::Information
220
221
  def print_welcome(output: STDOUT)
221
222
  feedback(<<~EOT, output:)
222
223
  💎 Running ollama_chat version: #{bold{OllamaChat::VERSION}}
223
- 🔌 Connected to ollama server version: #{bold{server_version}} on: #{bold{server_url}}
224
+ 🔌 Connected to ollama server version: #{bold{server_version}} on: #{bold{redact_password(server_url)}}
224
225
  EOT
225
226
  end
226
227
 
@@ -23,6 +23,7 @@ class OllamaChat::LinksSet
23
23
  @set.add(x)
24
24
  sync
25
25
  end
26
+ alias << add
26
27
 
27
28
  # Removes all links from the set and synchronizes it with the session.
28
29
  #
@@ -465,7 +465,7 @@ module OllamaChat::ModelHandling
465
465
  if model_metadata = model_present?(model, ollama:)
466
466
  return model_metadata
467
467
  end
468
- raise OllamaChat::UnknownModelError, "unknown model named #{@model.inspect}"
468
+ raise OllamaChat::UnknownModelError, "unknown model named #{model.inspect}"
469
469
  end
470
470
  end
471
471
 
@@ -587,17 +587,25 @@ module OllamaChat::ModelHandling
587
587
  old_model = @model
588
588
 
589
589
  if model.blank?
590
- @model = choose_model('', @model)
590
+ new_model = choose_model('', old_model)
591
591
  else
592
- @model = choose_model(model, config.model.name)
592
+ new_model = choose_model(model, config.model.name)
593
593
  end
594
594
 
595
- if @model_metadata = model_present?(@model, ollama:)
596
- session.update(current_model: @model)
597
- else
598
- session.update(current_model: nil)
595
+ unless model_metadata = model_present?(new_model, ollama:)
596
+ feedback("Model #{bold{new_model}} not found locally.", type: :warn)
597
+ unless confirm?(prompt: "Pull it from remote? (y/n) ", yes: /\Ay/i)
598
+ feedback("Cancelled.", type: :cancel)
599
+ old_model.full? && model_present?(old_model, ollama:) or
600
+ raise OllamaChat::UnknownModelError, "no usable model after cancel"
601
+ return @model_metadata
602
+ end
603
+ model_metadata = pull_model_unless_present(new_model, ollama:)
599
604
  end
600
605
 
606
+ @model, @model_metadata = new_model, model_metadata
607
+ session.update(current_model: @model)
608
+
601
609
  if old_model != @model
602
610
  reconfigure_model_options(profile:, keep_options:)
603
611
  if @model_metadata&.can?('thinking')
@@ -114,7 +114,7 @@ module OC
114
114
 
115
115
  URL = set do
116
116
  description 'Ollama base URL to connect to'
117
- default { 'http://%s' % OC::OLLAMA::HOST }
117
+ default { host = OC::OLLAMA::HOST? and Ollama.base_url(host).to_s }
118
118
  sensitive true
119
119
  decode { URI.parse(_1) if _1.present? }
120
120
  check { value.scheme =~ /\Ahttps?\z/ }
@@ -122,7 +122,7 @@ module OllamaChat::RAGHandling
122
122
  selected.merge(remaining)
123
123
  break
124
124
  else
125
- selected.add(choice)
125
+ selected << choice
126
126
  end
127
127
  end
128
128
  end
@@ -202,7 +202,7 @@ module OllamaChat::RAGHandling
202
202
  selected.merge(remaining)
203
203
  break
204
204
  else
205
- selected.add(choice)
205
+ selected << choice
206
206
  end
207
207
  end
208
208
  end
@@ -213,14 +213,25 @@ module OllamaChat::RAGHandling
213
213
 
214
214
  feedback("Restoring #{selected.size} collection(s): #{selected.join(', ')}")
215
215
 
216
- total = 0
216
+ total = 0
217
+ skipped = 0
217
218
  collections.slice(*selected).sort.each do |col, group|
218
219
  create_memory_collection(col)
219
220
  switch_collection(col) do
221
+ existing_tags = Set.new
222
+ @documents.each_record do |r|
223
+ r.tags.each { |t| existing_tags << t.to_s(link: false) }
224
+ end
225
+
220
226
  group.each do |entry|
227
+ tags = Array(entry['tags'])
228
+ if tags.any? { |t| existing_tags.include?(t) }
229
+ skipped += 1
230
+ next
231
+ end
221
232
  @documents.add(
222
233
  [entry['text']],
223
- tags: Array(entry['tags']),
234
+ tags:,
224
235
  batch_size: 1,
225
236
  source: entry['source']
226
237
  )
@@ -230,9 +241,10 @@ module OllamaChat::RAGHandling
230
241
  end
231
242
 
232
243
  log(:info, 'Memory restored',
233
- data: { file: filename.to_s, collections: selected, records: total })
244
+ data: { file: filename.to_s, collections: selected, records: total, skipped: })
234
245
  feedback(
235
- "Restored #{total} record(s) across #{selected.size} collection(s) from #{filename.to_s.inspect}.",
246
+ "Restored #{total} record(s) across #{selected.size} collection(s) from #{filename.to_s.inspect}." +
247
+ (skipped.zero? ? '' : " (#{skipped} duplicate(s) skipped)."),
236
248
  type: :success
237
249
  )
238
250
  end
@@ -374,7 +386,7 @@ module OllamaChat::RAGHandling
374
386
  source_tags = Hash.new { |h, k| h[k] = Set.new }
375
387
  @documents.records.each do |record|
376
388
  source = record.source.full? or next
377
- sources.add(source)
389
+ sources << source
378
390
  source_tags[source].merge(record.tags)
379
391
  end
380
392
  display_map = {}
@@ -480,7 +492,6 @@ module OllamaChat::RAGHandling
480
492
  col&.update(name: new_collection.to_s)
481
493
  log(:info, "Collection renamed", data: { old_name: current_collection, new_name: new_collection })
482
494
  feedback("Renamed current collection #{current_collection} to #{new_collection}.", type: :info)
483
- refresh_system_prompt
484
495
  rescue Sequel::UniqueConstraintViolation
485
496
  feedback("Renaming to #{new_collection} failed, it already exists in database.", type: :warn)
486
497
  rescue => e
@@ -600,12 +611,12 @@ module OllamaChat::RAGHandling
600
611
  end
601
612
 
602
613
  sources = {}
603
- seen = {}
614
+ seen = Set.new
604
615
  @documents.each_record do |record|
605
616
  source = @documents.normalize_source(record.source) or next
606
617
  if matched_sources.nil? || matched_sources.member?(source)
607
- seen.key?(source) and next
608
- seen[source] = true
618
+ seen.member?(source) and next
619
+ seen << source
609
620
  unless @documents.source_modified?(source)
610
621
  infobar.puts "Source #{source.to_s.inspect} is unmodified. => Skipping."
611
622
  next
@@ -616,8 +627,8 @@ module OllamaChat::RAGHandling
616
627
  end
617
628
 
618
629
  if patterns = col.patterns.full?
619
- new_sources = all_file_set(patterns).map(&:to_s).reject { seen.key?(_1) }.to_set
620
- new_sources.each { seen[_1] = true }
630
+ new_sources = all_file_set(patterns).map(&:to_s).reject { seen.member?(_1) }.to_set
631
+ new_sources.each { seen << _1 }
621
632
  new_sources.each { sources[_1] = [] }
622
633
  end
623
634
 
@@ -678,7 +689,6 @@ module OllamaChat::RAGHandling
678
689
  end
679
690
  feedback("Created collection '#{name}'.", type: :success)
680
691
  log(:info, "Collection created", data: { name:, description:, patterns: })
681
- refresh_system_prompt
682
692
  rescue Sequel::UniqueConstraintViolation
683
693
  feedback("Collection #{name.inspect} already exists.", type: :warn)
684
694
  rescue Sequel::Error => e
@@ -737,7 +747,6 @@ module OllamaChat::RAGHandling
737
747
  end
738
748
  feedback("Updated collection '#{col.name}'.", type: :success)
739
749
  log(:info, "Collection updated", data: { name: col.name, enabled: col.enabled })
740
- refresh_system_prompt
741
750
  rescue Sequel::Error => e
742
751
  feedback("Database error: #{e.message}", type: :warn)
743
752
  end
@@ -761,17 +770,31 @@ module OllamaChat::RAGHandling
761
770
  next
762
771
  end
763
772
 
773
+ if target_name.to_s == 'default'
774
+ feedback(
775
+ "Cannot delete #{target_name.inspect}: it is the default collection. " \
776
+ 'Switch to another collection first.',
777
+ type: :warn
778
+ )
779
+ next
780
+ end
781
+
782
+ destroyed = false
764
783
  if confirm?(prompt: "⚠️ Are you sure you want to permanently delete #{target_name.inspect}? (y/n) ", yes: /\Ay/i)
765
784
  begin
766
785
  col.chat = self
767
786
  col.destroy
787
+ destroyed = true
768
788
  feedback("Deleted collection #{target_name.inspect}.", type: :success)
769
789
  log(:info, "Collection deleted", data: { name: target_name })
770
- refresh_system_prompt
771
790
  rescue Sequel::Error => e
772
791
  feedback("Database error: #{e.message}", type: :warn)
773
792
  rescue => e
774
793
  feedback("Error removing from Documentrix: #{e.message}", type: :warn)
794
+ ensure
795
+ if destroyed && session.current_collection == target_name
796
+ session.update(current_collection: 'default')
797
+ end
775
798
  end
776
799
  else
777
800
  feedback("Deletion denied.", type: :denied)
@@ -42,11 +42,11 @@ module OllamaChat::SystemPromptManagement
42
42
  end
43
43
 
44
44
  # Re-interpolates the current system prompt with fresh dynamic values
45
- # (persona content, collection descriptions, runtime info).
45
+ # (persona content, persona name, runtime info).
46
46
  #
47
47
  # Call this after any operation that mutates the sources baked into
48
- # the system prompt, such as creating, editing, renaming, or deleting
49
- # a collection, or after a persona file has been edited on disk.
48
+ # the system prompt, such as switching personas or toggling runtime
49
+ # info.
50
50
  #
51
51
  # Safe to call when no system prompt is set; returns immediately.
52
52
  def refresh_system_prompt
@@ -60,10 +60,10 @@ class OllamaChat::Tools::Browse
60
60
  result = browse_url(url)
61
61
 
62
62
  message = if result.success?
63
- "Opened #{url.to_s.inspect} in browser."
64
- else
65
- "Could not open #{url.to_s.inspect} in browser."
66
- end
63
+ "Opened #{redact_password(url).inspect} in browser."
64
+ else
65
+ "Could not open #{redact_password(url).inspect} in browser."
66
+ end
67
67
 
68
68
  {
69
69
  success: result.success?,
@@ -7,6 +7,7 @@
7
7
  module OllamaChat::Tools::Concern
8
8
  extend Tins::Concern
9
9
  include OllamaChat::Utils::ValueFormatter
10
+ include OllamaChat::Utils::Redact
10
11
  include OllamaChat::Utils::Backup
11
12
  include OllamaChat::Feedback
12
13
 
@@ -21,9 +21,9 @@ class OllamaChat::Tools::ExecuteGrep
21
21
  function: Tool::Function.new(
22
22
  name:,
23
23
  description: <<~EOT,
24
- Pattern searcher – Runs a regex against all files at path, optionally
25
- case‑insensitive, returning matches (up to max_results). Useful for
26
- quick codebase queries or finding config values.
24
+ Pattern searcher – Runs a regex against all files' contents at path,
25
+ optionally case‑insensitive, returning matches (up to max_results).
26
+ Useful for quick codebase queries or finding config values.
27
27
  EOT
28
28
  parameters: Tool::Function::Parameters.new(
29
29
  type: 'object',
@@ -92,10 +92,10 @@ class OllamaChat::Tools::GenerateImage
92
92
  filename:, subfolder: '', type: 'output', rand:
93
93
  )
94
94
 
95
- chat.links.add(view_url.to_s)
95
+ chat.links << view_url.to_s
96
96
 
97
97
  message = "Image successfully generated! The user can view it here,"\
98
- " give this URL to the user: #{view_url}"
98
+ " give this URL to the user: #{redact_password(view_url)}"
99
99
  {
100
100
  status: 'success',
101
101
  message: ,
@@ -44,13 +44,13 @@ module OllamaChat::Utils::AnalyzeDirectory
44
44
  # directories are included in the tree. Defaults to `false`, which
45
45
  # skips any entry whose base name starts with a dot.
46
46
  #
47
- # @return [Array<Hash>] An array of entry hashes. Each hash contains:
48
- # * `:type` – "file" or "directory"
49
- # * `:name` – Base name of the file/directory
50
- # * `:path` – Absolute path
51
- # * `:depth` – Depth relative to the root (root = 0)
52
- # * `:height` – The maximum depth found in the entire tree
53
- # * `:children` – Array of child entry hashes (only for directories)
47
+ # @return [Hash] A hash with two keys:
48
+ # * `:height` – The maximum depth found in the entire tree.
49
+ # * `:entries` – Array of entry hashes. Each hash contains:
50
+ # * `:path` – Absolute path (directories end with `/`)
51
+ # * `:depth` – Depth relative to the root (root = 0)
52
+ # * `:children` – Array of child entry hashes (directories only,
53
+ # omitted when empty)
54
54
  #
55
55
  # @raise [StandardError] Any exception raised during traversal is
56
56
  # rescued and returned as a hash with `:error` and `:message`
@@ -72,15 +72,16 @@ module OllamaChat::Utils::AnalyzeDirectory
72
72
  height = e[:depth] if e[:depth] > height
73
73
  end
74
74
 
75
- structure_each_entry(entries) { |e| e[:height] = height }
76
-
77
75
  if max_depth && max_depth <= height
78
76
  structure_each_entry(entries) do |e|
79
- e[:children]&.reject! { |c| c[:depth] >= max_depth }
77
+ if children = e[:children].full?
78
+ children.reject! { |c| c[:depth] >= max_depth }
79
+ e.delete(:children) if children.empty?
80
+ end
80
81
  end
81
82
  end
82
83
 
83
- entries
84
+ { height:, entries: }
84
85
  rescue => e
85
86
  { error: e.class, message: e.message }
86
87
  end
@@ -116,19 +117,18 @@ module OllamaChat::Utils::AnalyzeDirectory
116
117
  next if exclude.any? { |e| child.basename.fnmatch?(e.to_s, File::FNM_PATHNAME) }
117
118
 
118
119
  if child.directory?
120
+ children = recurse_generate_structure(
121
+ child, exclude:, suffix:, depth: depth + 1, include_hidden:
122
+ ).full?
119
123
  entries << {
120
- type: 'directory',
121
- name: child.basename.to_s,
122
- path: child.expand_path.to_s,
123
- children: recurse_generate_structure(child, exclude:, suffix:, depth: depth + 1, include_hidden:),
124
- depth:
125
- }
124
+ path: child.expand_path.to_s << ?/,
125
+ depth: ,
126
+ children: ,
127
+ }.compact
126
128
  elsif child.file?
127
129
  # Skip unless suffix matches
128
130
  next if extname.present? && child.extname != extname
129
131
  entries << {
130
- type: 'file',
131
- name: child.basename.to_s,
132
132
  path: child.expand_path.to_s,
133
133
  depth:
134
134
  }
@@ -19,6 +19,7 @@ require 'ollama_chat/utils/cache_fetcher'
19
19
  # # Process the fetched content
20
20
  # end
21
21
  class OllamaChat::Utils::Fetcher
22
+ include OllamaChat::Utils::Redact
22
23
  # A module that extends IO objects with content type metadata and expiration
23
24
  # tracking.
24
25
  #
@@ -154,7 +155,7 @@ class OllamaChat::Utils::Fetcher
154
155
  def self.get(url, headers: {}, **options, &block)
155
156
  cache = options.delete(:cache) and
156
157
  cache = OllamaChat::Utils::CacheFetcher.new(cache)
157
- cache and infobar.puts "Getting #{url.to_s.inspect} via cache…"
158
+ cache and infobar.puts "Getting #{OllamaChat::Utils::Redact.redact_password(url).inspect} via cache…"
158
159
  if result = cache&.get(url, &block)
159
160
  content_type = result&.content_type || 'unknown'
160
161
  infobar.puts "…hit, found #{content_type} content in cache."
@@ -0,0 +1,32 @@
1
+ require 'uri'
2
+
3
+ # Redacts credentials from URLs for safe, user-facing display.
4
+ #
5
+ # Whenever a URL that may carry a password (e.g.
6
+ # `http://user:secret@host`) is about to be printed to the terminal or
7
+ # echoed back to the model, run it through {#redact_password} so the
8
+ # secret is masked as `***`.
9
+ #
10
+ # @example
11
+ # OllamaChat::Utils::Redact.redact_password('http://u:secret@example.com')
12
+ # # => "http://u:***@example.com"
13
+ #
14
+ # The module can also be +include+d to gain {#redact_password} as a
15
+ # private helper on the host class.
16
+ module OllamaChat::Utils::Redact
17
+ module_function
18
+
19
+ # Returns the URL as a string with its password component replaced by
20
+ # `***`. Accepts a +URI+ or a plain +String+ (parsed on the fly). The
21
+ # original object is never mutated: a duplicate is redacted and the
22
+ # result returned as a string. URLs without a password are returned
23
+ # unchanged.
24
+ #
25
+ # @param url [URI, String] the URL whose password should be hidden
26
+ # @return [String] the URL with its password redacted
27
+ def redact_password(url)
28
+ uri = url.is_a?(String) ? URI.parse(url) : url.dup
29
+ uri.password = '***' if uri.password
30
+ uri.to_s
31
+ end
32
+ end
@@ -9,6 +9,7 @@
9
9
  module OllamaChat::Utils
10
10
  end
11
11
 
12
+ require 'ollama_chat/utils/redact'
12
13
  require 'ollama_chat/utils/analyze_directory'
13
14
  require 'ollama_chat/utils/audio_player'
14
15
  require 'ollama_chat/utils/backup'
@@ -5,7 +5,7 @@
5
5
  # by hand; it is rewritten on every `rake version` invocation.
6
6
  module OllamaChat
7
7
  # The current version of the OllamaChat gem as a String.
8
- VERSION = '0.0.136'
8
+ VERSION = '0.0.138'
9
9
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
10
10
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
11
11
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -31,7 +31,7 @@ module OllamaChat::WebSearching
31
31
  log(:info, "Web search performed", data: {
32
32
  search_engine: engine, query:, results: results.size
33
33
  })
34
- results.each { |url| links.add(url) }
34
+ results.each { |url| links << url }
35
35
  end
36
36
  else
37
37
  feedback("Search engine #{bold{engine}} not implemented!", type: :warn)