legion-tty 0.4.3 → 0.4.5
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 +16 -0
- data/lib/legion/tty/components/status_bar.rb +5 -1
- data/lib/legion/tty/screens/chat.rb +113 -3
- 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: e82df7132986b553218c083f5b414f549a4aa72b1aa7cdf3a62bec7e944ff43d
|
|
4
|
+
data.tar.gz: d05fe8cc4cf17d78301d2a321fee9c9ebd1d9030973322973bd59b404f502f21
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 357be1595f03767084c835e496c2609714bbe558ee486ec111d7d67ae6c43f44fbc09fb81518f64e0828bc3b582a658ed1c728b07bae5000a7b3107bd190d99a
|
|
7
|
+
data.tar.gz: 6ae28cd4f18f1a1e10f20ccfac53bface69bf65d7d5221b33f6f33b9ca3315dc2913da69abfa4758bf18c2caa84ef36e3f678ac5aea06ead5da68494f94f61ec
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.5] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `/compact [N]` command: remove older messages, keep last N pairs (default 5)
|
|
7
|
+
- `/copy` command: copy last assistant response to clipboard (macOS pbcopy, Linux xclip)
|
|
8
|
+
- `/diff` command: show new messages since last session load
|
|
9
|
+
- Session load tracking: `@loaded_message_count` for diff comparison
|
|
10
|
+
|
|
11
|
+
## [0.4.4] - 2026-03-19
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Animated spinner in status bar thinking indicator (cycles through frames on each render)
|
|
15
|
+
- `/search <text>` command: case-insensitive search across chat message history
|
|
16
|
+
- `/theme <name>` command: switch between purple, green, blue, amber themes at runtime
|
|
17
|
+
- Chat input history: up/down arrow navigation through previous inputs (via TTY::Reader history_cycle)
|
|
18
|
+
|
|
3
19
|
## [0.4.3] - 2026-03-19
|
|
4
20
|
|
|
5
21
|
### Added
|
|
@@ -26,6 +26,8 @@ module Legion
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
SPINNER_FRAMES = %w[| / - \\].freeze
|
|
30
|
+
|
|
29
31
|
private
|
|
30
32
|
|
|
31
33
|
def build_segments
|
|
@@ -52,7 +54,9 @@ module Legion
|
|
|
52
54
|
def thinking_segment
|
|
53
55
|
return nil unless @state[:thinking]
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
@spinner_index = ((@spinner_index || 0) + 1) % SPINNER_FRAMES.size
|
|
58
|
+
frame = SPINNER_FRAMES[@spinner_index]
|
|
59
|
+
Theme.c(:warning, "#{frame} thinking...")
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
def tokens_segment
|
|
@@ -12,8 +12,9 @@ module Legion
|
|
|
12
12
|
module Screens
|
|
13
13
|
# rubocop:disable Metrics/ClassLength
|
|
14
14
|
class Chat < Base
|
|
15
|
-
SLASH_COMMANDS = %w[/help /quit /clear /model /session /cost /export /tools /dashboard
|
|
16
|
-
/sessions /system /delete /plan /palette /extensions /config
|
|
15
|
+
SLASH_COMMANDS = %w[/help /quit /clear /compact /copy /diff /model /session /cost /export /tools /dashboard
|
|
16
|
+
/hotkeys /save /load /sessions /system /delete /plan /palette /extensions /config
|
|
17
|
+
/theme /search].freeze
|
|
17
18
|
|
|
18
19
|
attr_reader :message_stream, :status_bar
|
|
19
20
|
|
|
@@ -259,6 +260,9 @@ module Legion
|
|
|
259
260
|
when '/quit' then :quit
|
|
260
261
|
when '/help' then handle_help
|
|
261
262
|
when '/clear' then handle_clear
|
|
263
|
+
when '/compact' then handle_compact(input)
|
|
264
|
+
when '/copy' then handle_copy(input)
|
|
265
|
+
when '/diff' then handle_diff(input)
|
|
262
266
|
when '/model' then handle_model(input)
|
|
263
267
|
when '/session' then handle_session(input)
|
|
264
268
|
when '/cost' then handle_cost
|
|
@@ -276,6 +280,7 @@ module Legion
|
|
|
276
280
|
when '/extensions' then handle_extensions_screen
|
|
277
281
|
when '/config' then handle_config_screen
|
|
278
282
|
when '/theme' then handle_theme(input)
|
|
283
|
+
when '/search' then handle_search(input)
|
|
279
284
|
else :handled
|
|
280
285
|
end
|
|
281
286
|
end
|
|
@@ -287,7 +292,11 @@ module Legion
|
|
|
287
292
|
content: "Commands:\n /help /quit /clear /model <name> /session <name> /cost\n " \
|
|
288
293
|
"/export [md|json] /tools /dashboard /hotkeys /save /load /sessions\n " \
|
|
289
294
|
"/system <prompt> /delete <session> /plan /palette /extensions /config\n " \
|
|
290
|
-
"/theme [name] -- switch color theme (purple, green, blue, amber)\n
|
|
295
|
+
"/theme [name] -- switch color theme (purple, green, blue, amber)\n " \
|
|
296
|
+
"/search <text> -- search message history\n " \
|
|
297
|
+
"/compact [n] -- keep last n message pairs (default 5)\n " \
|
|
298
|
+
"/copy -- copy last assistant message to clipboard\n " \
|
|
299
|
+
"/diff -- show new messages since session was loaded\n\n" \
|
|
291
300
|
'Hotkeys: Ctrl+D=dashboard Ctrl+K=palette Ctrl+S=sessions Esc=back'
|
|
292
301
|
)
|
|
293
302
|
:handled
|
|
@@ -397,6 +406,7 @@ module Legion
|
|
|
397
406
|
return :handled
|
|
398
407
|
end
|
|
399
408
|
@message_stream.messages.replace(data[:messages])
|
|
409
|
+
@loaded_message_count = @message_stream.messages.size
|
|
400
410
|
@session_name = name
|
|
401
411
|
@status_bar.update(session: name)
|
|
402
412
|
@message_stream.add_message(role: :system,
|
|
@@ -589,6 +599,106 @@ module Legion
|
|
|
589
599
|
:handled
|
|
590
600
|
end
|
|
591
601
|
|
|
602
|
+
def handle_search(input)
|
|
603
|
+
query = input.split(nil, 2)[1]
|
|
604
|
+
unless query
|
|
605
|
+
@message_stream.add_message(role: :system, content: 'Usage: /search <text>')
|
|
606
|
+
return :handled
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
results = search_messages(query)
|
|
610
|
+
if results.empty?
|
|
611
|
+
@message_stream.add_message(role: :system, content: "No messages matching '#{query}'.")
|
|
612
|
+
else
|
|
613
|
+
lines = results.map { |r| " [#{r[:role]}] #{truncate_text(r[:content], 80)}" }
|
|
614
|
+
@message_stream.add_message(
|
|
615
|
+
role: :system,
|
|
616
|
+
content: "Found #{results.size} message(s) matching '#{query}':\n#{lines.join("\n")}"
|
|
617
|
+
)
|
|
618
|
+
end
|
|
619
|
+
:handled
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# rubocop:disable Metrics/AbcSize
|
|
623
|
+
def handle_compact(input)
|
|
624
|
+
keep = (input.split(nil, 2)[1] || '5').to_i.clamp(1, 50)
|
|
625
|
+
msgs = @message_stream.messages
|
|
626
|
+
if msgs.size <= keep * 2
|
|
627
|
+
@message_stream.add_message(role: :system, content: 'Conversation is already compact.')
|
|
628
|
+
return :handled
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
system_msgs = msgs.select { |m| m[:role] == :system }
|
|
632
|
+
recent = msgs.reject { |m| m[:role] == :system }.last(keep * 2)
|
|
633
|
+
removed_count = msgs.size - system_msgs.size - recent.size
|
|
634
|
+
@message_stream.messages.replace(system_msgs + recent)
|
|
635
|
+
@message_stream.add_message(
|
|
636
|
+
role: :system,
|
|
637
|
+
content: "Compacted: removed #{removed_count} older messages, kept #{recent.size} recent."
|
|
638
|
+
)
|
|
639
|
+
:handled
|
|
640
|
+
end
|
|
641
|
+
# rubocop:enable Metrics/AbcSize
|
|
642
|
+
|
|
643
|
+
def handle_copy(_input)
|
|
644
|
+
last_assistant = @message_stream.messages.reverse.find { |m| m[:role] == :assistant }
|
|
645
|
+
unless last_assistant
|
|
646
|
+
@message_stream.add_message(role: :system, content: 'No assistant message to copy.')
|
|
647
|
+
return :handled
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
content = last_assistant[:content].to_s
|
|
651
|
+
copy_to_clipboard(content)
|
|
652
|
+
@message_stream.add_message(
|
|
653
|
+
role: :system,
|
|
654
|
+
content: "Copied #{content.length} characters to clipboard."
|
|
655
|
+
)
|
|
656
|
+
:handled
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def copy_to_clipboard(text)
|
|
660
|
+
IO.popen('pbcopy', 'w') { |io| io.write(text) }
|
|
661
|
+
rescue Errno::ENOENT
|
|
662
|
+
begin
|
|
663
|
+
IO.popen('xclip -selection clipboard', 'w') { |io| io.write(text) }
|
|
664
|
+
rescue Errno::ENOENT
|
|
665
|
+
nil
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
def handle_diff(_input)
|
|
670
|
+
if @loaded_message_count.nil?
|
|
671
|
+
@message_stream.add_message(role: :system, content: 'No session was loaded. Nothing to diff against.')
|
|
672
|
+
return :handled
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
new_count = @message_stream.messages.size - @loaded_message_count
|
|
676
|
+
if new_count <= 0
|
|
677
|
+
@message_stream.add_message(role: :system, content: 'No new messages since session was loaded.')
|
|
678
|
+
else
|
|
679
|
+
new_msgs = @message_stream.messages.last(new_count)
|
|
680
|
+
lines = new_msgs.map { |m| " + [#{m[:role]}] #{truncate_text(m[:content].to_s, 60)}" }
|
|
681
|
+
@message_stream.add_message(
|
|
682
|
+
role: :system,
|
|
683
|
+
content: "#{new_count} new message(s) since load:\n#{lines.join("\n")}"
|
|
684
|
+
)
|
|
685
|
+
end
|
|
686
|
+
:handled
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def search_messages(query)
|
|
690
|
+
pattern = query.downcase
|
|
691
|
+
@message_stream.messages.select do |msg|
|
|
692
|
+
msg[:content].is_a?(::String) && msg[:content].downcase.include?(pattern)
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def truncate_text(text, max_length)
|
|
697
|
+
return text if text.length <= max_length
|
|
698
|
+
|
|
699
|
+
"#{text[0...max_length]}..."
|
|
700
|
+
end
|
|
701
|
+
|
|
592
702
|
def detect_provider
|
|
593
703
|
cfg = safe_config
|
|
594
704
|
provider = cfg[:provider].to_s.downcase
|
data/lib/legion/tty/version.rb
CHANGED