prescient 0.4.0 → 0.5.0

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.
@@ -1,330 +1,94 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Example: Vector similarity search with Prescient gem and PostgreSQL pgvector
5
- # This example demonstrates how to store embeddings and perform similarity search
4
+ # Example: Vector similarity search with Prescient and PostgreSQL pgvector.
5
+ # The Store owns only its embedding table; applications own their documents.
6
6
 
7
7
  require_relative '../lib/prescient'
8
8
  require 'pg'
9
- require 'json'
10
9
 
11
- puts "=== Vector Similarity Search Example ==="
12
- puts "This example shows how to use Prescient with PostgreSQL pgvector for semantic search."
10
+ puts '=== Vector Similarity Search Example ==='
13
11
 
14
- # Database connection configuration
15
12
  DB_CONFIG = {
16
- host: ENV.fetch('DB_HOST', 'localhost'),
17
- port: ENV.fetch('DB_PORT', '5432'),
18
- dbname: ENV.fetch('DB_NAME', 'prescient_development'),
19
- user: ENV.fetch('DB_USER', 'prescient'),
20
- password: ENV.fetch('DB_PASSWORD', 'prescient_password')
13
+ host: ENV.fetch('DB_HOST', 'localhost'),
14
+ port: ENV.fetch('DB_PORT', '5432'),
15
+ dbname: ENV.fetch('DB_NAME', 'prescient_development'),
16
+ user: ENV.fetch('DB_USER', 'prescient'),
17
+ password: ENV.fetch('DB_PASSWORD', 'prescient_password'),
21
18
  }.freeze
22
19
 
23
20
  class VectorSearchExample
24
- def initialize
25
- @db = PG.connect(DB_CONFIG)
26
- @client = Prescient.client(:ollama)
27
- end
28
-
29
- def run_example
30
- puts "\n--- Setting up vector search example ---"
31
-
32
- # Check if services are available
33
- unless check_services_available
34
- puts "āŒ Required services not available. Please start with: docker compose up -d"
35
- return
36
- end
37
-
38
- # 1. Generate and store embeddings for existing documents
39
- puts "\nšŸ“Š Generating embeddings for sample documents..."
40
- generate_document_embeddings
41
-
42
- # 2. Perform similarity search
43
- puts "\nšŸ” Performing similarity searches..."
44
- search_examples
45
-
46
- # 3. Advanced search with filtering
47
- puts "\nšŸŽÆ Advanced search with metadata filtering..."
48
- advanced_search_examples
49
-
50
- # 4. Demonstrate different distance functions
51
- puts "\nšŸ“ Comparing different distance functions..."
52
- compare_distance_functions
53
-
54
- puts "\nšŸŽ‰ Vector search example completed!"
55
- end
56
-
57
- private
21
+ EMBEDDING_DIMENSIONS = 768
22
+ EMBEDDING_MODEL = 'nomic-embed-text'
58
23
 
59
- def check_services_available
60
- # Check database connection
61
- begin
62
- result = @db.exec("SELECT 1")
63
- puts "āœ… PostgreSQL connected"
64
- rescue PG::Error => e
65
- puts "āŒ PostgreSQL connection failed: #{e.message}"
66
- return false
67
- end
68
-
69
- # Check pgvector extension
70
- begin
71
- result = @db.exec("SELECT * FROM pg_extension WHERE extname = 'vector'")
72
- if result.ntuples > 0
73
- puts "āœ… pgvector extension available"
74
- else
75
- puts "āŒ pgvector extension not found"
76
- return false
77
- end
78
- rescue PG::Error => e
79
- puts "āŒ pgvector check failed: #{e.message}"
80
- return false
81
- end
82
-
83
- # Check Ollama connection
84
- if @client.available?
85
- puts "āœ… Ollama connected"
86
- else
87
- puts "āŒ Ollama not available"
88
- return false
89
- end
90
-
91
- true
24
+ def initialize
25
+ @connection = PG.connect(DB_CONFIG)
26
+ @client = Prescient.client(:ollama, enable_fallback: false)
27
+ @store = Prescient::Pgvector::Store.new(
28
+ connection: @connection,
29
+ dimensions: EMBEDDING_DIMENSIONS,
30
+ )
92
31
  end
93
32
 
94
- def generate_document_embeddings
95
- # Get documents that don't have embeddings yet
96
- query = <<~SQL
97
- SELECT d.id, d.title, d.content
98
- FROM documents d
99
- LEFT JOIN document_embeddings de ON d.id = de.document_id
100
- AND de.embedding_provider = 'ollama'
101
- AND de.embedding_model = 'nomic-embed-text'
102
- WHERE de.id IS NULL
103
- LIMIT 10
104
- SQL
105
-
106
- result = @db.exec(query)
107
-
108
- if result.ntuples == 0
109
- puts " All documents already have embeddings"
110
- return
33
+ def run
34
+ @store.install!
35
+ %i[cosine euclidean inner_product].each { |metric| @store.create_index!(metric:) }
36
+
37
+ documents.each do |document|
38
+ embedding = @client.generate_embedding(document[:content], model: EMBEDDING_MODEL)
39
+ @store.upsert(
40
+ id: document[:id],
41
+ embedding:,
42
+ provider: 'ollama',
43
+ model: EMBEDDING_MODEL,
44
+ content: document[:content],
45
+ metadata: { title: document[:title] },
46
+ )
111
47
  end
112
48
 
113
- result.each do |row|
114
- document_id = row['id']
115
- title = row['title']
116
- content = row['content']
117
-
118
- puts " Generating embedding for: #{title}"
119
-
120
- begin
121
- # Generate embedding using Prescient
122
- embedding = @client.generate_embedding(content)
123
-
124
- # Store in database
125
- insert_embedding(document_id, embedding, content, 'ollama', 'nomic-embed-text', 768)
126
-
127
- puts " āœ… Stored embedding (#{embedding.length} dimensions)"
128
-
129
- rescue Prescient::Error => e
130
- puts " āŒ Failed to generate embedding: #{e.message}"
131
- end
49
+ query = 'How do I improve database performance?'
50
+ embedding = @client.generate_embedding(query, model: EMBEDDING_MODEL)
51
+ results = @store.search(
52
+ embedding:,
53
+ limit: 3,
54
+ metric: :cosine,
55
+ provider: 'ollama',
56
+ model: EMBEDDING_MODEL,
57
+ )
58
+
59
+ puts "\nQuery: #{query}"
60
+ results.each_with_index do |result, index|
61
+ title = result[:metadata].fetch('title')
62
+ puts "#{index + 1}. #{title} (distance: #{result[:distance].round(4)})"
63
+ puts " #{result[:content]}"
132
64
  end
65
+ rescue Prescient::Error, PG::Error => e
66
+ warn "Vector search failed: #{e.message}"
67
+ ensure
68
+ @connection&.close
133
69
  end
134
70
 
135
- def insert_embedding(document_id, embedding, text, provider, model, dimensions)
136
- # Convert Ruby array to PostgreSQL vector format
137
- vector_str = "[#{embedding.join(',')}]"
138
-
139
- query = <<~SQL
140
- INSERT INTO document_embeddings
141
- (document_id, embedding_provider, embedding_model, embedding_dimensions, embedding, embedding_text)
142
- VALUES ($1, $2, $3, $4, $5, $6)
143
- SQL
144
-
145
- @db.exec_params(query, [document_id, provider, model, dimensions, vector_str, text])
146
- end
71
+ private
147
72
 
148
- def search_examples
149
- search_queries = [
150
- "How to learn programming?",
151
- "What is machine learning?",
152
- "Database optimization techniques",
153
- "API security best practices"
73
+ def documents
74
+ [
75
+ {
76
+ id: 'postgres-indexes',
77
+ title: 'PostgreSQL indexes',
78
+ content: 'Indexes can reduce query latency when their columns match common filters and ordering.',
79
+ },
80
+ {
81
+ id: 'ruby-performance',
82
+ title: 'Ruby performance',
83
+ content: 'Measure allocations and repeated work before optimizing a Ruby application.',
84
+ },
85
+ {
86
+ id: 'api-security',
87
+ title: 'API security',
88
+ content: 'Protect API credentials, validate inputs, and avoid exposing provider response bodies.',
89
+ },
154
90
  ]
155
-
156
- search_queries.each do |query_text|
157
- puts "\nšŸ” Searching for: '#{query_text}'"
158
- perform_similarity_search(query_text, limit: 3)
159
- end
160
- end
161
-
162
- def perform_similarity_search(query_text, limit: 5, distance_function: 'cosine')
163
- begin
164
- # Generate embedding for query
165
- query_embedding = @client.generate_embedding(query_text)
166
- query_vector = "[#{query_embedding.join(',')}]"
167
-
168
- # Choose distance operator based on function
169
- distance_op = case distance_function
170
- when 'cosine' then '<=>'
171
- when 'l2' then '<->'
172
- when 'inner_product' then '<#>'
173
- else '<=>'
174
- end
175
-
176
- # Perform similarity search
177
- search_query = <<~SQL
178
- SELECT
179
- d.title,
180
- d.content,
181
- d.metadata,
182
- de.embedding #{distance_op} $1::vector AS distance,
183
- 1 - (de.embedding <=> $1::vector) AS cosine_similarity
184
- FROM documents d
185
- JOIN document_embeddings de ON d.id = de.document_id
186
- WHERE de.embedding_provider = 'ollama'
187
- AND de.embedding_model = 'nomic-embed-text'
188
- ORDER BY de.embedding #{distance_op} $1::vector
189
- LIMIT $2
190
- SQL
191
-
192
- result = @db.exec_params(search_query, [query_vector, limit])
193
-
194
- if result.ntuples == 0
195
- puts " No results found"
196
- return
197
- end
198
-
199
- result.each_with_index do |row, index|
200
- similarity = (row['cosine_similarity'].to_f * 100).round(1)
201
- puts " #{index + 1}. #{row['title']} (#{similarity}% similar)"
202
- puts " #{row['content'][0..100]}..."
203
-
204
- # Show metadata if available
205
- if row['metadata'] && !row['metadata'].empty?
206
- metadata = JSON.parse(row['metadata'])
207
- tags = metadata['tags']&.join(', ')
208
- puts " Tags: #{tags}" if tags
209
- end
210
- puts
211
- end
212
-
213
- rescue Prescient::Error => e
214
- puts " āŒ Search failed: #{e.message}"
215
- rescue PG::Error => e
216
- puts " āŒ Database error: #{e.message}"
217
- end
218
- end
219
-
220
- def advanced_search_examples
221
- # Search with metadata filtering
222
- puts "\nšŸŽÆ Search for programming content with beginner difficulty:"
223
- advanced_search("programming basics", tags: ["programming"], difficulty: "beginner")
224
-
225
- puts "\nšŸŽÆ Search for AI/ML content:"
226
- advanced_search("artificial intelligence", tags: ["ai", "machine-learning"])
227
91
  end
228
-
229
- def advanced_search(query_text, filters = {})
230
- begin
231
- query_embedding = @client.generate_embedding(query_text)
232
- query_vector = "[#{query_embedding.join(',')}]"
233
-
234
- # Build WHERE clause for metadata filtering
235
- where_conditions = ["de.embedding_provider = 'ollama'", "de.embedding_model = 'nomic-embed-text'"]
236
- params = [query_vector]
237
- param_index = 2
238
-
239
- filters.each do |key, value|
240
- case key
241
- when :tags
242
- # Filter by tags array overlap
243
- where_conditions << "d.metadata->'tags' ?| $#{param_index}::text[]"
244
- params << value
245
- param_index += 1
246
- when :difficulty
247
- # Filter by exact difficulty match
248
- where_conditions << "d.metadata->>'difficulty' = $#{param_index}"
249
- params << value
250
- param_index += 1
251
- when :source_type
252
- # Filter by source type
253
- where_conditions << "d.source_type = $#{param_index}"
254
- params << value
255
- param_index += 1
256
- end
257
- end
258
-
259
- search_query = <<~SQL
260
- SELECT
261
- d.title,
262
- d.content,
263
- d.metadata,
264
- de.embedding <=> $1::vector AS cosine_distance,
265
- 1 - (de.embedding <=> $1::vector) AS cosine_similarity
266
- FROM documents d
267
- JOIN document_embeddings de ON d.id = de.document_id
268
- WHERE #{where_conditions.join(' AND ')}
269
- ORDER BY de.embedding <=> $1::vector
270
- LIMIT 3
271
- SQL
272
-
273
- result = @db.exec_params(search_query, params)
274
-
275
- if result.ntuples == 0
276
- puts " No results found with the specified filters"
277
- return
278
- end
279
-
280
- result.each_with_index do |row, index|
281
- similarity = (row['cosine_similarity'].to_f * 100).round(1)
282
- puts " #{index + 1}. #{row['title']} (#{similarity}% similar)"
283
-
284
- metadata = JSON.parse(row['metadata'])
285
- puts " Difficulty: #{metadata['difficulty']}"
286
- puts " Tags: #{metadata['tags']&.join(', ')}"
287
- puts " #{row['content'][0..80]}..."
288
- puts
289
- end
290
-
291
- rescue Prescient::Error => e
292
- puts " āŒ Search failed: #{e.message}"
293
- rescue PG::Error => e
294
- puts " āŒ Database error: #{e.message}"
295
- end
296
- end
297
-
298
- def compare_distance_functions
299
- query_text = "programming languages and development"
300
-
301
- puts "\nšŸ“ Comparing distance functions for: '#{query_text}'"
302
-
303
- %w[cosine l2 inner_product].each do |distance_func|
304
- puts "\n #{distance_func.upcase} Distance:"
305
- perform_similarity_search(query_text, limit: 2, distance_function: distance_func)
306
- end
307
- end
308
-
309
- def cleanup
310
- @db.close if @db
311
- end
312
- end
313
-
314
- # Run the example
315
- begin
316
- example = VectorSearchExample.new
317
- example.run_example
318
- rescue StandardError => e
319
- puts "āŒ Example failed: #{e.message}"
320
- puts e.backtrace.first(5).join("\n")
321
- ensure
322
- example&.cleanup
323
92
  end
324
93
 
325
- puts "\nšŸ’” Next steps:"
326
- puts " - Try different embedding models (OpenAI, HuggingFace)"
327
- puts " - Implement hybrid search (vector + keyword)"
328
- puts " - Add document chunking for large texts"
329
- puts " - Experiment with different similarity thresholds"
330
- puts " - Add result re-ranking and filtering"
94
+ VectorSearchExample.new.run
data/lib/prescient/cli.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
  require 'optparse'
5
+ require 'yaml'
5
6
 
6
7
  # Command-line interface for common Prescient operations.
7
8
  class Prescient::CLI
@@ -9,6 +10,90 @@ class Prescient::CLI
9
10
  # @return [Array<String>] Output format names
10
11
  FORMATS = ['text', 'json'].freeze
11
12
 
13
+ # Schema URL and annotated starter configuration for `config example`.
14
+ CONFIGURATION_EXAMPLE = <<~YAML
15
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/kanutocd/prescient/refs/heads/main/schema/prescient.configuration.schema.json
16
+ #
17
+ # Prescient configuration example.
18
+ #
19
+ # Precedence, from lowest to highest:
20
+ # 1. Built-in defaults and provider environment variables.
21
+ # 2. Values in this YAML file.
22
+ # 3. Per-operation CLI overrides such as --provider and --chat-model.
23
+ #
24
+ # Use `prescient config validate` after editing this file.
25
+ # Keep credentials out of source control; use *_env references instead.
26
+ version: 1
27
+
28
+ # Global behavior.
29
+ default_provider: ollama
30
+ timeout: 30
31
+ retry_attempts: 3
32
+ retry_delay: 1.0
33
+ fallback_providers: []
34
+ sensitive_keys:
35
+ - api_key
36
+ - password
37
+ - token
38
+ - secret
39
+
40
+ providers:
41
+ # Local Ollama requires no API key.
42
+ ollama:
43
+ type: ollama
44
+ url: http://localhost:11434
45
+ embedding_model: nomic-embed-text
46
+ chat_model: llama3.2:3b
47
+ # prompt_templates:
48
+ # system_prompt: You are a concise assistant.
49
+ # no_context_template: "%<system_prompt>s\\n\\nUser: %<query>s"
50
+ # with_context_template: "%<system_prompt>s\\n\\nContext:\\n%<context>s\\n\\nUser: %<query>s"
51
+
52
+ # Uncomment a cloud provider and set its credential in the environment.
53
+ # openai:
54
+ # type: openai
55
+ # api_key_env: OPENAI_API_KEY
56
+ # embedding_model: text-embedding-3-small
57
+ # chat_model: gpt-4.1-mini
58
+ # prompt_templates:
59
+ # system_prompt: You are a concise assistant.
60
+ # no_context_template: "%<system_prompt>s\n\nUser: %<query>s"
61
+
62
+ # anthropic:
63
+ # type: anthropic
64
+ # api_key_env: ANTHROPIC_API_KEY
65
+ # model: claude-sonnet-4-20250514
66
+
67
+ # gemini:
68
+ # type: gemini
69
+ # api_key_env: GEMINI_API_KEY
70
+ # embedding_model: gemini-embedding-001
71
+ # chat_model: gemini-2.5-flash
72
+
73
+ # mistral:
74
+ # type: mistral
75
+ # api_key_env: MISTRAL_API_KEY
76
+ # embedding_model: mistral-embed
77
+ # chat_model: mistral-large-latest
78
+
79
+ # DeepSeek supports text generation, but not embeddings.
80
+ # deepseek:
81
+ # type: deepseek
82
+ # api_key_env: DEEPSEEK_API_KEY
83
+ # chat_model: deepseek-v4-flash
84
+
85
+ # xai:
86
+ # type: xai
87
+ # api_key_env: XAI_API_KEY
88
+ # chat_model: grok-4.5
89
+
90
+ # huggingface:
91
+ # type: huggingface
92
+ # api_key_env: HUGGINGFACE_API_KEY
93
+ # embedding_model: sentence-transformers/all-MiniLM-L6-v2
94
+ # chat_model: google/gemma-2-2b-it
95
+ YAML
96
+
12
97
  # Raised when command-line arguments are invalid or incomplete.
13
98
  class UsageError < StandardError; end
14
99
 
@@ -42,10 +127,22 @@ class Prescient::CLI
42
127
  @errors = errors
43
128
  end
44
129
 
130
+ # Execute the CLI command and return its process status.
131
+ # @return [Integer] Process exit status
45
132
  def run
133
+ config_path = extract_global_config_path
134
+ Prescient.load_configuration(config_path) if config_path || ENV['PRESCIENT_CONFIG']
135
+
46
136
  command = @arguments.shift
47
137
  return print_help(2) unless command
48
138
 
139
+ run_command(command)
140
+ end
141
+
142
+ # Dispatch a parsed command to its handler.
143
+ # @param command [String] Command name
144
+ # @return [Integer] Process exit status
145
+ def run_command(command)
49
146
  case command
50
147
  when 'providers' then providers
51
148
  when 'health' then health
@@ -118,8 +215,15 @@ class Prescient::CLI
118
215
 
119
216
  def config
120
217
  subcommand = @arguments.shift
121
- raise UsageError, "unknown config command #{subcommand.inspect}" unless subcommand == 'validate'
218
+ case subcommand
219
+ when 'validate' then validate_config_command
220
+ when 'example' then configuration_example_command
221
+ else
222
+ raise UsageError, "unknown config command #{subcommand.inspect}"
223
+ end
224
+ end
122
225
 
226
+ def validate_config_command
123
227
  options = parse_options('Validate the current configuration')
124
228
  return options if options.is_a?(Integer)
125
229
 
@@ -132,6 +236,14 @@ class Prescient::CLI
132
236
  0
133
237
  end
134
238
 
239
+ def configuration_example_command
240
+ options = parse_options('Generate an annotated YAML configuration example')
241
+ return options if options.is_a?(Integer)
242
+
243
+ @output.write(CONFIGURATION_EXAMPLE)
244
+ 0
245
+ end
246
+
135
247
  def validate_configuration
136
248
  configuration = Prescient.configuration
137
249
  unless configuration.provider(configuration.default_provider)
@@ -162,6 +274,9 @@ class Prescient::CLI
162
274
  end
163
275
 
164
276
  def add_common_options(parser, options)
277
+ parser.on('--config PATH', 'Load configuration from a YAML file') do |value|
278
+ options[:config] = value
279
+ end
165
280
  parser.on('--format FORMAT', FORMATS, "Output format (#{FORMATS.join(', ')})") do |value|
166
281
  options[:format] = value
167
282
  end
@@ -206,6 +321,18 @@ class Prescient::CLI
206
321
  parser.on('--chat-model NAME', 'Override the chat model') do |value|
207
322
  options[:chat_model] = value
208
323
  end
324
+ parser.on('--system-prompt TEXT', 'Override the system prompt') do |value|
325
+ options[:system_prompt] = value
326
+ end
327
+ parser.on('--no-context-template TEXT', 'Override the no-context prompt template') do |value|
328
+ options[:no_context_template] = value
329
+ end
330
+ parser.on('--with-context-template TEXT', 'Override the with-context prompt template') do |value|
331
+ options[:with_context_template] = value
332
+ end
333
+ parser.on('--prompt-templates-file PATH', 'Load prompt templates from a YAML file') do |value|
334
+ options[:prompt_templates_file] = value
335
+ end
209
336
  end
210
337
 
211
338
  def add_credential_options(parser, options)
@@ -228,12 +355,38 @@ class Prescient::CLI
228
355
 
229
356
  def provider_options(options)
230
357
  {
231
- api_key: api_key_override(options),
232
- embedding_model: options[:embedding_model],
233
- chat_model: options[:chat_model],
358
+ api_key: api_key_override(options),
359
+ embedding_model: options[:embedding_model],
360
+ chat_model: options[:chat_model],
361
+ prompt_templates: prompt_templates(options),
234
362
  }.compact
235
363
  end
236
364
 
365
+ def prompt_templates(options)
366
+ templates = if options[:prompt_templates_file]
367
+ data = YAML.safe_load_file(
368
+ options[:prompt_templates_file],
369
+ permitted_classes: [],
370
+ permitted_symbols: [],
371
+ aliases: true,
372
+ )
373
+ raise UsageError, 'prompt templates file must contain a mapping' unless data.is_a?(Hash)
374
+
375
+ data.transform_keys(&:to_sym)
376
+ else
377
+ {}
378
+ end
379
+
380
+ [:system_prompt, :no_context_template, :with_context_template].each do |key|
381
+ templates[key] = options[key] if options[key]
382
+ end
383
+ templates.empty? ? nil : templates
384
+ rescue Errno::ENOENT
385
+ raise UsageError, "prompt templates file not found: #{options[:prompt_templates_file]}"
386
+ rescue Psych::SyntaxError => e
387
+ raise UsageError, "invalid prompt templates YAML: #{e.message}"
388
+ end
389
+
237
390
  def api_key_override(options)
238
391
  return options[:api_key] if options[:api_key]
239
392
  return ENV.fetch(options[:api_key_env]) if options[:api_key_env]
@@ -272,16 +425,55 @@ class Prescient::CLI
272
425
  generate TEXT Generate a text response
273
426
  embed TEXT Generate an embedding
274
427
  config validate Validate the current configuration
428
+ config example Generate an annotated YAML configuration example
275
429
 
276
430
  Options:
431
+ --config PATH Load configuration from a YAML file
277
432
  --provider NAME Select a provider
278
433
  --model NAME Override the selected operation's model
279
434
  --chat-model NAME Override the chat model
280
435
  --embedding-model NAME Override the embedding model
436
+ --system-prompt TEXT Override the system prompt
437
+ --no-context-template TEXT
438
+ Override the no-context prompt template
439
+ --with-context-template TEXT
440
+ Override the with-context prompt template
441
+ --prompt-templates-file PATH
442
+ Load prompt templates from a YAML file
281
443
  --api-key KEY Use an API key for the operation
282
444
  --api-key-env NAME Read the API key from an environment variable
283
445
  --format FORMAT Use text or json output
284
446
  HELP
285
447
  status
286
448
  end
449
+
450
+ def extract_global_config_path
451
+ config_path = nil
452
+ filtered_arguments = []
453
+ index = 0
454
+
455
+ while index < @arguments.length
456
+ argument = @arguments[index]
457
+ if argument == '--config'
458
+ value = @arguments[index + 1]
459
+ raise UsageError, '--config requires a path' unless value
460
+
461
+ config_path = value
462
+ index += 2
463
+ next
464
+ end
465
+
466
+ if argument.start_with?('--config=')
467
+ config_path = argument.split('=', 2).last
468
+ index += 1
469
+ next
470
+ end
471
+
472
+ filtered_arguments << argument
473
+ index += 1
474
+ end
475
+
476
+ @arguments = filtered_arguments
477
+ config_path
478
+ end
287
479
  end