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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4f6ee315fe6ff9851580bebff6f0187665a2c53f1719c48ad807879651eee44
4
- data.tar.gz: 938e195e473fb65a27840f31b82cefa9032c9f500d828f2844d8ca58b7f9bc95
3
+ metadata.gz: 3e2b8a679110725634b3db870bb623fc8f5cef89706890f9c57c579490c85114
4
+ data.tar.gz: 6487e27647ed48406731a9c0c1bf1664b64831b60139dd698ae794d2dc8d3871
5
5
  SHA512:
6
- metadata.gz: 27167cd635e065fca190930586ba383ffe8b8595c772eb917dd243aa7832b6d23807198e2e73b25b942154fb155c455e239c52e51b68ad16675beb363224be28
7
- data.tar.gz: cdbcae2d8a592ea9b6316f3069f593c49b11f242ddfedd5306313a53b6bce48e5c718c06fb2cbb21e44f4c8b323a4fae06839b47fda2cb41bd8c7312a405be55
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\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
- lines = [Theme.c(:accent, ' Services')]
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
- lines = [Theme.c(:accent, " Extensions (#{count})")]
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
- lines = [Theme.c(:accent, ' System')]
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
- lines = [Theme.c(:accent, ' Recent Activity')]
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, 'Ctrl+D')}=dashboard #{Theme.c(:muted, 'Ctrl+C')}=quit"
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
  {
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.4.10'
5
+ VERSION = '0.4.11'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-tty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.10
4
+ version: 0.4.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity