legion-tty 0.4.10 → 0.4.11
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 +8 -0
- data/lib/legion/tty/screens/chat.rb +46 -2
- data/lib/legion/tty/screens/dashboard.rb +96 -5
- data/lib/legion/tty/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e2b8a679110725634b3db870bb623fc8f5cef89706890f9c57c579490c85114
|
|
4
|
+
data.tar.gz: 6487e27647ed48406731a9c0c1bf1664b64831b60139dd698ae794d2dc8d3871
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c89fb3dbcb807c628889b2b60566b3b23cad1356c26de42cd1d02d75a2977e7856528b0193e8bb9dd630ee4cf52b2b55e06e009c8fc3e34550a1712d6768b68
|
|
7
|
+
data.tar.gz: ae426495d1eb8708723a5c2804a4a77e4a557480324c23b370b0ebe324ea1a7ece957665b133f7810952f03f586cb0e87dcef3e1c78e9fcde81fe13e0e6e4760
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.11] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Dashboard LLM status panel: shows provider, model, started/daemon status with green/red icons
|
|
7
|
+
- Dashboard panel navigation: j/k/arrows to move between panels, 1-5 to jump, 'e' to open extensions
|
|
8
|
+
- `/uptime` command: show current chat session elapsed time
|
|
9
|
+
- `/bookmark` command: export all pinned messages to markdown file
|
|
10
|
+
|
|
3
11
|
## [0.4.10] - 2026-03-19
|
|
4
12
|
|
|
5
13
|
### Added
|
|
@@ -15,7 +15,7 @@ module Legion
|
|
|
15
15
|
SLASH_COMMANDS = %w[/help /quit /clear /compact /copy /diff /model /session /cost /export /tools /dashboard
|
|
16
16
|
/hotkeys /save /load /sessions /system /delete /plan /palette /extensions /config
|
|
17
17
|
/theme /search /stats /personality /undo /history /pin /pins /rename
|
|
18
|
-
/context /alias /snippet /debug].freeze
|
|
18
|
+
/context /alias /snippet /debug /uptime /bookmark].freeze
|
|
19
19
|
|
|
20
20
|
PERSONALITIES = {
|
|
21
21
|
'default' => 'You are Legion, an async cognition engine and AI assistant. Be helpful and concise.',
|
|
@@ -27,6 +27,7 @@ module Legion
|
|
|
27
27
|
|
|
28
28
|
attr_reader :message_stream, :status_bar
|
|
29
29
|
|
|
30
|
+
# rubocop:disable Metrics/AbcSize
|
|
30
31
|
def initialize(app, output: $stdout, input_bar: nil)
|
|
31
32
|
super(app)
|
|
32
33
|
@output = output
|
|
@@ -43,8 +44,11 @@ module Legion
|
|
|
43
44
|
@aliases = {}
|
|
44
45
|
@snippets = {}
|
|
45
46
|
@debug_mode = false
|
|
47
|
+
@session_start = Time.now
|
|
46
48
|
end
|
|
47
49
|
|
|
50
|
+
# rubocop:enable Metrics/AbcSize
|
|
51
|
+
|
|
48
52
|
def activate
|
|
49
53
|
@running = true
|
|
50
54
|
cfg = safe_config
|
|
@@ -315,6 +319,8 @@ module Legion
|
|
|
315
319
|
when '/alias' then handle_alias(input)
|
|
316
320
|
when '/snippet' then handle_snippet(input)
|
|
317
321
|
when '/debug' then handle_debug
|
|
322
|
+
when '/uptime' then handle_uptime
|
|
323
|
+
when '/bookmark' then handle_bookmark
|
|
318
324
|
else :handled
|
|
319
325
|
end
|
|
320
326
|
end
|
|
@@ -342,7 +348,9 @@ module Legion
|
|
|
342
348
|
"/context -- show active session context summary\n " \
|
|
343
349
|
"/alias [shortname /command] -- create or list command aliases\n " \
|
|
344
350
|
"/snippet save|load|list|delete <name> -- manage reusable text snippets\n " \
|
|
345
|
-
"/debug -- toggle debug mode (shows internal state)\n
|
|
351
|
+
"/debug -- toggle debug mode (shows internal state)\n " \
|
|
352
|
+
"/uptime -- show how long this session has been active\n " \
|
|
353
|
+
"/bookmark -- export pinned messages to a markdown file\n\n" \
|
|
346
354
|
'Hotkeys: Ctrl+D=dashboard Ctrl+K=palette Ctrl+S=sessions Esc=back'
|
|
347
355
|
)
|
|
348
356
|
:handled
|
|
@@ -1053,6 +1061,42 @@ module Legion
|
|
|
1053
1061
|
:handled
|
|
1054
1062
|
end
|
|
1055
1063
|
|
|
1064
|
+
def handle_uptime
|
|
1065
|
+
elapsed = Time.now - @session_start
|
|
1066
|
+
hours = (elapsed / 3600).to_i
|
|
1067
|
+
minutes = ((elapsed % 3600) / 60).to_i
|
|
1068
|
+
seconds = (elapsed % 60).to_i
|
|
1069
|
+
@message_stream.add_message(role: :system, content: "Session uptime: #{hours}h #{minutes}m #{seconds}s")
|
|
1070
|
+
:handled
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
# rubocop:disable Metrics/AbcSize
|
|
1074
|
+
def handle_bookmark
|
|
1075
|
+
require 'fileutils'
|
|
1076
|
+
if @pinned_messages.empty?
|
|
1077
|
+
@message_stream.add_message(role: :system, content: 'No pinned messages to export.')
|
|
1078
|
+
return :handled
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
exports_dir = File.expand_path('~/.legionio/exports')
|
|
1082
|
+
FileUtils.mkdir_p(exports_dir)
|
|
1083
|
+
timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
|
|
1084
|
+
path = File.join(exports_dir, "bookmarks-#{timestamp}.md")
|
|
1085
|
+
lines = ["# Pinned Messages\n", "_Exported: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}_\n\n---\n"]
|
|
1086
|
+
@pinned_messages.each_with_index do |msg, i|
|
|
1087
|
+
role_label = msg[:role].to_s.capitalize
|
|
1088
|
+
lines << "\n## Bookmark #{i + 1} (#{role_label})\n\n#{msg[:content]}\n"
|
|
1089
|
+
end
|
|
1090
|
+
File.write(path, lines.join)
|
|
1091
|
+
@message_stream.add_message(role: :system, content: "Bookmarks exported to: #{path}")
|
|
1092
|
+
:handled
|
|
1093
|
+
rescue StandardError => e
|
|
1094
|
+
@message_stream.add_message(role: :system, content: "Bookmark export failed: #{e.message}")
|
|
1095
|
+
:handled
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
# rubocop:enable Metrics/AbcSize
|
|
1099
|
+
|
|
1056
1100
|
def debug_segment
|
|
1057
1101
|
return nil unless @debug_mode
|
|
1058
1102
|
|
|
@@ -8,11 +8,14 @@ module Legion
|
|
|
8
8
|
module Screens
|
|
9
9
|
# rubocop:disable Metrics/ClassLength
|
|
10
10
|
class Dashboard < Base
|
|
11
|
+
PANELS = %i[services llm extensions system activity].freeze
|
|
12
|
+
|
|
11
13
|
def initialize(app)
|
|
12
14
|
super
|
|
13
15
|
@last_refresh = nil
|
|
14
16
|
@refresh_interval = 5
|
|
15
17
|
@cached_data = {}
|
|
18
|
+
@selected_panel = 0
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def activate
|
|
@@ -26,6 +29,7 @@ module Legion
|
|
|
26
29
|
rows = []
|
|
27
30
|
rows.concat(render_header(width))
|
|
28
31
|
rows.concat(render_services_panel(width))
|
|
32
|
+
rows.concat(render_llm_panel(width))
|
|
29
33
|
rows.concat(render_extensions_panel(width))
|
|
30
34
|
rows.concat(render_system_panel(width))
|
|
31
35
|
rows.concat(render_activity_panel(width, remaining_height(height, rows.size)))
|
|
@@ -36,6 +40,7 @@ module Legion
|
|
|
36
40
|
|
|
37
41
|
# rubocop:enable Metrics/AbcSize
|
|
38
42
|
|
|
43
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
39
44
|
def handle_input(key)
|
|
40
45
|
case key
|
|
41
46
|
when 'r', :f5
|
|
@@ -43,15 +48,35 @@ module Legion
|
|
|
43
48
|
:handled
|
|
44
49
|
when 'q', :escape
|
|
45
50
|
:pop_screen
|
|
51
|
+
when 'j', :down
|
|
52
|
+
@selected_panel = (@selected_panel + 1) % PANELS.size
|
|
53
|
+
:handled
|
|
54
|
+
when 'k', :up
|
|
55
|
+
@selected_panel = (@selected_panel - 1) % PANELS.size
|
|
56
|
+
:handled
|
|
57
|
+
when '1' then navigate_to_panel(0)
|
|
58
|
+
when '2' then navigate_to_panel(1)
|
|
59
|
+
when '3' then navigate_to_panel(2)
|
|
60
|
+
when '4' then navigate_to_panel(3)
|
|
61
|
+
when '5' then navigate_to_panel(4)
|
|
62
|
+
when 'e'
|
|
63
|
+
extensions_shortcut
|
|
46
64
|
else
|
|
47
65
|
:pass
|
|
48
66
|
end
|
|
49
67
|
end
|
|
50
68
|
|
|
69
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
70
|
+
|
|
71
|
+
def selected_panel
|
|
72
|
+
PANELS[@selected_panel]
|
|
73
|
+
end
|
|
74
|
+
|
|
51
75
|
def refresh_data
|
|
52
76
|
@last_refresh = Time.now
|
|
53
77
|
@cached_data = {
|
|
54
78
|
services: probe_services,
|
|
79
|
+
llm: llm_info,
|
|
55
80
|
extensions: discover_extensions,
|
|
56
81
|
system: system_info,
|
|
57
82
|
activity: recent_activity
|
|
@@ -73,7 +98,8 @@ module Legion
|
|
|
73
98
|
|
|
74
99
|
def render_services_panel(_width)
|
|
75
100
|
services = @cached_data[:services] || {}
|
|
76
|
-
|
|
101
|
+
prefix = panel_prefix(:services)
|
|
102
|
+
lines = [Theme.c(:accent, "#{prefix}Services")]
|
|
77
103
|
services.each do |name, info|
|
|
78
104
|
icon = info[:running] ? Theme.c(:success, "\u2713") : Theme.c(:error, "\u2717")
|
|
79
105
|
port_str = Theme.c(:muted, ":#{info[:port]}")
|
|
@@ -83,10 +109,29 @@ module Legion
|
|
|
83
109
|
lines
|
|
84
110
|
end
|
|
85
111
|
|
|
112
|
+
# rubocop:disable Metrics/AbcSize
|
|
113
|
+
def render_llm_panel(_width)
|
|
114
|
+
llm = @cached_data[:llm] || {}
|
|
115
|
+
prefix = panel_prefix(:llm)
|
|
116
|
+
lines = [Theme.c(:accent, "#{prefix}LLM")]
|
|
117
|
+
started_icon = llm[:started] ? Theme.c(:success, "\u2713") : Theme.c(:error, "\u2717")
|
|
118
|
+
daemon_icon = llm[:daemon] ? Theme.c(:success, "\u2713") : Theme.c(:error, "\u2717")
|
|
119
|
+
lines << " #{started_icon} Legion::LLM started"
|
|
120
|
+
lines << " #{daemon_icon} Daemon available"
|
|
121
|
+
lines << " Provider: #{Theme.c(:secondary, llm[:provider] || 'none')}"
|
|
122
|
+
lines << " Model: #{Theme.c(:secondary, llm[:model] || 'none')}" if llm[:model]
|
|
123
|
+
lines << ''
|
|
124
|
+
lines
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# rubocop:enable Metrics/AbcSize
|
|
128
|
+
|
|
129
|
+
# rubocop:disable Metrics/AbcSize
|
|
86
130
|
def render_extensions_panel(_width)
|
|
87
131
|
extensions = @cached_data[:extensions] || []
|
|
88
132
|
count = extensions.size
|
|
89
|
-
|
|
133
|
+
prefix = panel_prefix(:extensions)
|
|
134
|
+
lines = [Theme.c(:accent, "#{prefix}Extensions (#{count})")]
|
|
90
135
|
if extensions.empty?
|
|
91
136
|
lines << Theme.c(:muted, ' No lex-* gems found')
|
|
92
137
|
else
|
|
@@ -100,10 +145,13 @@ module Legion
|
|
|
100
145
|
lines
|
|
101
146
|
end
|
|
102
147
|
|
|
148
|
+
# rubocop:enable Metrics/AbcSize
|
|
149
|
+
|
|
103
150
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
104
151
|
def render_system_panel(_width)
|
|
105
152
|
sys = @cached_data[:system] || {}
|
|
106
|
-
|
|
153
|
+
prefix = panel_prefix(:system)
|
|
154
|
+
lines = [Theme.c(:accent, "#{prefix}System")]
|
|
107
155
|
lines << " Ruby: #{Theme.c(:secondary, sys[:ruby_version] || 'unknown')}"
|
|
108
156
|
lines << " OS: #{Theme.c(:secondary, sys[:os] || 'unknown')}"
|
|
109
157
|
lines << " Host: #{Theme.c(:secondary, sys[:hostname] || 'unknown')}"
|
|
@@ -117,7 +165,8 @@ module Legion
|
|
|
117
165
|
|
|
118
166
|
def render_activity_panel(_width, max_lines)
|
|
119
167
|
activity = @cached_data[:activity] || []
|
|
120
|
-
|
|
168
|
+
prefix = panel_prefix(:activity)
|
|
169
|
+
lines = [Theme.c(:accent, "#{prefix}Recent Activity")]
|
|
121
170
|
if activity.empty?
|
|
122
171
|
lines << Theme.c(:muted, ' No recent activity')
|
|
123
172
|
else
|
|
@@ -132,7 +181,8 @@ module Legion
|
|
|
132
181
|
|
|
133
182
|
def render_help_bar(width)
|
|
134
183
|
help = " #{Theme.c(:muted, 'r')}=refresh #{Theme.c(:muted, 'q')}=back " \
|
|
135
|
-
"#{Theme.c(:muted, '
|
|
184
|
+
"#{Theme.c(:muted, 'j/k')}=navigate #{Theme.c(:muted, '1-5')}=jump " \
|
|
185
|
+
"#{Theme.c(:muted, 'e')}=extensions #{Theme.c(:muted, 'Ctrl+C')}=quit"
|
|
136
186
|
[Theme.c(:muted, '-' * width), help]
|
|
137
187
|
end
|
|
138
188
|
|
|
@@ -148,6 +198,47 @@ module Legion
|
|
|
148
198
|
end
|
|
149
199
|
end
|
|
150
200
|
|
|
201
|
+
def panel_prefix(panel_name)
|
|
202
|
+
PANELS[@selected_panel] == panel_name ? '>> ' : ' '
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def navigate_to_panel(index)
|
|
206
|
+
@selected_panel = index
|
|
207
|
+
:handled
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def extensions_shortcut
|
|
211
|
+
if PANELS[@selected_panel] == :extensions && @app.respond_to?(:screen_manager)
|
|
212
|
+
require_relative '../screens/extensions'
|
|
213
|
+
@app.screen_manager.push(Screens::Extensions.new(@app))
|
|
214
|
+
:handled
|
|
215
|
+
else
|
|
216
|
+
:pass
|
|
217
|
+
end
|
|
218
|
+
rescue LoadError, StandardError
|
|
219
|
+
:pass
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
223
|
+
def llm_info
|
|
224
|
+
info = { provider: 'none', model: nil, started: false, daemon: false }
|
|
225
|
+
if defined?(Legion::LLM)
|
|
226
|
+
info[:started] = Legion::LLM.respond_to?(:started?) && Legion::LLM.started?
|
|
227
|
+
settings = Legion::LLM.respond_to?(:settings) ? Legion::LLM.settings : {}
|
|
228
|
+
info[:provider] = settings[:default_provider]&.to_s || 'none'
|
|
229
|
+
info[:model] = settings[:model]&.to_s
|
|
230
|
+
end
|
|
231
|
+
if defined?(Legion::LLM::DaemonClient)
|
|
232
|
+
info[:daemon] = Legion::LLM::DaemonClient.respond_to?(:available?) &&
|
|
233
|
+
Legion::LLM::DaemonClient.available?
|
|
234
|
+
end
|
|
235
|
+
info
|
|
236
|
+
rescue StandardError
|
|
237
|
+
info
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
241
|
+
|
|
151
242
|
def probe_services
|
|
152
243
|
require 'socket'
|
|
153
244
|
{
|
data/lib/legion/tty/version.rb
CHANGED