legion-tty 0.4.5 → 0.4.6

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: e82df7132986b553218c083f5b414f549a4aa72b1aa7cdf3a62bec7e944ff43d
4
- data.tar.gz: d05fe8cc4cf17d78301d2a321fee9c9ebd1d9030973322973bd59b404f502f21
3
+ metadata.gz: 750c41521ce23860a6e8613b4e84471e4c2813657ec68f23b24fcf0aa7015ecd
4
+ data.tar.gz: b203df2393abcc0afa085b7ec30c73fbbe3815d2f9886d1b3744fdbbf1fddc62
5
5
  SHA512:
6
- metadata.gz: 357be1595f03767084c835e496c2609714bbe558ee486ec111d7d67ae6c43f44fbc09fb81518f64e0828bc3b582a658ed1c728b07bae5000a7b3107bd190d99a
7
- data.tar.gz: 6ae28cd4f18f1a1e10f20ccfac53bface69bf65d7d5221b33f6f33b9ca3315dc2913da69abfa4758bf18c2caa84ef36e3f678ac5aea06ead5da68494f94f61ec
6
+ metadata.gz: 06d4eab7afdfbdb8933c3166c3b5dc3cd028c7462f29d4601364ba759ae9b648bbdd1a7e4f304587dcc1148af166840815bba87d5c81044cd622355964cb9cc1
7
+ data.tar.gz: e331268056bf882144790fe0c88f95454050a6b16a017b033f00e29785f71a027107784fc8a4e25906a3d3cd733c0d20454577423d2e5a038b21b6d9fb490c29
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.6] - 2026-03-19
4
+
5
+ ### Added
6
+ - `/stats` command: show conversation statistics (message counts, characters, token summary)
7
+ - `/personality <style>` command: switch between default/concise/detailed/friendly/technical personas
8
+ - Notification component: transient message display with TTL expiry and level-based icons/colors
9
+
3
10
  ## [0.4.5] - 2026-03-19
4
11
 
5
12
  ### Added
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../theme'
4
+
5
+ module Legion
6
+ module TTY
7
+ module Components
8
+ class Notification
9
+ LEVELS = %i[info success warning error].freeze
10
+ ICONS = { info: 'i', success: '+', warning: '!', error: 'x' }.freeze
11
+ COLORS = { info: :info, success: :success, warning: :warning, error: :error }.freeze
12
+
13
+ attr_reader :message, :level, :created_at
14
+
15
+ def initialize(message:, level: :info, ttl: 5)
16
+ @message = message
17
+ @level = LEVELS.include?(level) ? level : :info
18
+ @ttl = ttl
19
+ @created_at = Time.now
20
+ end
21
+
22
+ def expired?
23
+ Time.now - @created_at > @ttl
24
+ end
25
+
26
+ def render(width: 80)
27
+ icon = Theme.c(COLORS[@level], ICONS[@level])
28
+ text = Theme.c(COLORS[@level], @message)
29
+ line = "#{icon} #{text}"
30
+ plain_len = strip_ansi(line).length
31
+ line + (' ' * [width - plain_len, 0].max)
32
+ end
33
+
34
+ private
35
+
36
+ def strip_ansi(str)
37
+ str.gsub(/\e\[[0-9;]*m/, '')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -14,7 +14,15 @@ module Legion
14
14
  class Chat < Base
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
- /theme /search].freeze
17
+ /theme /search /stats /personality].freeze
18
+
19
+ PERSONALITIES = {
20
+ 'default' => 'You are Legion, an async cognition engine and AI assistant. Be helpful and concise.',
21
+ 'concise' => 'You are Legion. Respond in as few words as possible. No filler.',
22
+ 'detailed' => 'You are Legion. Provide thorough, detailed explanations with examples when helpful.',
23
+ 'friendly' => 'You are Legion, a friendly AI companion. Use a warm, conversational tone.',
24
+ 'technical' => 'You are Legion, a senior engineer. Use precise technical language. Include code examples.'
25
+ }.freeze
18
26
 
19
27
  attr_reader :message_stream, :status_bar
20
28
 
@@ -281,6 +289,8 @@ module Legion
281
289
  when '/config' then handle_config_screen
282
290
  when '/theme' then handle_theme(input)
283
291
  when '/search' then handle_search(input)
292
+ when '/stats' then handle_stats
293
+ when '/personality' then handle_personality(input)
284
294
  else :handled
285
295
  end
286
296
  end
@@ -296,7 +306,9 @@ module Legion
296
306
  "/search <text> -- search message history\n " \
297
307
  "/compact [n] -- keep last n message pairs (default 5)\n " \
298
308
  "/copy -- copy last assistant message to clipboard\n " \
299
- "/diff -- show new messages since session was loaded\n\n" \
309
+ "/diff -- show new messages since session was loaded\n " \
310
+ "/stats -- show conversation statistics\n " \
311
+ "/personality [name] -- switch assistant personality\n\n" \
300
312
  'Hotkeys: Ctrl+D=dashboard Ctrl+K=palette Ctrl+S=sessions Esc=back'
301
313
  )
302
314
  :handled
@@ -619,6 +631,64 @@ module Legion
619
631
  :handled
620
632
  end
621
633
 
634
+ def handle_stats
635
+ @message_stream.add_message(role: :system, content: build_stats_lines.join("\n"))
636
+ :handled
637
+ end
638
+
639
+ def build_stats_lines
640
+ msgs = @message_stream.messages
641
+ counts = count_by_role(msgs)
642
+ total_chars = msgs.sum { |m| m[:content].to_s.length }
643
+ lines = stats_header_lines(msgs, counts, total_chars)
644
+ lines << " Tool calls: #{counts[:tool]}" if counts[:tool].positive?
645
+ lines
646
+ end
647
+
648
+ def count_by_role(msgs)
649
+ %i[user assistant system tool].to_h { |role| [role, msgs.count { |m| m[:role] == role }] }
650
+ end
651
+
652
+ def stats_header_lines(msgs, counts, total_chars)
653
+ [
654
+ "Messages: #{msgs.size} total",
655
+ " User: #{counts[:user]}, Assistant: #{counts[:assistant]}, System: #{counts[:system]}",
656
+ "Characters: #{format_stat_number(total_chars)}",
657
+ "Session: #{@session_name}",
658
+ "Tokens: #{@token_tracker.summary}"
659
+ ]
660
+ end
661
+
662
+ def format_stat_number(num)
663
+ num.to_s.chars.reverse.each_slice(3).map(&:join).join(',').reverse
664
+ end
665
+
666
+ def handle_personality(input)
667
+ name = input.split(nil, 2)[1]
668
+ if name && PERSONALITIES.key?(name)
669
+ apply_personality(name)
670
+ elsif name
671
+ available = PERSONALITIES.keys.join(', ')
672
+ @message_stream.add_message(role: :system,
673
+ content: "Unknown personality '#{name}'. Available: #{available}")
674
+ else
675
+ current = @personality || 'default'
676
+ available = PERSONALITIES.keys.join(', ')
677
+ @message_stream.add_message(role: :system, content: "Current: #{current}\nAvailable: #{available}")
678
+ end
679
+ :handled
680
+ end
681
+
682
+ def apply_personality(name)
683
+ @personality = name
684
+ if @llm_chat.respond_to?(:with_instructions)
685
+ @llm_chat.with_instructions(PERSONALITIES[name])
686
+ @message_stream.add_message(role: :system, content: "Personality switched to: #{name}")
687
+ else
688
+ @message_stream.add_message(role: :system, content: "Personality set to: #{name} (no active LLM)")
689
+ end
690
+ end
691
+
622
692
  # rubocop:disable Metrics/AbcSize
623
693
  def handle_compact(input)
624
694
  keep = (input.split(nil, 2)[1] || '5').to_i.clamp(1, 50)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.4.5'
5
+ VERSION = '0.4.6'
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.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -207,6 +207,7 @@ files:
207
207
  - lib/legion/tty/components/markdown_view.rb
208
208
  - lib/legion/tty/components/message_stream.rb
209
209
  - lib/legion/tty/components/model_picker.rb
210
+ - lib/legion/tty/components/notification.rb
210
211
  - lib/legion/tty/components/progress_panel.rb
211
212
  - lib/legion/tty/components/session_picker.rb
212
213
  - lib/legion/tty/components/status_bar.rb