ollama_chat 0.0.109 → 0.0.111

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: e6c85b95f4c5051b6b0f257dd8f63ee2a3997146c527fd8af251458fee641039
4
- data.tar.gz: 45a66e25a1a92d8cbd86253a991ad28eeb48d1a8ca68c1f8bbb8df90bbbba37f
3
+ metadata.gz: 54b2bf70b7f843ad09b5de5f0881bf810ec78a08c79c5e1cd93d86aa75b68696
4
+ data.tar.gz: 33e475ea5e115609e02ba6b92cdf6a9b1b1dc6eccbfde6978fc80623daa3040b
5
5
  SHA512:
6
- metadata.gz: a401173a7deb626af01ad8c9adf358259a6ffd7ea292644bb1a5a4553df2f70e9c74bb4ff0807d04914a7aa47e8140e81e0bd46745e33900962d8559950ea914
7
- data.tar.gz: 6b04fcce611684e73bbe54cdadb01859fc4219f57960604a64c3037b414e56a02c7389a5bfef2341d0a2aa2a44d8a723761469f9c677ec8784c2c9d9e8b6fae0
6
+ metadata.gz: e7dad8f668a54b9c1e89937a445894eeb9872e1541d54dfc36fcf136d5c759bde7e0c21c9b818ce2e8ffa3bd7274163ada6b18553590dc812f3e89bdae839a27
7
+ data.tar.gz: 8bbd695f1eacc33c6928f8a382acdb8ed68b57348a0ce5199a9203bd7070df59f71a7a37e2862297c6c7dbc12e0d00aa9c46ef90f6392fc7579b87c8de438b7d
data/CHANGES.md CHANGED
@@ -1,5 +1,118 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-08-25 v0.0.111
4
+
5
+ ### Added
6
+
7
+ * `OllamaChat::Tools::ExecuteJiraTwg` tool
8
+ (`lib/ollama_chat/tools/execute_jira_twg.rb`) as a `twg` CLI wrapper,
9
+ featuring `Shellwords.shellsplit` argument parsing,
10
+ `OllamaChat::ExecuteError` handling for non-zero exits, and
11
+ `Kramdown::ANSI::Width.truncate` for feedback messages.
12
+ * `OC::OLLAMA::CHAT::TOOLS::JIRA::TWG` configuration constant, defaulting to
13
+ `which 2>/dev/null twg`.
14
+ * `execute_jira_twg` entry in `default_config.yml` with `default: false` and
15
+ `require_confirmation: true`.
16
+ * Two-step tool approval workflow in `FollowChat#handle_tool_calls`,
17
+ replacing single `chat.ask?` with `chat.confirm?` followed by `chat.ask?`
18
+ to allow single-keypress confirmation or free-text instructions.
19
+ * `session_sync` method in `session_management.rb` to persist messages,
20
+ links, history, update `working_directory`, and re-lock the session.
21
+ * `context_usage` method in `lib/ollama_chat/information.rb` to calculate and
22
+ format context usage, including `current_context_length` and
23
+ `context_filled` helpers.
24
+ * Context usage gauge in `info_session` displaying tokens used, total, and
25
+ percentage.
26
+ * `api_ps.json` asset containing a **27.3B** `qwen3.8:27b` model entry with a
27
+ **262144** context length.
28
+ * `spec/ollama_chat/tools/execute_jira_twg_spec.rb` with 8 specs covering
29
+ identity, execution, quoted arguments, nil guards, exception rescue, and
30
+ non-zero exits.
31
+ * `#session_sync` spec verifying ordered calls to
32
+ `store_messages_in_session`, `links.sync`, `save_history`, and
33
+ `session.lock`.
34
+
35
+ ### Changed
36
+
37
+ * `session_close` now delegates to `session_sync` before calling
38
+ `session.unlock`.
39
+ * `change_session` `ensure` block now uses `session_sync` instead of inline
40
+ `session.update(working_directory:)`.
41
+ * `set_new_session` now includes `ensure session.lock` to guarantee locking
42
+ on early return.
43
+ * `context_usage` logic extracted from `dynamic_runtime_information_values`
44
+ for improved maintainability.
45
+ * `session_management.rb` now logs the formatted `context_usage` string
46
+ instead of raw token counts and byte sizes.
47
+ * `self.register_name` in `lib/ollama_chat/tools/gem_path_lookup.rb`
48
+ simplified to use endless method definition syntax.
49
+ * Context ingestion logic now guards against empty file sets, emitting a
50
+ `STDERR` message and canceling if no files match the given patterns.
51
+ * `parsing_spec.rb` and `directory_structure_spec.rb` expected file counts
52
+ updated from **23** to **24**.
53
+
54
+ ### Fixed
55
+
56
+ * `current_context_length` now uses safe navigation operators to guard
57
+ against missing `ollama.ps` or `models`.
58
+ * `context_usage` body wrapped in a guard to return `nil` when context length
59
+ is unavailable.
60
+ * `which` stderr silenced in all OC config defaults using `2>/dev/null`.
61
+
62
+ ## 2026-08-20 v0.0.110
63
+
64
+ ### Added
65
+
66
+ * **App State Support**: Introduced `009_add_app_states_table.rb` migration
67
+ and `app_state.rb` model.
68
+ * **Prompt Synchronization**: Added `/prompt sync` subcommand with unified
69
+ diffs and boot check capabilities.
70
+ * Implemented `AppState` model with `get`/`set` helpers and a `seed` boot
71
+ check using per-prompt `Digest::SHA256` hashes XOR-folded into a
72
+ fingerprint.
73
+ * Boot check prints `~`/`+`/`-` detail report on drift and prompts user
74
+ to acknowledge; declining retains old fingerprint for repeated notice
75
+ on next boot.
76
+ * Added `OC::DIFF_COMMAND` setting (`diff -u --color=always`) to `oc.rb`
77
+ for unified diff display, decoded via `Shellwords.split`.
78
+ * Implemented `prompt_sync` and `show_prompt_diff` methods in
79
+ `prompt_management.rb` to compare DB prompts against shipped defaults,
80
+ show unified diff, offer `OC::DIFF_TOOL` resolution, and clean up
81
+ orphaned default prompts.
82
+ * Wired `/prompt sync` subcommand into `commands.rb` (regexp, completion,
83
+ dispatch).
84
+ * **Test Mode**: Added `OllamaChat.test_mode` flag in `ollama_chat.rb` to
85
+ suppress interactive prompts during specs; set in `spec_helper.rb`.
86
+ * **Documentation**: Added YARD `@!attribute` docs for `test_mode` and
87
+ `test_mode?`.
88
+
89
+ ### Changed
90
+
91
+ * **Web Searching**: Updated `web_searching.rb` to store `search_engine` in
92
+ local `engine` to avoid double method call.
93
+ * **Session Management**: Added call to `session_close` after
94
+ `clean_messages!` succeeds in `lib/ollama_chat/commands.rb`.
95
+ * **Error Messages**: Simplified stale context error message in `PatchFile`
96
+ when a checksum mismatch occurs.
97
+
98
+ ### Fixed
99
+
100
+ * **Spec Expectations**: Updated `web_searching_spec.rb` to replace `allow`
101
+ with `expect` for message expectations.
102
+ * Changed `log` expectations to `.at_least(:once)` since `links.add` also
103
+ triggers `log` internally.
104
+ * Removed unused `before { allow(chat).to receive(:log) }` from the
105
+ `#web` describe block.
106
+ * Added `.at_least(:once)` to `document_policy.selected` as it is called
107
+ in both `if` and `elsif` branches.
108
+ * Changed `fetch_source`, `summarize`, and `import` expectations to
109
+ `.twice` since they are called once per URL (2 URLs in test data).
110
+
111
+ ### Tests
112
+
113
+ * Added 7 new spec examples for `prompt_sync` and `show_prompt_diff` in
114
+ `prompt_management_spec.rb`.
115
+
3
116
  ## 2026-08-18 v0.0.109
4
117
 
5
118
  ### Added
@@ -484,14 +484,15 @@ module OllamaChat::Commands
484
484
 
485
485
  command(
486
486
  name: :prompt,
487
- regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|rename|-e))?(\s+(?:-[ef]|-c\s+(?:\w+|\?)))?(?:\s+([^-].*))?$),
488
- complete: [ 'prompt', %w[ edit info add delete list duplicate import export reset rename ] ],
487
+ regexp: %r(^/prompt(?:\s+(edit|info|add|delete|list|duplicate|import|export|reset|rename|sync|-e))?(\s+(?:-[ef]|-c\s+(?:\w+|\?)))?(?:\s+([^-].*))?$),
488
+ complete: [ 'prompt', %w[ edit info add delete list duplicate import export reset rename sync ] ],
489
489
  optional: true,
490
490
  options: '[-c CONTEXT|-e|-f]',
491
491
  help: <<~EOT,
492
492
  📝 Manage prompt templates:
493
- Subcommands: edit, info, add, delete, list,
494
- duplicate, import, export, reset, rename.
493
+ Subcommands: edit, info, add, delete, list,
494
+ duplicate, import, export, reset, rename,
495
+ sync.
495
496
  Options: -c [context]
496
497
  (? for interactive in /prompt),
497
498
  -e (edit next)
@@ -544,6 +545,8 @@ module OllamaChat::Commands
544
545
  STDOUT.puts "No default value found for prompt #{bold{prompt.name}}."
545
546
  end
546
547
  end
548
+ when 'sync'
549
+ prompt_sync(context:)
547
550
  when nil, '-e'
548
551
  if prompt = choose_prompt(
549
552
  prompt: 'Which template shall guide the next response? %s',
@@ -591,14 +594,15 @@ module OllamaChat::Commands
591
594
 
592
595
  command(
593
596
  name: :conversation,
594
- regexp: %r(^/conversation\s+(clean|save|load)(\s+-c)?(?:\s+([^-].*\.jsonl?))?$),
595
- complete: [ 'conversation', %w[ save load clean ] ],
597
+ regexp: %r(^/conversation\s+(clean|compact|save|load)(\s+-c)?(?:\s+([^-].*\.jsonl?))?$),
598
+ complete: [ 'conversation', %w[ save load clean compact ] ],
596
599
  options: '[-c] [FILENAME]',
597
600
  help: <<~EOT
598
601
  💾 Save/Load conversation state
599
602
  (-c to clean before saving)
600
603
  FILENAME ends with .json or .jsonl
601
604
  Or clean inplace (removes tool content, images, and thinking)
605
+ Or compact (summarize old messages, keep recent)
602
606
  EOT
603
607
  ) do |subcommand,opts,path|
604
608
  if %w[ save load ].include?(subcommand) && path.blank?
@@ -619,10 +623,23 @@ module OllamaChat::Commands
619
623
  )
620
624
  then
621
625
  messages.clean_messages!
626
+ session_sync
622
627
  STDOUT.puts "Conversation cleaned."
623
628
  else
624
629
  STDOUT.puts 'Cancelled.'
625
630
  end
631
+ when 'compact'
632
+ if confirm?(
633
+ prompt: '🔔 Compact conversation? Old messages will be summarized. (y/n) ',
634
+ yes: /\Ay/i
635
+ )
636
+ then
637
+ messages.compact!
638
+ session_sync
639
+ STDOUT.puts "Conversation compacted."
640
+ else
641
+ STDOUT.puts 'Cancelled.'
642
+ end
626
643
  end
627
644
  :next
628
645
  end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table :app_states do
4
+ primary_key :id
5
+ String :key, null: false, unique: true
6
+ Text :value # JSON-encoded payload
7
+ DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP, null: false
8
+ DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP, null: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,122 @@
1
+ require 'digest'
2
+
3
+ # Represents a key/value pair of persistent application state stored in
4
+ # the database.
5
+ #
6
+ # This model provides a generic, JSON-backed store for small bits of
7
+ # runtime state that need to survive across sessions—such as the
8
+ # XOR-SHA256 fingerprint of the shipped default prompts used for boot
9
+ # drift detection.
10
+ class OllamaChat::Database::Models::AppState < Sequel::Model(OllamaChat::DB)
11
+ plugin :timestamps, update_on_create: true
12
+ plugin :serialization, :json, :value
13
+ plugin :validation_helpers
14
+
15
+ # Validates the application state entry.
16
+ #
17
+ # Ensures that the `key` is present and unique.
18
+ def validate
19
+ super
20
+ validates_presence :key
21
+ validates_unique :key
22
+ end
23
+
24
+ # Retrieves the JSON payload stored under the given key.
25
+ #
26
+ # @param key [String, Symbol] the state key to look up
27
+ # @return [Hash, nil] the deserialized JSON payload, or nil if no
28
+ # record exists for the key
29
+ def self.get(key)
30
+ where(key: key.to_s).first&.value
31
+ end
32
+
33
+ # Creates or updates the JSON payload stored under the given key.
34
+ #
35
+ # @param key [String, Symbol] the state key to write
36
+ # @param value [Hash, nil] the JSON-serializable payload to store
37
+ # @return [OllamaChat::Database::Models::AppState] the saved record
38
+ def self.set(key, value)
39
+ if found = where(key: key.to_s).first
40
+ found.update(value:)
41
+ found
42
+ else
43
+ create(key: key.to_s, value:)
44
+ end
45
+ end
46
+
47
+ # Computes per-prompt SHA256 hashes and an XOR fingerprint of all shipped
48
+ # default prompts.
49
+ #
50
+ # At boot, if the stored fingerprint differs from the current one, a detail
51
+ # report (modified / added / removed) is printed to STDERR and the user is
52
+ # prompted to accept the new fingerprint. If declined, the old fingerprint
53
+ # is retained and the notice will reappear on the next boot.
54
+ #
55
+ # This method is invoked automatically by the model seeding loop in
56
+ # `OllamaChat::Database.setup_models`.
57
+ #
58
+ # @param chat [OllamaChat::Chat] the chat instance providing the config
59
+ # and `confirm?`
60
+ def self.seed(chat)
61
+ stored = get(:prompt_defaults_fingerprint)
62
+
63
+ stored_fingerprint = stored&.dig('fingerprint')
64
+
65
+ hashes = {}
66
+ chat.config.prompts.to_h.each do |context, prompts|
67
+ prompts.each do |name, content|
68
+ hashes["#{context}/#{name}"] =
69
+ Digest::SHA256.hexdigest(content.to_s)
70
+ end
71
+ end
72
+
73
+ fingerprint = hashes.values.reduce(0) do |sum, hex|
74
+ sum ^ hex.to_i(16)
75
+ end.to_s(16)
76
+
77
+ return true if fingerprint == stored_fingerprint
78
+
79
+ if stored&.dig('hashes')
80
+ old = stored['hashes']
81
+ added = hashes.keys - old.keys
82
+ removed = old.keys - hashes.keys
83
+ changed = hashes.select { |k, v| old[k] && old[k] != v }.keys
84
+
85
+ STDERR.puts "⚠️ Shipped prompts changed since last boot:"
86
+ changed.each { |k| STDERR.puts " ~ #{k} (modified)" }
87
+ added.each { |k| STDERR.puts " + #{k} (new)" }
88
+ removed.each { |k| STDERR.puts " - #{k} (removed)" }
89
+ elsif !OllamaChat.test_mode?
90
+ STDERR.puts '⚠️ First run — storing prompt fingerprints.'
91
+ set(:prompt_defaults_fingerprint,
92
+ { fingerprint:, hashes:, computed_at: Time.now.iso8601 })
93
+ return true
94
+ end
95
+
96
+ prompt = <<~EOT.chomp << ' '
97
+ Keep local prompts (stop nagging)?
98
+ Consider adopting via /prompt sync or /prompt reset. (y/n) %s
99
+ EOT
100
+ if OllamaChat.test_mode? || chat.confirm?(prompt:, yes: /\Ay/i, timeout: 5)
101
+ set(:prompt_defaults_fingerprint,
102
+ { fingerprint:, hashes:, computed_at: Time.now.iso8601 })
103
+ end
104
+
105
+ true
106
+ end
107
+
108
+ # @!attribute [v] id
109
+ # @return [Integer] The primary key for the state entry.
110
+ #
111
+ # @!attribute [v] key
112
+ # @return [String] The unique state key.
113
+ #
114
+ # @!attribute [v] value
115
+ # @return [Hash, nil] The JSON-serialized state payload.
116
+ #
117
+ # @!attribute [v] created_at
118
+ # @return [Time, nil] The timestamp when the state was created.
119
+ #
120
+ # @!attribute [v] updated_at
121
+ # @return [Time, nil] The timestamp of the last update to the state.
122
+ end
@@ -137,18 +137,25 @@ class OllamaChat::FollowChat
137
137
  end
138
138
  STDOUT.puts
139
139
  confirmed = :implicit
140
- function = JSON.pretty_generate(tool_call.function)
140
+ resolve = nil
141
+ function = JSON.pretty_generate(tool_call.function)
141
142
  chat.log(:info, "Tool call received", data: { tool: name, function: })
142
143
  if chat.tool_function(name).require_confirmation?
143
- prompt = "🔔 I want to execute tool %s\n%s\nConfirm? (y/n) " % [
144
- bold { name },
145
- italic { function },
144
+ STDOUT.puts "🔔 I want to execute tool %s\n%s\n" % [
145
+ bold { name }, italic { function },
146
146
  ]
147
- prompt.gsub!('%', '%%')
148
- if chat.confirm?(prompt:, yes: /\Ay/i)
147
+ prompt = ' Allow ✅[y]es / ⛔️[n]o / 📝[i]nstruct? '
148
+ answer = chat.confirm?(prompt:)&.to_s&.downcase
149
+ resolve = 'You **MUST** ask the user for instructions on how to proceed!!!'
150
+ case answer
151
+ when 'y'
149
152
  confirmed = :explicit
153
+ when 'n'
154
+ confirmed = :denied
150
155
  else
151
156
  confirmed = :denied
157
+ instr = chat.ask?(prompt: '📝 Instructions: ')&.strip
158
+ resolve = instr.full? || resolve
152
159
  end
153
160
  else
154
161
  STDOUT.puts "Executing tool %s\n%s" % [
@@ -160,10 +167,7 @@ class OllamaChat::FollowChat
160
167
  result = nil
161
168
  case confirmed
162
169
  when :denied
163
- result = JSON(
164
- message: 'User denied confirmation!',
165
- resolve: 'You **MUST** ask the user for instructions on how to proceed!!!',
166
- )
170
+ result = JSON(message: 'User denied confirmation!', resolve:)
167
171
  STDOUT.printf(
168
172
  "\n%s Execution of tool %s denied by user.\n\n", ?🚫, bold { name }
169
173
  )
@@ -174,11 +178,7 @@ class OllamaChat::FollowChat
174
178
  "\n%s Execution of tool %s confirmed.\n\n", symbol, bold { name }
175
179
  )
176
180
  result = OllamaChat::Tools.registered[name].execute(tool_call, chat:)
177
- if confirmed == :explicit
178
- chat.log(:info, "Tool execution confirmed", data: { tool: name, confirmed: })
179
- else
180
- chat.log(:info, "Tool execution confirmed", data: { tool: name, confirmed: })
181
- end
181
+ chat.log(:info, "Tool execution confirmed", data: { tool: name, confirmed: })
182
182
  end
183
183
 
184
184
  chat.tool_call_results[name] << result
@@ -132,6 +132,12 @@ module OllamaChat::Information
132
132
  output.puts " No persona selected."
133
133
  end
134
134
  output.puts "🧠 Current chat model is #{bold{@model}}."
135
+ context_usage = '%s of %s (%s)' % [
136
+ session.estimate_tokens.tokens_formatted,
137
+ format_tokens(current_context_length),
138
+ bold { '%.1f%%' % (100 * context_filled)},
139
+ ]
140
+ output.puts " Context Length: #{context_usage}"
135
141
  output.print ' '; think_mode.show(output:)
136
142
  output.print ' '; think_loud.show(output:)
137
143
  output.print ' '; think_strip.show(output:)
@@ -360,6 +366,7 @@ module OllamaChat::Information
360
366
  tools_support: tools_support.on? ? 'enabled' : 'disabled',
361
367
  voice: voice.on? ? 'enabled' : 'disabled',
362
368
  weekday: now.strftime('%A'),
369
+ context_usage: ,
363
370
  }
364
371
  end
365
372
 
@@ -373,4 +380,47 @@ module OllamaChat::Information
373
380
  def dynamic_runtime_information
374
381
  prompt(:dynamic_runtime_info).to_s % dynamic_runtime_information_values
375
382
  end
383
+
384
+ # Resolves the effective context window size for the current model.
385
+ #
386
+ # Prefers the session-level `num_ctx` override (set via `/model_options`
387
+ # or the stored model options profile). Falls back to the model's native
388
+ # `context_length` in the default profile or as reported by the Ollama server
389
+ # if possible.
390
+ #
391
+ # @return [Integer, NilClass] the context length in tokens or nil
392
+ def current_context_length
393
+ get_session_model_options[:num_ctx] ||
394
+ get_stored_model_options(@model)[:num_ctx] ||
395
+ ollama.ps&.models&.find { _1.name == @model }&.context_length
396
+ end
397
+
398
+ # Computes the fraction of the context window currently in use.
399
+ #
400
+ # Divides the estimated token count of the session's messages by the
401
+ # effective context length, clamped to the range 0.0–1.0.
402
+ #
403
+ # @return [Float] the ratio of used context (e.g. `0.73` for 73%)
404
+ def context_filled
405
+ es = session.estimate_tokens
406
+ (es.tokens.to_f / current_context_length).clamp(0..1).to_f
407
+ end
408
+
409
+ # Formats the current context usage as a human-readable string.
410
+ #
411
+ # Returns a string like `"167.7 KT of 262.1 KT (64.0%)"` combining
412
+ # the estimated tokens in use, the effective context length, and
413
+ # the percentage (via +context_filled+).
414
+ #
415
+ # @return [String, nil] the formatted usage string, or +nil+ if
416
+ # the context length cannot be determined.
417
+ def context_usage
418
+ if cl = current_context_length
419
+ '%s of %s (%s)' % [
420
+ session.estimate_tokens.tokens_formatted,
421
+ format_tokens(current_context_length),
422
+ '%.1f%%' % (100 * context_filled),
423
+ ]
424
+ end
425
+ end
376
426
  end
@@ -95,14 +95,22 @@ module OllamaChat::InputContent
95
95
  format = @context_format.selected
96
96
  myself = self
97
97
  if patterns
98
- ContextSpook::generate_context(verbose: true, format:) do |context|
98
+ count = 0
99
+ ctx = ContextSpook::generate_context(verbose: true, format:) do |context|
99
100
  context do
100
101
  myself.file_set_each(patterns, all:) do |filename|
101
102
  filename.file? or next
102
103
  file filename.to_path
104
+ count += 1
103
105
  end
104
106
  end
105
- end.send("to_#{format.downcase}")
107
+ end
108
+ if count > 0
109
+ ctx.send("to_#{format.downcase}")
110
+ STDOUT.puts "✅ Ingesting context now."
111
+ else
112
+ STDERR.puts "❌ No files in context. ⇨ Cancelled."
113
+ end
106
114
  else
107
115
  if context_filename = choose_filename('.contexts/*.rb')
108
116
  ContextSpook.generate_context(context_filename, verbose: true, format:).
@@ -1,5 +1,6 @@
1
1
  require 'const_conf'
2
2
  require 'pathname'
3
+ require 'shellwords'
3
4
 
4
5
  # Environment configuration module for OllamaChat
5
6
  #
@@ -38,7 +39,7 @@ module OC
38
39
  description 'Pager command to use in case terminal lines are exceeded by output'
39
40
 
40
41
  default do
41
- if fallback_pager = `which less`.full?(:chomp) || `which more`.full?(:chomp)
42
+ if fallback_pager = `which 2>/dev/null less`.full?(:chomp) || `which 2>/dev/null more`.full?(:chomp)
42
43
  fallback_pager << ' -r'
43
44
  end
44
45
  end
@@ -48,7 +49,7 @@ module OC
48
49
  description 'Editor to use'
49
50
 
50
51
  default do
51
- if editor = %w[ vim vi ].find { `which #{_1}`.full?(:chomp) }
52
+ if editor = %w[ vim vi ].find { `which 2>/dev/null #{_1}`.full?(:chomp) }
52
53
  editor
53
54
  else
54
55
  warn 'Need an editor command configured via env var "EDITOR"'
@@ -60,15 +61,23 @@ module OC
60
61
  description 'Browser to use'
61
62
 
62
63
  default do
63
- %w[ open xdg-open ].find { `which #{_1}` }.full?(:chomp)
64
+ %w[ open xdg-open ].find { `which 2>/dev/null #{_1}` }.full?(:chomp)
64
65
  end
65
66
  end
66
67
 
68
+ DIFF_COMMAND = set do
69
+ description 'Command (string) to generate a unified diff with'
70
+
71
+ required true
72
+ default { 'diff -u --color=always' }
73
+ decode { Shellwords.split(_1) if _1.present? }
74
+ end
75
+
67
76
  DIFF_TOOL = set do
68
77
  description 'Diff tool to apply changes with'
69
78
 
70
79
  default do
71
- if diff = `which vimdiff`.full?(:chomp)
80
+ if diff = `which 2>/dev/null vimdiff`.full?(:chomp)
72
81
  diff
73
82
  else
74
83
  warn 'Need a diff tool configured via env var "DIFF_TOOL"'
@@ -184,7 +193,7 @@ module OC
184
193
 
185
194
  CTAGS_TOOL = set do
186
195
  description 'Tools ctags path'
187
- default { `which ctags`.full?(:chomp) }
196
+ default { `which 2>/dev/null ctags`.full?(:chomp) }
188
197
  check { value.blank? || File.exist?(value) }
189
198
  end
190
199
 
@@ -231,6 +240,11 @@ module OC
231
240
  sensitive true
232
241
  required { OC::OLLAMA::CHAT::TOOLS::JIRA::URL? }
233
242
  end
243
+
244
+ TWG = set do
245
+ description 'Path to the twg CLI binary'
246
+ default { `which 2>/dev/null twg`.full?(:chomp) }
247
+ end
234
248
  end
235
249
 
236
250
  module IMAGE_GENERATOR
@@ -73,6 +73,7 @@ prompts:
73
73
  80%% of this width in your output of responses, especially when using
74
74
  markdown tables.
75
75
  - **Session**: %{session_name}
76
+ - Context usage is %{context_usage}.
76
77
  - Markdown output is %{markdown}. **Never** output markdown as your
77
78
  responses if it is disabled.
78
79
  - Voice output is %{voice}. Speak naturally and short if voice
@@ -240,7 +241,6 @@ prompts:
240
241
  How does this implementation handle malformed JSON or missing keys in the
241
242
  middle of a 2GB file without crashing the process?
242
243
 
243
-
244
244
  What is the time complexity of the current insert logic, and can we implement
245
245
  bulk inserts to reduce database round trips?
246
246
 
@@ -508,6 +508,10 @@ tools:
508
508
  result_display_timeout: 3
509
509
  default: false
510
510
  require_confirmation: true
511
+ execute_jira_twg:
512
+ result_display_timeout: 3
513
+ default: false
514
+ require_confirmation: true
511
515
  get_rfc:
512
516
  url: "https://www.rfc-editor.org/rfc/rfc%{rfc_id}.txt"
513
517
  result_display_timeout: 3