legion-tty 0.4.17 → 0.4.18

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: 764baccaffa8d9863f8cb85248c15775fce561f729b74d70ff0b597f343eb0f5
4
- data.tar.gz: 39cacbd736ae3aacbeb54c429428c55cbde2a9325654f6d32e3e272c60586f7e
3
+ metadata.gz: 6faa2820fd40c949a40eb6e336cf75067e3ab400d9631980fc49fda2f0b5dc29
4
+ data.tar.gz: 35c674132cb7f41f0c2c8d4150afe10c99f4ac30fed392e78477fb074c383d67
5
5
  SHA512:
6
- metadata.gz: f91d4641d6ca7566d9229c1ae644b820f7963793a5a93d92be56547d178f5c205ddcf28ed2b0303fd545c86c85bfeda99f1ebe226392940e748f504a6045868c
7
- data.tar.gz: 03c2a6d201175628179ad926a6453c9446bb1bcdfd999dafafa38dc59aa0a0a422ebe654a89b8751f5f745e45a2c51ca28bf84ee9ba60c368120b9f13e6b6e97
6
+ metadata.gz: 3766b9312e3fa871e39cfc1b42eac013c82ab680509f10f6f034474fda23381d1bfa4bc67e9e7a95d1557ca546ae3cd6d209818d4fe814111b33cc35b167f46f
7
+ data.tar.gz: c0644da99c0c8cd8b0002366e2e9f46edf23d99a157a8e5652165e3112442516da2178e16ca91596afdbef04a57fe379a347bd6f5c6883903bae24f599a294dd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.18] - 2026-03-19
4
+
5
+ ### Added
6
+ - `/focus` command: toggle minimal UI mode (hides status bar for distraction-free writing)
7
+ - `/retry` command: resend last user message to LLM, replacing previous assistant response
8
+ - `/merge <session>` command: import messages from another saved session into current conversation
9
+ - `/sort [length|role]` command: display messages sorted by character length or grouped by role
10
+
3
11
  ## [0.4.17] - 2026-03-19
4
12
 
5
13
  ### Added
@@ -299,6 +299,54 @@ module Legion
299
299
  end
300
300
  end
301
301
 
302
+ def handle_sort(input)
303
+ arg = input.split(nil, 2)[1]
304
+ if arg == 'role'
305
+ sort_by_role
306
+ else
307
+ sort_by_length
308
+ end
309
+ :handled
310
+ end
311
+
312
+ def sort_by_length
313
+ msgs = @message_stream.messages
314
+ if msgs.empty?
315
+ @message_stream.add_message(role: :system, content: 'No messages to sort.')
316
+ return
317
+ end
318
+
319
+ sorted = msgs.sort_by { |m| -m[:content].to_s.length }.first(10)
320
+ lines = sorted.map { |m| format_length_line(m) }
321
+ @message_stream.add_message(
322
+ role: :system,
323
+ content: "Messages by length (top #{sorted.size}):\n#{lines.join("\n")}"
324
+ )
325
+ end
326
+
327
+ def format_length_line(msg)
328
+ len = msg[:content].to_s.length
329
+ preview = truncate_text(msg[:content].to_s, 60)
330
+ " [#{msg[:role]}] (#{len} chars) #{preview}"
331
+ end
332
+
333
+ def sort_by_role
334
+ msgs = @message_stream.messages
335
+ if msgs.empty?
336
+ @message_stream.add_message(role: :system, content: 'No messages to sort.')
337
+ return
338
+ end
339
+
340
+ counts = msgs.group_by { |m| m[:role] }
341
+ .transform_values(&:size)
342
+ .sort_by { |_, count| -count }
343
+ lines = counts.map { |role, count| " #{role}: #{count}" }
344
+ @message_stream.add_message(
345
+ role: :system,
346
+ content: "Messages by role:\n#{lines.join("\n")}"
347
+ )
348
+ end
349
+
302
350
  def favorites_file
303
351
  File.expand_path('~/.legionio/favorites.json')
304
352
  end
@@ -4,6 +4,7 @@ module Legion
4
4
  module TTY
5
5
  module Screens
6
6
  class Chat < Base
7
+ # rubocop:disable Metrics/ModuleLength
7
8
  module ModelCommands
8
9
  private
9
10
 
@@ -116,7 +117,24 @@ module Legion
116
117
  @message_stream.add_message(role: :system, content: "Personality set to: #{name} (no active LLM)")
117
118
  end
118
119
  end
120
+
121
+ def handle_retry
122
+ unless @last_user_input
123
+ @message_stream.add_message(role: :system, content: 'Nothing to retry.')
124
+ return :handled
125
+ end
126
+
127
+ msgs = @message_stream.messages
128
+ last_assistant_idx = msgs.rindex { |m| m[:role] == :assistant }
129
+ msgs.delete_at(last_assistant_idx) if last_assistant_idx
130
+
131
+ @status_bar.notify(message: 'Retrying...', level: :info, ttl: 2)
132
+ @message_stream.add_message(role: :assistant, content: '')
133
+ send_to_llm(@last_user_input)
134
+ :handled
135
+ end
119
136
  end
137
+ # rubocop:enable Metrics/ModuleLength
120
138
  end
121
139
  end
122
140
  end
@@ -156,6 +156,29 @@ module Legion
156
156
  rescue StandardError
157
157
  nil
158
158
  end
159
+
160
+ def handle_merge(input)
161
+ name = input.split(nil, 2)[1]
162
+ unless name
163
+ @message_stream.add_message(role: :system, content: 'Usage: /merge <session-name>')
164
+ return :handled
165
+ end
166
+
167
+ data = @session_store.load(name)
168
+ unless data
169
+ @message_stream.add_message(role: :system, content: 'Session not found.')
170
+ return :handled
171
+ end
172
+
173
+ imported = data[:messages]
174
+ @message_stream.messages.concat(imported)
175
+ @status_bar.update(message_count: @message_stream.messages.size)
176
+ @message_stream.add_message(
177
+ role: :system,
178
+ content: "Merged #{imported.size} messages from '#{name}'."
179
+ )
180
+ :handled
181
+ end
159
182
  end
160
183
  # rubocop:enable Metrics/ModuleLength
161
184
  end
@@ -303,6 +303,16 @@ module Legion
303
303
  )
304
304
  :handled
305
305
  end
306
+
307
+ def handle_focus
308
+ @focus_mode = !@focus_mode
309
+ if @focus_mode
310
+ @status_bar.notify(message: 'Focus mode ON', level: :info, ttl: 2)
311
+ else
312
+ @status_bar.notify(message: 'Focus mode OFF', level: :info, ttl: 2)
313
+ end
314
+ :handled
315
+ end
306
316
  end
307
317
  # rubocop:enable Metrics/ModuleLength
308
318
  end
@@ -30,7 +30,8 @@ module Legion
30
30
  /theme /search /grep /stats /personality /undo /history /pin /pins /rename
31
31
  /context /alias /snippet /debug /uptime /time /bookmark /welcome /tips
32
32
  /wc /import /mute /autosave /react /macro /tag /tags /repeat /count
33
- /template /fav /favs /log /version].freeze
33
+ /template /fav /favs /log /version
34
+ /focus /retry /merge /sort].freeze
34
35
 
35
36
  PERSONALITIES = {
36
37
  'default' => 'You are Legion, an async cognition engine and AI assistant. Be helpful and concise.',
@@ -68,6 +69,8 @@ module Legion
68
69
  @recording_macro = nil
69
70
  @macro_buffer = []
70
71
  @last_command = nil
72
+ @focus_mode = false
73
+ @last_user_input = nil
71
74
  end
72
75
 
73
76
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
@@ -131,6 +134,7 @@ module Legion
131
134
  end
132
135
 
133
136
  def handle_user_message(input)
137
+ @last_user_input = input
134
138
  @message_stream.add_message(role: :user, content: input)
135
139
  if @plan_mode
136
140
  @message_stream.add_message(role: :system, content: '(bookmarked)')
@@ -160,16 +164,9 @@ module Legion
160
164
  end
161
165
 
162
166
  def render(width, height)
163
- bar_line = @status_bar.render(width: width)
164
- divider = Theme.c(:muted, '-' * width)
165
- dbg = debug_segment
166
- extra_rows = dbg ? 1 : 0
167
- stream_height = [height - 2 - extra_rows, 1].max
168
- stream_lines = @message_stream.render(width: width, height: stream_height)
169
- @status_bar.update(scroll: @message_stream.scroll_position)
170
- lines = stream_lines + [divider, bar_line]
171
- lines << dbg if dbg
172
- lines
167
+ return render_focus(width, height) if @focus_mode
168
+
169
+ render_normal(width, height)
173
170
  end
174
171
 
175
172
  def handle_input(key)
@@ -187,6 +184,25 @@ module Legion
187
184
 
188
185
  private
189
186
 
187
+ def render_focus(width, height)
188
+ stream_lines = @message_stream.render(width: width, height: [height, 1].max)
189
+ @status_bar.update(scroll: @message_stream.scroll_position)
190
+ stream_lines
191
+ end
192
+
193
+ def render_normal(width, height)
194
+ bar_line = @status_bar.render(width: width)
195
+ divider = Theme.c(:muted, '-' * width)
196
+ dbg = debug_segment
197
+ extra_rows = dbg ? 1 : 0
198
+ stream_height = [height - 2 - extra_rows, 1].max
199
+ stream_lines = @message_stream.render(width: width, height: stream_height)
200
+ @status_bar.update(scroll: @message_stream.scroll_position)
201
+ lines = stream_lines + [divider, bar_line]
202
+ lines << dbg if dbg
203
+ lines
204
+ end
205
+
190
206
  def record_macro_step(input, cmd, result)
191
207
  return unless @recording_macro
192
208
  return if cmd == '/macro'
@@ -385,6 +401,10 @@ module Legion
385
401
  when '/favs' then handle_favs
386
402
  when '/log' then handle_log(input)
387
403
  when '/version' then handle_version
404
+ when '/focus' then handle_focus
405
+ when '/retry' then handle_retry
406
+ when '/merge' then handle_merge(input)
407
+ when '/sort' then handle_sort(input)
388
408
  else :handled
389
409
  end
390
410
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.4.17'
5
+ VERSION = '0.4.18'
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.17
4
+ version: 0.4.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity