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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +3 -3
- data/lib/legion/tty/app.rb +27 -21
- data/lib/legion/tty/background/github_probe.rb +27 -24
- data/lib/legion/tty/background/kerberos_probe.rb +2 -1
- data/lib/legion/tty/background/llm_probe.rb +12 -8
- data/lib/legion/tty/background/scanner.rb +19 -16
- data/lib/legion/tty/components/command_palette.rb +5 -1
- data/lib/legion/tty/components/digital_rain.rb +7 -1
- data/lib/legion/tty/components/markdown_view.rb +6 -1
- data/lib/legion/tty/components/message_stream.rb +5 -2
- data/lib/legion/tty/components/model_picker.rb +5 -1
- data/lib/legion/tty/components/progress_panel.rb +4 -1
- data/lib/legion/tty/components/session_picker.rb +5 -1
- data/lib/legion/tty/components/status_bar.rb +7 -0
- data/lib/legion/tty/components/table_view.rb +7 -1
- data/lib/legion/tty/components/tool_call_parser.rb +4 -1
- data/lib/legion/tty/daemon_client.rb +46 -13
- data/lib/legion/tty/keybinding_manager.rb +4 -1
- data/lib/legion/tty/notification_gate.rb +40 -0
- data/lib/legion/tty/notify.rb +5 -1
- data/lib/legion/tty/screens/chat/export_commands.rb +8 -4
- data/lib/legion/tty/screens/chat/message_commands.rb +8 -4
- data/lib/legion/tty/screens/chat/model_commands.rb +6 -2
- data/lib/legion/tty/screens/chat/session_commands.rb +6 -2
- data/lib/legion/tty/screens/chat/ui_commands.rb +333 -6
- data/lib/legion/tty/screens/chat.rb +90 -16
- data/lib/legion/tty/screens/config.rb +6 -3
- data/lib/legion/tty/screens/dashboard.rb +26 -9
- data/lib/legion/tty/screens/extensions.rb +4 -1
- data/lib/legion/tty/screens/onboarding.rb +79 -75
- data/lib/legion/tty/session_store.rb +6 -3
- data/lib/legion/tty/version.rb +1 -1
- data/lib/legion/tty.rb +1 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
data = body[:data] || body
|
|
100
|
-
{ status: :ok, data: data }
|
|
102
|
+
parse_inference_response(response)
|
|
101
103
|
rescue StandardError => e
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/legion/tty/notify.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
log.warn { "auto_save_session failed: #{e.message}" }
|
|
158
162
|
nil
|
|
159
163
|
end
|
|
160
164
|
|