ollama_chat 0.0.49 → 0.0.51

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: 5636cead23d0a8b39d9a170149f20826553fb038be13dd1b1ed9eb1eef21cce3
4
- data.tar.gz: 06de0d2ea5695997b9284ff656ea307a8d4da6e9638c09a60515a3092f4c220e
3
+ metadata.gz: 6fb9a8c4d866252bfa8ace6399ae3a9c7b72ace633c5b98b7a5c40543be105ba
4
+ data.tar.gz: d899a33db800219a4874d1db8da428dfacf14d6013fd2a2b76f58c12c7cece82
5
5
  SHA512:
6
- metadata.gz: e6ba37eaeac77525769df31a8b068e2a8c87bc4bf5ca3e6422df7443e3e8f46a82b0fe95f344795eec5f938a9661042585f37fdd2d9737f7f0440d33f6822a57
7
- data.tar.gz: 85a386b337e1f3a73fc6099d85ee954e458db117c97a64e48851c06278a7f3c02d2dfd11d8a68c41cc287145c20b346e29f8af3b4ba1e8e87a857315fa691694
6
+ metadata.gz: 303e0bda16f1cf0eba110271a63e40eb4563a90af5cb152465ba51bde1a3dcfaf3b317210df77cfcdc78541f1c0c0bbcf0346edfd134821928922083cc37cddd
7
+ data.tar.gz: b05f2ead74ccb3e534beb407203fb26d6070715337255ab09b451b52fd355a740c0a3ceaad348091a5e302b11db4d56cd9c3c71e0e207268c4fc0a93d2231b8f
data/CHANGES.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-01-06 v0.0.51
4
+
5
+ - Added `/input` command to allow users to select files using glob patterns
6
+ like `src/*.c` or `**/*.rb`
7
+ - Implemented `input` method in `OllamaChat::Chat` class using `Dir.glob` for
8
+ pattern matching
9
+ - Integrated with `OllamaChat::Utils::Chooser` for interactive file selection
10
+ - Added `/input` command to help text in `OllamaChat::Information` module
11
+ - Supports default pattern `**/*` when no argument is provided
12
+ - Returns file content as string for use in chat conversations
13
+ - Integrated `/context` command in `OllamaChat::Chat` with pattern matching
14
+ support
15
+ - Updated `display_chat_help_message` to document `/context [pattern...]`
16
+ command
17
+ - Modified `README.md` to include `/context` command in help section
18
+ - Introduced `context_spook` **~> 1.1** dependency
19
+ - Created `OllamaChat::InputContent` module with `input`, `choose_filename`,
20
+ and `context_spook` methods
21
+ - Extracted input functionality from `Chat` class to `InputContent` module for
22
+ better organization
23
+ - Support both static context files (`.contexts/*.rb`) and dynamic pattern
24
+ matching (`lib/**/*.rb`)
25
+ - Implemented proper content handling with `@parse_content = false` to prevent
26
+ parsing of URLs
27
+ - Used `File.file?` instead of `File.stat(it).file?` for cleaner file checking
28
+ - Support multiple space-separated patterns in `/context` command
29
+ - Added comprehensive YARD documentation for all new methods and parameters
30
+ - Improved pipe command handling with dynamic command resolution
31
+ - Moved `RedisCache` class comment
32
+
33
+ ## 2026-01-03 v0.0.50
34
+
35
+ - Use Redis-based expiring cache implementation with the new
36
+ `OllamaChat::RedisCache` class
37
+ - Replaced `Documentrix::Documents::RedisCache` with `OllamaChat::RedisCache`
38
+ in the `setup_cache` method
39
+ - Updated `GemHadar` development dependency to version **2.16.3**
40
+ - Integrated changelog generation capability into GemHadar configuration
41
+ - Updated Redis image to valkey version **8.1.5** with latest bug fixes and
42
+ improvements
43
+ - Improved file path resolution using `Pathname` for better handling of
44
+ relative paths and home directory shortcuts
45
+ - Enhanced test suite configuration and stubbing for more consistent test
46
+ execution
47
+ - Updated Ruby image tag to stable **4.0**-alpine
48
+ - Added `lib/ollama_chat/redis_cache.rb` to `extra_rdoc_files` and `files`
49
+ lists in gemspec
50
+ - Added `spec/ollama_chat/redis_cache_spec.rb` to `test_files` list in gemspec
51
+ - Removed duplicate entry for `lib/ollama_chat/redis_cache.rb` from `files`
52
+ list
53
+ - Improved test setup for source fetching with explicit configuration file
54
+
3
55
  ## 2025-12-24 v0.0.49
4
56
 
5
57
  - Updated `unix_socks` gem dependency from **~> 0.2** to **~> 0.3**
data/README.md CHANGED
@@ -180,6 +180,8 @@ The following commands can be given inside the chat, if prefixed by a `/`:
180
180
  /links [clear] display (or clear) links used in the chat
181
181
  /save filename store conversation messages
182
182
  /load filename load conversation messages
183
+ /input [pattern] select and read content from a file (default: **/*)
184
+ /context [pattern...] collect context with glob patterns
183
185
  /output filename save last response to filename
184
186
  /pipe command write last response to command's stdin
185
187
  /vim insert the last message into a vim server
data/Rakefile CHANGED
@@ -28,6 +28,10 @@ GemHadar do
28
28
 
29
29
  readme 'README.md'
30
30
 
31
+ changelog do
32
+ filename 'CHANGES.md'
33
+ end
34
+
31
35
  required_ruby_version '>= 3.2'
32
36
 
33
37
  executables << 'ollama_chat' << 'ollama_chat_send'
@@ -36,10 +40,6 @@ GemHadar do
36
40
  'static.yml' => {}
37
41
  )
38
42
 
39
- changelog do
40
- filename 'CHANGES.md'
41
- end
42
-
43
43
  dependency 'excon', '~> 1.0'
44
44
  dependency 'ollama-ruby', '~> 1.18'
45
45
  dependency 'documentrix', '>= 0.0.4'
@@ -58,6 +58,7 @@ GemHadar do
58
58
  dependency 'bigdecimal', '~> 3.1'
59
59
  dependency 'csv', '~> 3.0'
60
60
  dependency 'const_conf', '~> 0.3'
61
+ dependency 'context_spook', '~> 1.1'
61
62
  development_dependency 'all_images', '~> 0.6'
62
63
  development_dependency 'rspec', '~> 3.2'
63
64
  development_dependency 'kramdown', '~> 2.0'
data/docker-compose.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  services:
2
2
  redis:
3
3
  container_name: redis
4
- image: valkey/valkey:8.1.3-alpine
4
+ image: valkey/valkey:8.1.5-alpine
5
5
  restart: unless-stopped
6
6
  ports: [ "127.0.0.1:9736:6379" ]
7
7
  volumes:
@@ -15,6 +15,7 @@ require 'pdf/reader'
15
15
  require 'csv'
16
16
  require 'socket'
17
17
  require 'shellwords'
18
+ require 'context_spook'
18
19
 
19
20
  # A chat client for interacting with Ollama models through a terminal
20
21
  # interface.
@@ -49,6 +50,7 @@ class OllamaChat::Chat
49
50
  include OllamaChat::ServerSocket
50
51
  include OllamaChat::KramdownANSI
51
52
  include OllamaChat::Conversation
53
+ include OllamaChat::InputContent
52
54
 
53
55
  # Initializes a new OllamaChat::Chat instance with the given command-line
54
56
  # arguments.
@@ -328,6 +330,14 @@ class OllamaChat::Chat
328
330
  when %r(^/web\s+(?:(\d+)\s+)?(.+))
329
331
  @parse_content = false
330
332
  web($1, $2)
333
+ when %r(^/input(?:\s+(.+))?$)
334
+ @parse_content = false
335
+ input($1) or :next
336
+ when %r(^/context(?:\s+(.+))?$)
337
+ arg = $1
338
+ arg and patterns = arg.scan(/(\S+)/).flatten
339
+ @parse_content = false
340
+ context_spook(patterns) or :next
331
341
  when %r(^/save\s+(.+)$)
332
342
  save_conversation($1)
333
343
  :next
@@ -749,12 +759,12 @@ class OllamaChat::Chat
749
759
  # The setup_cache method initializes and returns a Redis cache instance with
750
760
  # expiring keys if a Redis URL is configured.
751
761
  #
752
- # @return [ Documentrix::Documents::RedisCache, nil ] the configured Redis
762
+ # @return [ OllamaChat::RedisCache, nil ] the configured Redis
753
763
  # cache instance or nil if no URL is set.
754
764
  def setup_cache
755
765
  if url = config.redis.expiring.url?
756
766
  ex = config.redis.expiring.ex?.to_i
757
- Documentrix::Documents::RedisCache.new(
767
+ OllamaChat::RedisCache.new(
758
768
  prefix: 'Expiring-',
759
769
  url:,
760
770
  ex:
@@ -147,6 +147,8 @@ module OllamaChat::Information
147
147
  /links [clear] display (or clear) links used in the chat
148
148
  /save filename store conversation messages
149
149
  /load filename load conversation messages
150
+ /input [pattern] select and read content from a file (default: **/*)
151
+ /context [pattern...] collect context with glob patterns
150
152
  /output filename save last response to filename
151
153
  /pipe command write last response to command's stdin
152
154
  /vim insert the last message into a vim server
@@ -0,0 +1,91 @@
1
+ # A module that provides input content processing functionality for OllamaChat.
2
+ #
3
+ # The InputContent module encapsulates methods for reading and returning
4
+ # content from selected files, selecting files from a list of matching files,
5
+ # and collecting project context using the context_spook library. It supports
6
+ # interactive file selection and context collection for enhancing chat
7
+ # interactions with local or remote content.
8
+ module OllamaChat::InputContent
9
+ # The input method reads and returns the content of a selected file.
10
+ #
11
+ # This method searches for files matching the given pattern and presents them
12
+ # in an interactive chooser menu. If a file is selected, its content is read
13
+ # and returned. If the user chooses to exit or no file is selected, the
14
+ # method returns nil.
15
+ #
16
+ # @param pattern [ String ] the glob pattern to search for files (defaults to '**/*')
17
+ #
18
+ # @return [ String, nil ] the content of the selected file or nil if no file
19
+ # was chosen
20
+ def input(pattern)
21
+ pattern ||= '**/*'
22
+ if filename = choose_filename(pattern)
23
+ File.read(filename)
24
+ end
25
+ end
26
+
27
+ # The choose_filename method selects a file from a list of matching files.
28
+ #
29
+ # This method searches for files matching the given glob pattern, presents
30
+ # them in an interactive chooser menu, and returns the selected filename. If
31
+ # the user chooses to exit or no file is selected, the method returns nil.
32
+ #
33
+ # @param pattern [ String ] the glob pattern to search for files (defaults to '**/*')
34
+ #
35
+ # @return [ String, nil ] the path to the selected file or nil if no file was chosen
36
+ def choose_filename(pattern)
37
+ files = Dir.glob(pattern).select { File.file?(it) }
38
+ files.unshift('[EXIT]')
39
+ case chosen = OllamaChat::Utils::Chooser.choose(files)
40
+ when '[EXIT]', nil
41
+ STDOUT.puts "Exiting chooser."
42
+ return
43
+ else
44
+ chosen
45
+ end
46
+ end
47
+
48
+ # The context_spook method collects and returns project context using the
49
+ # context_spook library.
50
+ #
51
+ # This method generates structured project context that can be used to
52
+ # provide AI models with comprehensive information about the codebase. It
53
+ # supports both:
54
+ # - On-the-fly pattern matching for specific file patterns
55
+ # - Loading context from predefined definition files in ./.contexts/
56
+ #
57
+ # When patterns are provided, it collects files matching the glob patterns
58
+ # and generates context data including file contents, sizes, and metadata.
59
+ # When no patterns are provided, it loads the default context definition
60
+ # file.
61
+ #
62
+ # @param patterns [Array<String>, nil] Optional array of glob patterns to
63
+ # filter files
64
+ # @return [String, nil] JSON string of context data or nil if no context
65
+ # could be generated
66
+ #
67
+ # @example Collect context for Ruby files only
68
+ # context_spook(['lib/**/*.rb'])
69
+ #
70
+ # @example Collect context for multiple patterns
71
+ # context_spook(['lib/**/*.rb', 'spec/**/*.rb'])
72
+ #
73
+ # @example Load default context
74
+ # context_spook(nil)
75
+ def context_spook(patterns)
76
+ if patterns
77
+ ContextSpook::generate_context(verbose: false) do |context|
78
+ context do
79
+ Dir.glob(patterns).each do |filename|
80
+ File.file?(filename) or next
81
+ file filename
82
+ end
83
+ end
84
+ end.to_json
85
+ else
86
+ if context_filename = choose_filename('.contexts/*.rb')
87
+ ContextSpook.generate_context(context_filename, verbose: false).to_json
88
+ end
89
+ end
90
+ end
91
+ end
@@ -78,10 +78,11 @@ module OllamaChat::MessageOutput
78
78
  # @return [ TrueClass ] returns true if the file was successfully written
79
79
  # @return [ nil ] returns nil if the user chose not to overwrite or if an error occurred
80
80
  def attempt_to_write_file(filename, message)
81
- if !File.exist?(filename) ||
82
- ask?(prompt: "File #{filename.inspect} already exists, overwrite? (y/n) ") =~ /\Ay/i
81
+ path = Pathname.new(filename.to_s).expand_path
82
+ if !path.exist? ||
83
+ ask?(prompt: "File #{path.to_s.inspect} already exists, overwrite? (y/n) ") =~ /\Ay/i
83
84
  then
84
- File.open(filename, ?w) do |output|
85
+ File.open(path, ?w) do |output|
85
86
  output.write(message.content)
86
87
  end
87
88
  else
@@ -0,0 +1,158 @@
1
+ require 'redis'
2
+
3
+ module OllamaChat
4
+ # A Redis-based cache implementation for OllamaChat
5
+ #
6
+ # This class provides a wrapper around Redis that offers a simple key-value
7
+ # caching interface with support for expiration times and namespace isolation.
8
+ # It's designed to be used as a cache backend for various components in the
9
+ # OllamaChat application.
10
+ #
11
+ # @example Basic usage
12
+ # cache = OllamaChat::RedisCache.new(prefix: 'myapp-', url: 'redis://localhost:6379')
13
+ # cache['key'] = 'value'
14
+ # value = cache['key']
15
+ # cache.delete('key')
16
+ #
17
+ # @example With expiration
18
+ # cache = OllamaChat::RedisCache.new(prefix: 'expiring-', url: 'redis://localhost:6379', ex: 3600)
19
+ # cache['key'] = 'value' # Automatically expires in 1 hour
20
+ #
21
+ # @example Iteration
22
+ # cache.each do |key, value|
23
+ # puts "#{key}: #{value}"
24
+ # end
25
+ #
26
+ # @example Cache management
27
+ # cache.clear # Remove all entries with this prefix
28
+ # size = cache.size # Get number of entries
29
+ class RedisCache
30
+ include Enumerable
31
+
32
+ # Initializes a new RedisCache instance
33
+ #
34
+ # @param prefix [String] The prefix to use for all keys in this cache
35
+ # @param url [String, nil] The Redis connection URL (defaults to ENV['REDIS_URL'])
36
+ # @param ex [Integer, nil] Default expiration time in seconds
37
+ #
38
+ # @raise [ArgumentError] If no Redis URL is provided
39
+ def initialize(prefix:, url: ENV['REDIS_URL'], ex: nil)
40
+ @prefix = prefix
41
+ @url = url
42
+ @ex = ex
43
+ raise ArgumentError, 'require redis url' unless @url
44
+ end
45
+
46
+ # Returns the Redis connection instance
47
+ #
48
+ # This method lazily initializes the Redis connection to avoid
49
+ # establishing connections until they're actually needed.
50
+ #
51
+ # @return [Redis] The Redis client instance
52
+ def redis
53
+ @redis ||= Redis.new(url: @url)
54
+ end
55
+
56
+ # Retrieves a value from the cache by key
57
+ #
58
+ # @param key [String] The cache key to retrieve
59
+ # @return [String, nil] The cached value or nil if not found
60
+ def [](key)
61
+ value = redis.get(pre(key))
62
+ value
63
+ end
64
+
65
+ # Stores a value in the cache with the given key
66
+ #
67
+ # @param key [String] The cache key
68
+ # @param value [String] The value to cache
69
+ # @return [String] The cached value
70
+ def []=(key, value)
71
+ set(key, value)
72
+ end
73
+
74
+ # Stores a value in the cache with optional expiration
75
+ #
76
+ # @param key [String] The cache key
77
+ # @param value [String] The value to cache
78
+ # @param ex [Integer, nil] Expiration time in seconds (overrides default)
79
+ # @return [String] The cached value
80
+ def set(key, value, ex: nil)
81
+ ex ||= @ex
82
+ if !ex.nil? && ex < 1
83
+ redis.del(pre(key))
84
+ else
85
+ redis.set(pre(key), value, ex:)
86
+ end
87
+ value
88
+ end
89
+
90
+ # Gets the time-to-live for a key
91
+ #
92
+ # @param key [String] The cache key
93
+ # @return [Integer] The remaining time-to-live in seconds, or -1 if not found
94
+ def ttl(key)
95
+ redis.ttl(pre(key))
96
+ end
97
+
98
+ # Checks if a key exists in the cache
99
+ #
100
+ # @param key [String] The cache key to check
101
+ # @return [Boolean] true if the key exists, false otherwise
102
+ def key?(key)
103
+ !!redis.exists?(pre(key))
104
+ end
105
+
106
+ # Deletes a key from the cache
107
+ #
108
+ # @param key [String] The cache key to delete
109
+ # @return [Boolean] true if the key was deleted, false if it didn't exist
110
+ def delete(key)
111
+ redis.del(pre(key)) == 1
112
+ end
113
+
114
+ # Gets the number of entries in the cache
115
+ #
116
+ # @return [Integer] The number of entries
117
+ def size
118
+ s = 0
119
+ redis.scan_each(match: "#{@prefix}*") { |key| s += 1 }
120
+ s
121
+ end
122
+
123
+ # Clears all entries from the cache with this prefix
124
+ #
125
+ # @return [OllamaChat::RedisCache] Returns self for chaining
126
+ def clear
127
+ redis.scan_each(match: "#{@prefix}*") { |key| redis.del(key) }
128
+ self
129
+ end
130
+
131
+ # Iterates over all entries in the cache
132
+ #
133
+ # @yield [key, value] Yields each key-value pair
134
+ # @return [OllamaChat::RedisCache] Returns self for chaining
135
+ def each(&block)
136
+ redis.scan_each(match: "#{@prefix}*") { |key| block.(key, self[unpre(key)]) }
137
+ self
138
+ end
139
+
140
+ private
141
+
142
+ # Prepends the prefix to a key
143
+ #
144
+ # @param key [String] The key to prefix
145
+ # @return [String] The prefixed key
146
+ def pre(key)
147
+ [ @prefix, key ].join
148
+ end
149
+
150
+ # Removes the prefix from a key
151
+ #
152
+ # @param key [String] The prefixed key
153
+ # @return [String] The key without prefix
154
+ def unpre(key)
155
+ key.sub(/\A#@prefix/, '')
156
+ end
157
+ end
158
+ end
@@ -1,6 +1,6 @@
1
1
  module OllamaChat
2
2
  # OllamaChat version
3
- VERSION = '0.0.49'
3
+ VERSION = '0.0.51'
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
@@ -15,6 +15,7 @@ require 'documentrix'
15
15
  require 'unix_socks'
16
16
  require 'ollama_chat/version'
17
17
  require 'ollama_chat/utils'
18
+ require 'ollama_chat/redis_cache'
18
19
  require 'ollama_chat/message_format'
19
20
  require 'ollama_chat/ollama_chat_config'
20
21
  require 'ollama_chat/follow_chat'
@@ -35,5 +36,6 @@ require 'ollama_chat/history'
35
36
  require 'ollama_chat/server_socket'
36
37
  require 'ollama_chat/kramdown_ansi'
37
38
  require 'ollama_chat/conversation'
39
+ require 'ollama_chat/input_content'
38
40
  require 'ollama_chat/env_config'
39
41
  require 'ollama_chat/chat'
data/ollama_chat.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: ollama_chat 0.0.49 ruby lib
2
+ # stub: ollama_chat 0.0.51 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "ollama_chat".freeze
6
- s.version = "0.0.49".freeze
6
+ s.version = "0.0.51".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,19 +12,19 @@ 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]
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/conversation.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/env_config.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.rb".freeze, "lib/ollama_chat/information.rb".freeze, "lib/ollama_chat/kramdown_ansi.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/ollama_chat_config.rb".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/utils.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/file_argument.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, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_chat_send".freeze, "config/searxng/settings.yml".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/conversation.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/env_config.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.rb".freeze, "lib/ollama_chat/information.rb".freeze, "lib/ollama_chat/kramdown_ansi.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/ollama_chat_config.rb".freeze, "lib/ollama_chat/ollama_chat_config/default_config.yml".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/utils.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/file_argument.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/duckduckgo.html".freeze, "spec/assets/example.atom".freeze, "spec/assets/example.csv".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/kitten.jpg".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/follow_chat_spec.rb".freeze, "spec/ollama_chat/information_spec.rb".freeze, "spec/ollama_chat/kramdown_ansi_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/server_socket_spec.rb".freeze, "spec/ollama_chat/source_fetching_spec.rb".freeze, "spec/ollama_chat/switches_spec.rb".freeze, "spec/ollama_chat/utils/cache_fetcher_spec.rb".freeze, "spec/ollama_chat/utils/fetcher_spec.rb".freeze, "spec/ollama_chat/utils/file_argument_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/conversation.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/env_config.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.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/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/ollama_chat_config.rb".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/redis_cache.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/utils.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/file_argument.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, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_chat_send".freeze, "config/searxng/settings.yml".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/conversation.rb".freeze, "lib/ollama_chat/dialog.rb".freeze, "lib/ollama_chat/document_cache.rb".freeze, "lib/ollama_chat/env_config.rb".freeze, "lib/ollama_chat/follow_chat.rb".freeze, "lib/ollama_chat/history.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/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/ollama_chat_config.rb".freeze, "lib/ollama_chat/ollama_chat_config/default_config.yml".freeze, "lib/ollama_chat/parsing.rb".freeze, "lib/ollama_chat/redis_cache.rb".freeze, "lib/ollama_chat/server_socket.rb".freeze, "lib/ollama_chat/source_fetching.rb".freeze, "lib/ollama_chat/switches.rb".freeze, "lib/ollama_chat/think_control.rb".freeze, "lib/ollama_chat/utils.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/file_argument.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/duckduckgo.html".freeze, "spec/assets/example.atom".freeze, "spec/assets/example.csv".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/kitten.jpg".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/follow_chat_spec.rb".freeze, "spec/ollama_chat/information_spec.rb".freeze, "spec/ollama_chat/kramdown_ansi_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/source_fetching_spec.rb".freeze, "spec/ollama_chat/switches_spec.rb".freeze, "spec/ollama_chat/utils/cache_fetcher_spec.rb".freeze, "spec/ollama_chat/utils/fetcher_spec.rb".freeze, "spec/ollama_chat/utils/file_argument_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]
20
20
  s.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze)
21
21
  s.rubygems_version = "4.0.2".freeze
22
22
  s.summary = "A command-line interface (CLI) for interacting with an Ollama AI model.".freeze
23
- s.test_files = ["spec/assets/example.rb".freeze, "spec/ollama_chat/chat_spec.rb".freeze, "spec/ollama_chat/clipboard_spec.rb".freeze, "spec/ollama_chat/follow_chat_spec.rb".freeze, "spec/ollama_chat/information_spec.rb".freeze, "spec/ollama_chat/kramdown_ansi_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/server_socket_spec.rb".freeze, "spec/ollama_chat/source_fetching_spec.rb".freeze, "spec/ollama_chat/switches_spec.rb".freeze, "spec/ollama_chat/utils/cache_fetcher_spec.rb".freeze, "spec/ollama_chat/utils/fetcher_spec.rb".freeze, "spec/ollama_chat/utils/file_argument_spec.rb".freeze, "spec/ollama_chat/web_searching_spec.rb".freeze, "spec/spec_helper.rb".freeze]
23
+ s.test_files = ["spec/assets/example.rb".freeze, "spec/ollama_chat/chat_spec.rb".freeze, "spec/ollama_chat/clipboard_spec.rb".freeze, "spec/ollama_chat/follow_chat_spec.rb".freeze, "spec/ollama_chat/information_spec.rb".freeze, "spec/ollama_chat/kramdown_ansi_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/source_fetching_spec.rb".freeze, "spec/ollama_chat/switches_spec.rb".freeze, "spec/ollama_chat/utils/cache_fetcher_spec.rb".freeze, "spec/ollama_chat/utils/fetcher_spec.rb".freeze, "spec/ollama_chat/utils/file_argument_spec.rb".freeze, "spec/ollama_chat/web_searching_spec.rb".freeze, "spec/spec_helper.rb".freeze]
24
24
 
25
25
  s.specification_version = 4
26
26
 
27
- s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.16.2".freeze])
27
+ s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.16.3".freeze])
28
28
  s.add_development_dependency(%q<all_images>.freeze, ["~> 0.6".freeze])
29
29
  s.add_development_dependency(%q<rspec>.freeze, ["~> 3.2".freeze])
30
30
  s.add_development_dependency(%q<kramdown>.freeze, ["~> 2.0".freeze])
@@ -50,4 +50,5 @@ Gem::Specification.new do |s|
50
50
  s.add_runtime_dependency(%q<bigdecimal>.freeze, ["~> 3.1".freeze])
51
51
  s.add_runtime_dependency(%q<csv>.freeze, ["~> 3.0".freeze])
52
52
  s.add_runtime_dependency(%q<const_conf>.freeze, ["~> 0.3".freeze])
53
+ s.add_runtime_dependency(%q<context_spook>.freeze, ["~> 1.1".freeze])
53
54
  end
@@ -1,8 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe OllamaChat::Chat, protect_env: true do
4
+ use_default_config = -> a {
5
+ a << '-f' << 'lib/ollama_chat/ollama_chat_config/default_config.yml'
6
+ }
7
+
4
8
  let :argv do
5
- %w[ -C test ]
9
+ use_default_config.(%w[ -C test ])
6
10
  end
7
11
 
8
12
  before do
@@ -94,7 +98,7 @@ describe OllamaChat::Chat, protect_env: true do
94
98
  end
95
99
 
96
100
  it 'returns :next when input is "/model"' do
97
- expect(chat).to receive(:choose_model).with('', 'llama3.1')
101
+ expect(chat).to receive(:choose_model).and_return 'llama3.1'
98
102
  expect(chat.handle_input("/model")).to eq :next
99
103
  end
100
104
 
@@ -226,7 +230,7 @@ describe OllamaChat::Chat, protect_env: true do
226
230
  connect_to_ollama_server(instantiate: false)
227
231
 
228
232
  let :argv do
229
- %w[ -C test -c ] << asset('conversation.json')
233
+ use_default_config.(%w[ -C test -c ] << asset('conversation.json'))
230
234
  end
231
235
 
232
236
  it 'dispays the last exchange of the converstation' do
@@ -243,7 +247,7 @@ describe OllamaChat::Chat, protect_env: true do
243
247
  context 'with MemoryCache' do
244
248
 
245
249
  let :argv do
246
- %w[ -M ]
250
+ use_default_config.(%w[ -M ])
247
251
  end
248
252
 
249
253
  it 'can use MemoryCache' do
@@ -265,7 +269,7 @@ describe OllamaChat::Chat, protect_env: true do
265
269
  connect_to_ollama_server(instantiate: false)
266
270
 
267
271
  let :argv do
268
- %w[ -C test -D ] << asset('example.html')
272
+ use_default_config.(%w[ -C test -D ] << asset('example.html'))
269
273
  end
270
274
 
271
275
  it 'Adds documents passed to app via -D option' do
@@ -17,10 +17,10 @@ describe OllamaChat::MessageOutput do
17
17
  end
18
18
 
19
19
  it 'pipe can write to command stdin' do
20
- expect(STDERR).to receive(:puts).with(/No response available to output to pipe command "true"/)
21
- expect(chat.pipe('true')).to be_nil
20
+ expect(STDERR).to receive(:puts).with(/No response available to output to pipe command ".*true.*"/)
21
+ expect(chat.pipe(`which true`)).to be_nil
22
22
  chat.instance_variable_get(:@messages).load_conversation(asset('conversation.json'))
23
- expect(STDOUT).to receive(:puts).with(/Last response was piped to \"true\"./)
24
- expect(chat.pipe('true')).to eq chat
23
+ expect(STDOUT).to receive(:puts).with(/Last response was piped to \".*true.*\"./)
24
+ expect(chat.pipe(`which true`)).to eq chat
25
25
  end
26
26
  end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe OllamaChat::RedisCache do
4
+ let :prefix do
5
+ 'test-'
6
+ end
7
+
8
+ let :cache do
9
+ described_class.new(prefix:, url: 'something').expose
10
+ end
11
+
12
+ it 'can be instantiated' do
13
+ expect(cache).to be_a described_class
14
+ end
15
+
16
+ it 'raises ArgumentError if url is missing' do
17
+ expect {
18
+ described_class.new prefix:, url: nil
19
+ }.to raise_error ArgumentError
20
+ end
21
+
22
+ context 'test redis interactions' do
23
+ let :redis do
24
+ double('Redis')
25
+ end
26
+
27
+ before do
28
+ allow_any_instance_of(described_class).to receive(:redis).and_return(redis)
29
+ end
30
+
31
+ it 'has Redis client' do
32
+ expect(cache.redis).to eq redis
33
+ end
34
+
35
+ it 'can get a key' do
36
+ key = 'foo'
37
+ expect(redis).to receive(:get).with(prefix + key).and_return 'some_value'
38
+ expect(cache[key]).to eq 'some_value'
39
+ end
40
+
41
+ it 'can set a value for a key' do
42
+ key, value = 'foo', 'some_value'
43
+ expect(redis).to receive(:set).with(prefix + key, value, ex: nil)
44
+ cache[key] = value
45
+ end
46
+
47
+ it 'can set a value for a key with ttl' do
48
+ cache = described_class.new prefix:, url: 'something', ex: 3_600
49
+ key, value = 'foo', 'some_value'
50
+ expect(redis).to receive(:set).with(prefix + key, value, ex: 3_600)
51
+ cache[key] = value
52
+ expect(redis).to receive(:ttl).with(prefix + key).and_return 3_600
53
+ expect(cache.ttl(key)).to eq 3_600
54
+ end
55
+
56
+ it 'can determine if key exists' do
57
+ key = 'foo'
58
+ expect(redis).to receive(:exists?).with(prefix + key).and_return(false, true)
59
+ expect(cache.key?('foo')).to eq false
60
+ expect(cache.key?('foo')).to eq true
61
+ end
62
+
63
+ it 'can delete' do
64
+ key = 'foo'
65
+ expect(redis).to receive(:del).with(prefix + key).and_return 1
66
+ expect(cache.delete(key)).to eq true
67
+ expect(redis).to receive(:del).with(prefix + key).and_return 0
68
+ expect(cache.delete(key)).to eq false
69
+ end
70
+
71
+ it 'can iterate over keys, values' do
72
+ key, value = 'foo', 'some_value'
73
+ expect(redis).to receive(:set).with(prefix + key, value, ex: nil)
74
+ cache[key] = value
75
+ expect(redis).to receive(:scan_each).with(match: "#{prefix}*").
76
+ and_yield("#{prefix}foo")
77
+ expect(redis).to receive(:get).with(prefix + key).and_return('some_value')
78
+ cache.each do |k, v|
79
+ expect(k).to eq prefix + key
80
+ expect(v).to eq value
81
+ end
82
+ end
83
+
84
+ it 'returns size' do
85
+ expect(redis).to receive(:scan_each).with(match: "#{prefix}*").
86
+ and_yield("#{prefix}foo").
87
+ and_yield("#{prefix}bar").
88
+ and_yield("#{prefix}baz")
89
+ expect(cache.size).to eq 3
90
+ end
91
+
92
+ it 'can clear' do
93
+ expect(redis).to receive(:scan_each).with(match: 'test-*').and_yield(
94
+ 'test-foo'
95
+ )
96
+ expect(redis).to receive(:del).with('test-foo')
97
+ expect(cache.clear).to eq cache
98
+ end
99
+
100
+ it 'can iterate over keys under a prefix' do
101
+ expect(redis).to receive(:scan_each).with(match: 'test-*')
102
+ cache.to_a
103
+ end
104
+
105
+ it 'can compute prefix with pre' do
106
+ expect(cache.pre('foo')).to eq 'test-foo'
107
+ end
108
+
109
+ it 'can remove prefix with unpre' do
110
+ expect(cache.unpre('test-foo')).to eq 'foo'
111
+ end
112
+ end
113
+ end
114
+
@@ -2,7 +2,9 @@ require 'spec_helper'
2
2
 
3
3
  describe OllamaChat::SourceFetching do
4
4
  let :chat do
5
- OllamaChat::Chat.new
5
+ OllamaChat::Chat.new(
6
+ argv: %w[ -f lib/ollama_chat/ollama_chat_config/default_config.yml ]
7
+ )
6
8
  end
7
9
 
8
10
  connect_to_ollama_server
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.49
4
+ version: 0.0.51
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 2.16.2
18
+ version: 2.16.3
19
19
  type: :development
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 2.16.2
25
+ version: 2.16.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: all_images
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -379,6 +379,20 @@ dependencies:
379
379
  - - "~>"
380
380
  - !ruby/object:Gem::Version
381
381
  version: '0.3'
382
+ - !ruby/object:Gem::Dependency
383
+ name: context_spook
384
+ requirement: !ruby/object:Gem::Requirement
385
+ requirements:
386
+ - - "~>"
387
+ - !ruby/object:Gem::Version
388
+ version: '1.1'
389
+ type: :runtime
390
+ prerelease: false
391
+ version_requirements: !ruby/object:Gem::Requirement
392
+ requirements:
393
+ - - "~>"
394
+ - !ruby/object:Gem::Version
395
+ version: '1.1'
382
396
  description: |
383
397
  The app provides a command-line interface (CLI) to an Ollama AI model,
384
398
  allowing users to engage in text-based conversations and generate
@@ -404,6 +418,7 @@ extra_rdoc_files:
404
418
  - lib/ollama_chat/follow_chat.rb
405
419
  - lib/ollama_chat/history.rb
406
420
  - lib/ollama_chat/information.rb
421
+ - lib/ollama_chat/input_content.rb
407
422
  - lib/ollama_chat/kramdown_ansi.rb
408
423
  - lib/ollama_chat/message_format.rb
409
424
  - lib/ollama_chat/message_list.rb
@@ -411,6 +426,7 @@ extra_rdoc_files:
411
426
  - lib/ollama_chat/model_handling.rb
412
427
  - lib/ollama_chat/ollama_chat_config.rb
413
428
  - lib/ollama_chat/parsing.rb
429
+ - lib/ollama_chat/redis_cache.rb
414
430
  - lib/ollama_chat/server_socket.rb
415
431
  - lib/ollama_chat/source_fetching.rb
416
432
  - lib/ollama_chat/switches.rb
@@ -443,6 +459,7 @@ files:
443
459
  - lib/ollama_chat/follow_chat.rb
444
460
  - lib/ollama_chat/history.rb
445
461
  - lib/ollama_chat/information.rb
462
+ - lib/ollama_chat/input_content.rb
446
463
  - lib/ollama_chat/kramdown_ansi.rb
447
464
  - lib/ollama_chat/message_format.rb
448
465
  - lib/ollama_chat/message_list.rb
@@ -451,6 +468,7 @@ files:
451
468
  - lib/ollama_chat/ollama_chat_config.rb
452
469
  - lib/ollama_chat/ollama_chat_config/default_config.yml
453
470
  - lib/ollama_chat/parsing.rb
471
+ - lib/ollama_chat/redis_cache.rb
454
472
  - lib/ollama_chat/server_socket.rb
455
473
  - lib/ollama_chat/source_fetching.rb
456
474
  - lib/ollama_chat/switches.rb
@@ -491,6 +509,7 @@ files:
491
509
  - spec/ollama_chat/message_output_spec.rb
492
510
  - spec/ollama_chat/model_handling_spec.rb
493
511
  - spec/ollama_chat/parsing_spec.rb
512
+ - spec/ollama_chat/redis_cache_spec.rb
494
513
  - spec/ollama_chat/server_socket_spec.rb
495
514
  - spec/ollama_chat/source_fetching_spec.rb
496
515
  - spec/ollama_chat/switches_spec.rb
@@ -536,6 +555,7 @@ test_files:
536
555
  - spec/ollama_chat/message_output_spec.rb
537
556
  - spec/ollama_chat/model_handling_spec.rb
538
557
  - spec/ollama_chat/parsing_spec.rb
558
+ - spec/ollama_chat/redis_cache_spec.rb
539
559
  - spec/ollama_chat/server_socket_spec.rb
540
560
  - spec/ollama_chat/source_fetching_spec.rb
541
561
  - spec/ollama_chat/switches_spec.rb