legion-tty 0.4.41 → 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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +3 -3
  4. data/lib/legion/tty/app.rb +27 -21
  5. data/lib/legion/tty/background/github_probe.rb +27 -24
  6. data/lib/legion/tty/background/kerberos_probe.rb +2 -1
  7. data/lib/legion/tty/background/llm_probe.rb +12 -8
  8. data/lib/legion/tty/background/scanner.rb +19 -16
  9. data/lib/legion/tty/components/command_palette.rb +5 -1
  10. data/lib/legion/tty/components/digital_rain.rb +7 -1
  11. data/lib/legion/tty/components/markdown_view.rb +6 -1
  12. data/lib/legion/tty/components/message_stream.rb +5 -2
  13. data/lib/legion/tty/components/model_picker.rb +5 -1
  14. data/lib/legion/tty/components/progress_panel.rb +4 -1
  15. data/lib/legion/tty/components/session_picker.rb +5 -1
  16. data/lib/legion/tty/components/status_bar.rb +7 -0
  17. data/lib/legion/tty/components/table_view.rb +7 -1
  18. data/lib/legion/tty/components/tool_call_parser.rb +4 -1
  19. data/lib/legion/tty/daemon_client.rb +46 -13
  20. data/lib/legion/tty/keybinding_manager.rb +4 -1
  21. data/lib/legion/tty/notification_gate.rb +40 -0
  22. data/lib/legion/tty/notify.rb +5 -1
  23. data/lib/legion/tty/screens/chat/export_commands.rb +8 -4
  24. data/lib/legion/tty/screens/chat/message_commands.rb +8 -4
  25. data/lib/legion/tty/screens/chat/model_commands.rb +6 -2
  26. data/lib/legion/tty/screens/chat/session_commands.rb +6 -2
  27. data/lib/legion/tty/screens/chat/ui_commands.rb +333 -6
  28. data/lib/legion/tty/screens/chat.rb +90 -16
  29. data/lib/legion/tty/screens/config.rb +6 -3
  30. data/lib/legion/tty/screens/dashboard.rb +26 -9
  31. data/lib/legion/tty/screens/extensions.rb +4 -1
  32. data/lib/legion/tty/screens/onboarding.rb +79 -75
  33. data/lib/legion/tty/session_store.rb +6 -3
  34. data/lib/legion/tty/version.rb +1 -1
  35. data/lib/legion/tty.rb +1 -0
  36. metadata +4 -3
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
3
4
  require_relative '../theme'
4
5
 
5
6
  module Legion
@@ -7,6 +8,11 @@ module Legion
7
8
  module Components
8
9
  # rubocop:disable Metrics/ClassLength
9
10
  class DigitalRain
11
+ include Legion::Logging::Helper
12
+
13
+ class << self
14
+ include Legion::Logging::Helper
15
+ end
10
16
  # rubocop:disable Naming/VariableNumber
11
17
  FADE_SHADES = %i[
12
18
  purple_12 purple_11 purple_10 purple_9 purple_8
@@ -40,7 +46,7 @@ module Legion
40
46
  .map { |s| s.name.sub(/^lex-/, '') }
41
47
  gems.empty? ? FALLBACK_NAMES : gems
42
48
  rescue StandardError => e
43
- Legion::Logging.debug("extension_names failed: #{e.message}") if defined?(Legion::Logging)
49
+ log.debug { "extension_names failed: #{e.message}" }
44
50
  FALLBACK_NAMES
45
51
  end
46
52
 
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tty-markdown'
4
+ require 'legion/logging'
4
5
 
5
6
  module Legion
6
7
  module TTY
7
8
  module Components
8
9
  module MarkdownView
10
+ class << self
11
+ include Legion::Logging::Helper
12
+ end
13
+
9
14
  def self.render(text, width: 80)
10
15
  ::TTY::Markdown.parse(text, width: width)
11
16
  rescue StandardError => e
12
- Legion::Logging.warn("markdown render failed: #{e.message}") if defined?(Legion::Logging)
17
+ log.warn { "markdown render failed: #{e.message}" }
13
18
  "#{text}\n(markdown render error: #{e.message})"
14
19
  end
15
20
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'English'
4
+ require 'legion/logging'
4
5
  require_relative '../theme'
5
6
 
6
7
  module Legion
@@ -8,6 +9,8 @@ module Legion
8
9
  module Components
9
10
  # rubocop:disable Metrics/ClassLength
10
11
  class MessageStream
12
+ include Legion::Logging::Helper
13
+
11
14
  attr_reader :messages, :scroll_offset
12
15
  attr_accessor :mute_system, :silent_mode, :highlights, :filter, :truncate_limit, :wrap_width, :show_numbers,
13
16
  :colorize, :show_timestamps
@@ -183,7 +186,7 @@ module Legion
183
186
  require_relative 'markdown_view'
184
187
  MarkdownView.render(text, width: width)
185
188
  rescue StandardError => e
186
- Legion::Logging.warn("render_markdown failed: #{e.message}") if defined?(Legion::Logging)
189
+ log.warn { "render_markdown failed: #{e.message}" }
187
190
  text
188
191
  end
189
192
 
@@ -208,7 +211,7 @@ module Legion
208
211
  result.gsub(pattern) { "#{HIGHLIGHT_COLOR}#{$LAST_MATCH_INFO}#{HIGHLIGHT_RESET}" }
209
212
  end
210
213
  rescue StandardError => e
211
- Legion::Logging.warn("apply_highlights failed: #{e.message}") if defined?(Legion::Logging)
214
+ log.warn { "apply_highlights failed: #{e.message}" }
212
215
  text
213
216
  end
214
217
 
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  module Components
6
8
  class ModelPicker
9
+ include Legion::Logging::Helper
10
+
7
11
  def initialize(current_provider: nil, current_model: nil)
8
12
  @current_provider = current_provider
9
13
  @current_model = current_model
@@ -38,7 +42,7 @@ module Legion
38
42
  end
39
43
  prompt.select('Select model:', choices, per_page: 10)
40
44
  rescue ::TTY::Reader::InputInterrupt, Interrupt => e
41
- Legion::Logging.debug("model picker cancelled: #{e.message}") if defined?(Legion::Logging)
45
+ log.debug { "model picker cancelled: #{e.message}" }
42
46
  nil
43
47
  end
44
48
  end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
3
4
  require_relative '../theme'
4
5
 
5
6
  module Legion
6
7
  module TTY
7
8
  module Components
8
9
  class ProgressPanel
10
+ include Legion::Logging::Helper
11
+
9
12
  attr_reader :title, :total, :current
10
13
 
11
14
  def initialize(title:, total:, output: $stdout)
@@ -62,7 +65,7 @@ module Legion
62
65
  width: 40
63
66
  )
64
67
  rescue LoadError => e
65
- Legion::Logging.debug("tty-progressbar not available: #{e.message}") if defined?(Legion::Logging)
68
+ log.debug { "tty-progressbar not available: #{e.message}" }
66
69
  nil
67
70
  end
68
71
  end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  module Components
6
8
  class SessionPicker
9
+ include Legion::Logging::Helper
10
+
7
11
  def initialize(session_store:)
8
12
  @session_store = session_store
9
13
  end
@@ -20,7 +24,7 @@ module Legion
20
24
  choices << { name: '+ New session', value: :new }
21
25
  prompt.select('Select session:', choices, per_page: 10)
22
26
  rescue ::TTY::Reader::InputInterrupt, Interrupt => e
23
- Legion::Logging.debug("session picker cancelled: #{e.message}") if defined?(Legion::Logging)
27
+ log.debug { "session picker cancelled: #{e.message}" }
24
28
  nil
25
29
  end
26
30
  end
@@ -15,6 +15,9 @@ module Legion
15
15
  end
16
16
 
17
17
  def notify(message:, level: :info, ttl: 5)
18
+ priority = level_to_priority(level)
19
+ return if priority != :urgent && !Legion::TTY::NotificationGate.should_deliver?(priority: priority)
20
+
18
21
  @notifications << Notification.new(message: message, level: level, ttl: ttl)
19
22
  end
20
23
 
@@ -122,6 +125,10 @@ module Legion
122
125
  Theme.c(:muted, "#{scroll[:current]}/#{scroll[:total]}")
123
126
  end
124
127
 
128
+ def level_to_priority(level)
129
+ { info: :ambient, success: :low, warning: :normal, error: :urgent }[level] || :normal
130
+ end
131
+
125
132
  def format_number(num)
126
133
  num.to_s.chars.reverse.each_slice(3).map(&:join).join(',').reverse
127
134
  end
@@ -1,15 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  module Components
6
8
  module TableView
9
+ class << self
10
+ include Legion::Logging::Helper
11
+ end
12
+
7
13
  def self.render(headers:, rows:, width: 80)
8
14
  require 'tty-table'
9
15
  table = ::TTY::Table.new(header: headers, rows: rows)
10
16
  table.render(:unicode, width: width, padding: [0, 1]) || ''
11
17
  rescue StandardError => e
12
- Legion::Logging.warn("table render failed: #{e.message}") if defined?(Legion::Logging)
18
+ log.warn { "table render failed: #{e.message}" }
13
19
  "Table render error: #{e.message}"
14
20
  end
15
21
  end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/json'
4
+ require 'legion/logging'
4
5
 
5
6
  module Legion
6
7
  module TTY
7
8
  module Components
8
9
  class ToolCallParser
10
+ include Legion::Logging::Helper
11
+
9
12
  OPEN_TAG = '<tool_call>'
10
13
  CLOSE_TAG = '</tool_call>'
11
14
  MAX_BUFFER = 4096
@@ -97,7 +100,7 @@ module Legion
97
100
 
98
101
  @on_tool_call.call(name: name, args: args)
99
102
  rescue StandardError => e
100
- Legion::Logging.warn("emit_tool_call failed: #{e.message}") if defined?(Legion::Logging)
103
+ log.warn { "emit_tool_call failed: #{e.message}" }
101
104
  @on_text.call("#{OPEN_TAG}#{json_str}#{CLOSE_TAG}")
102
105
  end
103
106
 
@@ -4,6 +4,7 @@ require 'net/http'
4
4
  require 'uri'
5
5
  require 'fileutils'
6
6
  require 'legion/json'
7
+ require 'legion/logging'
7
8
 
8
9
  module Legion
9
10
  module TTY
@@ -12,11 +13,14 @@ module Legion
12
13
 
13
14
  # rubocop:disable Metrics/ClassLength
14
15
  class << self
16
+ include Legion::Logging::Helper
17
+
15
18
  def configure(daemon_url: 'http://127.0.0.1:4567', cache_file: nil, timeout: 5)
16
19
  @daemon_url = daemon_url
17
20
  @cache_file = cache_file || File.expand_path('~/.legionio/catalog.json')
18
21
  @timeout = timeout
19
22
  @manifest = nil
23
+ log.info { "TTY daemon client configured daemon_url=#{@daemon_url} timeout=#{@timeout}" }
20
24
  end
21
25
 
22
26
  def available?
@@ -26,7 +30,7 @@ module Legion
26
30
  end
27
31
  response.code.to_i == 200
28
32
  rescue StandardError => e
29
- Legion::Logging.debug("daemon available? check failed: #{e.message}") if defined?(Legion::Logging)
33
+ handle_exception(e, level: :debug, operation: 'tty.daemon_client.available?', daemon_url: daemon_url)
30
34
  false
31
35
  end
32
36
 
@@ -37,12 +41,9 @@ module Legion
37
41
  end
38
42
  return nil unless response.code.to_i == 200
39
43
 
40
- body = Legion::JSON.load(response.body)
41
- @manifest = body[:data]
42
- write_cache(@manifest)
43
- @manifest
44
+ store_manifest(Legion::JSON.load(response.body)[:data])
44
45
  rescue StandardError => e
45
- Legion::Logging.warn("fetch_manifest failed: #{e.message}") if defined?(Legion::Logging)
46
+ handle_exception(e, level: :warn, operation: 'tty.daemon_client.fetch_manifest', daemon_url: daemon_url)
46
47
  nil
47
48
  end
48
49
 
@@ -53,7 +54,7 @@ module Legion
53
54
 
54
55
  @manifest = Legion::JSON.load(File.read(@cache_file))
55
56
  rescue StandardError => e
56
- Legion::Logging.warn("cached_manifest failed: #{e.message}") if defined?(Legion::Logging)
57
+ handle_exception(e, level: :warn, operation: 'tty.daemon_client.cached_manifest', cache_file: @cache_file)
57
58
  nil
58
59
  end
59
60
 
@@ -80,29 +81,47 @@ module Legion
80
81
 
81
82
  uri = URI("#{daemon_url}/api/llm/chat")
82
83
  payload = Legion::JSON.dump({ message: message, model: model, provider: provider })
84
+ log.debug { "TTY chat request model=#{model} provider=#{provider} message_length=#{message.to_s.length}" }
83
85
  response = post_json(uri, payload)
84
86
 
85
87
  return nil unless response && SUCCESS_CODES.include?(response.code.to_i)
86
88
 
87
89
  Legion::JSON.load(response.body)
88
90
  rescue StandardError => e
89
- Legion::Logging.warn("chat failed: #{e.message}") if defined?(Legion::Logging)
91
+ handle_exception(e, level: :warn, operation: 'tty.daemon_client.chat',
92
+ daemon_url: daemon_url, model: model, provider: provider)
90
93
  nil
91
94
  end
92
95
 
93
96
  def inference(messages:, tools: [], model: nil, provider: nil, timeout: 120)
97
+ log.debug { "TTY inference model=#{model} provider=#{provider} msgs=#{Array(messages).size}" }
94
98
  response = post_inference(messages: messages, tools: tools, model: model,
95
99
  provider: provider, timeout: timeout)
96
100
  return inference_error_result(response) unless SUCCESS_CODES.include?(response.code.to_i)
97
101
 
98
- body = Legion::JSON.load(response.body)
99
- data = body[:data] || body
100
- { status: :ok, data: data }
102
+ parse_inference_response(response)
101
103
  rescue StandardError => e
102
- Legion::Logging.warn("inference failed: #{e.message}") if defined?(Legion::Logging)
104
+ handle_exception(e, level: :warn, operation: 'tty.daemon_client.inference',
105
+ daemon_url: daemon_url, model: model, provider: provider, timeout: timeout)
103
106
  { status: :unavailable, error: { message: e.message } }
104
107
  end
105
108
 
109
+ def run_tool(name:, args: {})
110
+ uri = URI("#{daemon_url}/api/tools/run")
111
+ payload = Legion::JSON.dump({ name: name, args: args })
112
+ response = post_json(uri, payload)
113
+ if SUCCESS_CODES.include?(response.code.to_i)
114
+ body = Legion::JSON.load(response.body)
115
+ { status: :ok, data: body[:data] || body }
116
+ else
117
+ { status: :error, error: "HTTP #{response.code}" }
118
+ end
119
+ rescue StandardError => e
120
+ handle_exception(e, level: :warn, operation: 'tty.daemon_client.run_tool',
121
+ daemon_url: daemon_url, name: name)
122
+ { status: :unavailable }
123
+ end
124
+
106
125
  def reset!
107
126
  @daemon_url = nil
108
127
  @cache_file = nil
@@ -112,6 +131,19 @@ module Legion
112
131
 
113
132
  private
114
133
 
134
+ def parse_inference_response(response)
135
+ body = Legion::JSON.load(response.body)
136
+ data = body[:data] || body
137
+ { status: :ok, data: data }
138
+ end
139
+
140
+ def store_manifest(data)
141
+ @manifest = data
142
+ write_cache(@manifest)
143
+ log.info { "TTY fetched daemon manifest entries=#{Array(@manifest).size}" }
144
+ @manifest
145
+ end
146
+
115
147
  def post_inference(messages:, tools:, model:, provider:, timeout:)
116
148
  uri = URI("#{daemon_url}/api/llm/inference")
117
149
  payload = Legion::JSON.dump({ messages: messages, tools: tools,
@@ -151,8 +183,9 @@ module Legion
151
183
 
152
184
  FileUtils.mkdir_p(File.dirname(@cache_file))
153
185
  File.write(@cache_file, Legion::JSON.dump(data))
186
+ log.debug { "TTY daemon manifest cache updated file=#{@cache_file}" }
154
187
  rescue StandardError => e
155
- Legion::Logging.warn("write_cache failed: #{e.message}") if defined?(Legion::Logging)
188
+ handle_exception(e, level: :warn, operation: 'tty.daemon_client.write_cache', cache_file: @cache_file)
156
189
  nil
157
190
  end
158
191
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/json'
4
+ require 'legion/logging'
4
5
  require 'fileutils'
5
6
 
6
7
  module Legion
@@ -15,6 +16,8 @@ module Legion
15
16
  #
16
17
  # User overrides loaded from ~/.legionio/keybindings.json at boot.
17
18
  class KeybindingManager
19
+ include Legion::Logging::Helper
20
+
18
21
  CONTEXTS = %i[global chat dashboard extensions config command_palette session_picker history].freeze
19
22
 
20
23
  OVERRIDES_PATH = File.expand_path('~/.legionio/keybindings.json')
@@ -108,7 +111,7 @@ module Legion
108
111
  raw = Legion::JSON.parse(File.read(@overrides_path), symbolize_names: true)
109
112
  raw.each { |key, cfg| apply_override(key, cfg) }
110
113
  rescue Legion::JSON::ParseError => e
111
- Legion::Logging.warn("keybindings load failed: #{e.message}") if defined?(Legion::Logging)
114
+ log.warn { "keybindings load failed: #{e.message}" }
112
115
  end
113
116
 
114
117
  private
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/logging'
4
+
5
+ module Legion
6
+ module TTY
7
+ module NotificationGate
8
+ class << self
9
+ include Legion::Logging::Helper
10
+
11
+ def should_deliver?(priority: :normal)
12
+ return true unless gaia_gate_available?
13
+
14
+ Legion::Gaia::NotificationGate.instance.should_notify?(priority: priority)
15
+ rescue StandardError => e
16
+ log.debug { "NotificationGate.should_deliver? failed: #{e.message}" }
17
+ true
18
+ end
19
+
20
+ def update_presence(availability:, activity: nil)
21
+ return unless gaia_gate_available?
22
+
23
+ Legion::Gaia::NotificationGate.instance.update_presence(
24
+ availability: availability, activity: activity
25
+ )
26
+ rescue StandardError => e
27
+ log.debug { "NotificationGate.update_presence failed: #{e.message}" }
28
+ nil
29
+ end
30
+
31
+ private
32
+
33
+ def gaia_gate_available?
34
+ defined?(Legion::Gaia::NotificationGate) &&
35
+ Legion::Gaia::NotificationGate.respond_to?(:instance)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  # Notify — OS-level terminal notification dispatcher with auto-detection.
@@ -20,6 +22,8 @@ module Legion
20
22
  BACKENDS = %w[iterm2 kitty ghostty notify_send osascript bell].freeze
21
23
 
22
24
  class << self
25
+ include Legion::Logging::Helper
26
+
23
27
  # Send a notification.
24
28
  # @param message [String] notification body
25
29
  # @param title [String] notification title
@@ -82,7 +86,7 @@ module Legion
82
86
  else notify_bell
83
87
  end
84
88
  rescue StandardError => e
85
- Legion::Logging.warn("Notify dispatch failed: #{e.message}") if defined?(Legion::Logging)
89
+ log.warn { "Notify dispatch failed: #{e.message}" }
86
90
  notify_bell
87
91
  end
88
92
 
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  module Screens
6
8
  class Chat < Base
7
9
  module ExportCommands
10
+ include Legion::Logging::Helper
11
+
8
12
  private
9
13
 
10
14
  def handle_export(input)
@@ -15,7 +19,7 @@ module Legion
15
19
  @message_stream.add_message(role: :system, content: "Exported to: #{path}")
16
20
  :handled
17
21
  rescue StandardError => e
18
- Legion::Logging.warn("handle_export failed: #{e.message}") if defined?(Legion::Logging)
22
+ log.warn { "handle_export failed: #{e.message}" }
19
23
  @message_stream.add_message(role: :system, content: "Export failed: #{e.message}")
20
24
  :handled
21
25
  end
@@ -129,7 +133,7 @@ module Legion
129
133
  @message_stream.add_message(role: :system, content: "Bookmarks exported to: #{path}")
130
134
  :handled
131
135
  rescue StandardError => e
132
- Legion::Logging.warn("handle_bookmark failed: #{e.message}") if defined?(Legion::Logging)
136
+ log.warn { "handle_bookmark failed: #{e.message}" }
133
137
  @message_stream.add_message(role: :system, content: "Bookmark export failed: #{e.message}")
134
138
  :handled
135
139
  end
@@ -152,7 +156,7 @@ module Legion
152
156
  end
153
157
  :handled
154
158
  rescue StandardError => e
155
- Legion::Logging.warn("handle_tee failed: #{e.message}") if defined?(Legion::Logging)
159
+ log.warn { "handle_tee failed: #{e.message}" }
156
160
  @message_stream.add_message(role: :system, content: "Tee error: #{e.message}")
157
161
  :handled
158
162
  end
@@ -162,7 +166,7 @@ module Legion
162
166
 
163
167
  File.open(@tee_path, 'a') { |f| f.puts(line) }
164
168
  rescue StandardError => e
165
- Legion::Logging.warn("tee_message failed: #{e.message}") if defined?(Legion::Logging)
169
+ log.warn { "tee_message failed: #{e.message}" }
166
170
  nil
167
171
  end
168
172
  end
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  module Screens
6
8
  class Chat < Base
7
9
  module MessageCommands
10
+ include Legion::Logging::Helper
11
+
8
12
  INJECT_VALID_ROLES = %w[user assistant system].freeze
9
13
  TRANSFORM_OPS = %w[upcase downcase reverse strip squeeze].freeze
10
14
 
@@ -98,7 +102,7 @@ module Legion
98
102
  display_grep_results(results, pattern_str)
99
103
  :handled
100
104
  rescue RegexpError => e
101
- Legion::Logging.warn("handle_grep regex error: #{e.message}") if defined?(Legion::Logging)
105
+ log.warn { "handle_grep regex error: #{e.message}" }
102
106
  @message_stream.add_message(role: :system, content: "Invalid regex: #{e.message}")
103
107
  :handled
104
108
  end
@@ -302,11 +306,11 @@ module Legion
302
306
  def copy_to_clipboard(text)
303
307
  IO.popen('pbcopy', 'w') { |io| io.write(text) }
304
308
  rescue Errno::ENOENT => e
305
- Legion::Logging.debug("pbcopy not available: #{e.message}") if defined?(Legion::Logging)
309
+ log.debug { "pbcopy not available: #{e.message}" }
306
310
  begin
307
311
  IO.popen('xclip -selection clipboard', 'w') { |io| io.write(text) }
308
312
  rescue Errno::ENOENT => e
309
- Legion::Logging.debug("xclip not available: #{e.message}") if defined?(Legion::Logging)
313
+ log.debug { "xclip not available: #{e.message}" }
310
314
  nil
311
315
  end
312
316
  end
@@ -398,7 +402,7 @@ module Legion
398
402
  parsed = ::JSON.parse(raw, symbolize_names: true)
399
403
  parsed.is_a?(Array) ? parsed : []
400
404
  rescue StandardError => e
401
- Legion::Logging.warn("load_favorites failed: #{e.message}") if defined?(Legion::Logging)
405
+ log.warn { "load_favorites failed: #{e.message}" }
402
406
  []
403
407
  end
404
408
 
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  module Screens
6
8
  class Chat < Base
7
9
  module ModelCommands
10
+ include Legion::Logging::Helper
11
+
8
12
  private
9
13
 
10
14
  def handle_model(input)
@@ -24,7 +28,7 @@ module Legion
24
28
  @message_stream.add_message(role: :system,
25
29
  content: "Model preference set to: #{name} (applied on next daemon request)")
26
30
  rescue StandardError => e
27
- Legion::Logging.warn("switch_model failed: #{e.message}") if defined?(Legion::Logging)
31
+ log.warn { "switch_model failed: #{e.message}" }
28
32
  @message_stream.add_message(role: :system, content: "Failed to switch model: #{e.message}")
29
33
  end
30
34
 
@@ -40,7 +44,7 @@ module Legion
40
44
 
41
45
  Legion::LLM.chat(provider: name)
42
46
  rescue StandardError => e
43
- Legion::Logging.warn("try_provider_switch failed: #{e.message}") if defined?(Legion::Logging)
47
+ log.warn { "try_provider_switch failed: #{e.message}" }
44
48
  nil
45
49
  end
46
50
 
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+
3
5
  module Legion
4
6
  module TTY
5
7
  module Screens
6
8
  class Chat < Base
7
9
  module SessionCommands
10
+ include Legion::Logging::Helper
11
+
8
12
  private
9
13
 
10
14
  def handle_save(input)
@@ -142,7 +146,7 @@ module Legion
142
146
  @last_autosave = Time.now
143
147
  @status_bar.notify(message: 'Autosaved', level: :info, ttl: 2)
144
148
  rescue StandardError => e
145
- Legion::Logging.warn("check_autosave failed: #{e.message}") if defined?(Legion::Logging)
149
+ log.warn { "check_autosave failed: #{e.message}" }
146
150
  nil
147
151
  end
148
152
 
@@ -154,7 +158,7 @@ module Legion
154
158
  end
155
159
  @session_store.save(@session_name, messages: @message_stream.messages)
156
160
  rescue StandardError => e
157
- Legion::Logging.warn("auto_save_session failed: #{e.message}") if defined?(Legion::Logging)
161
+ log.warn { "auto_save_session failed: #{e.message}" }
158
162
  nil
159
163
  end
160
164