legion-tty 0.4.7 → 0.4.8

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: 5b54f067b654097c2ea13e9a36aed12ac6e79f01a7e737494fa57da489d9ada9
4
- data.tar.gz: 767c21b9ed0f1ac37f92db12bbef79432d42de65695d07736c9e1478b48ef96d
3
+ metadata.gz: 49d5dd5b69b7fa3412226bc38e93b0adb0df6be7c3b0317955bf7f9698a0759d
4
+ data.tar.gz: ed73f018bbb85ee31fdf11aba24f5b6f332f28ef85970f9189ba5164402d8690
5
5
  SHA512:
6
- metadata.gz: 6cec6ebd2ee78648a6f8d00e5db9c3004d1b9e1787b914a31135b1ed75137d467c9982363c83dc40f8697b9ef930869e9ab719b066297e5dc61011361a8f5f22
7
- data.tar.gz: 3b1f39208ef1198f3db8620aee08f56d65146f3d761d5356b04992aadd79514aae957e044384878181b390cf738e2258ea08fadf6c4299c3c1aaf1bba0e4b7f4
6
+ metadata.gz: 67a87dfd8538f4c42e5c9897d4460d8457071660e95df3fe019757d3e9e56ef95627d4a5a3576914611257f78fd766190909e4e95f8f4bee26b1c0328745c35f
7
+ data.tar.gz: a6f9bd2210d04ad3d2200a4029e32141eb82b29043dee974cb5b37d5d6b927b1f2b284cac1d3e39c77c6105fa7c08f61a3da8a678e4d77e88de675cfe9cac2ea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.8] - 2026-03-19
4
+
5
+ ### Added
6
+ - `/export html` format: dark-theme HTML export with XSS-safe content escaping
7
+ - Extension homepage opener: press 'o' in extensions browser to open gem homepage in browser
8
+ - Config JSON validation: validates data before saving to prevent corrupt config files
9
+
3
10
  ## [0.4.7] - 2026-03-19
4
11
 
5
12
  ### Added
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Rich terminal UI for the LegionIO async cognition engine.
4
4
 
5
- **Version**: 0.4.6
5
+ **Version**: 0.4.8
6
6
 
7
7
  Think Claude Code meets Codex CLI, but for LegionIO: onboarding wizard with identity detection, streaming AI chat shell, operational dashboard, extensions browser, config editor, and session persistence - all rendered with the [tty-ruby](https://ttytoolkit.org/) gem ecosystem.
8
8
 
@@ -458,13 +458,16 @@ module Legion
458
458
  def handle_export(input)
459
459
  require 'fileutils'
460
460
  format = input.split[1]&.downcase
461
- format = 'md' unless %w[json md].include?(format)
461
+ format = 'md' unless %w[json md html].include?(format)
462
462
  exports_dir = File.expand_path('~/.legionio/exports')
463
463
  FileUtils.mkdir_p(exports_dir)
464
464
  timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
465
- path = File.join(exports_dir, "chat-#{timestamp}.#{format == 'json' ? 'json' : 'md'}")
465
+ ext = { 'json' => 'json', 'md' => 'md', 'html' => 'html' }[format]
466
+ path = File.join(exports_dir, "chat-#{timestamp}.#{ext}")
466
467
  if format == 'json'
467
468
  export_json(path)
469
+ elsif format == 'html'
470
+ export_html(path)
468
471
  else
469
472
  export_markdown(path)
470
473
  end
@@ -815,6 +818,41 @@ module Legion
815
818
  File.write(path, ::JSON.pretty_generate(data))
816
819
  end
817
820
 
821
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
822
+ def export_html(path)
823
+ lines = [
824
+ '<!DOCTYPE html><html><head>',
825
+ '<meta charset="utf-8">',
826
+ '<title>Chat Export</title>',
827
+ '<style>',
828
+ 'body { font-family: system-ui; max-width: 800px; margin: 0 auto; ' \
829
+ 'padding: 20px; background: #1e1b2e; color: #d0cce6; }',
830
+ '.msg { margin: 12px 0; padding: 8px 12px; border-radius: 8px; }',
831
+ '.user { background: #2a2640; }',
832
+ '.assistant { background: #1a1730; }',
833
+ '.system { background: #25223a; color: #8b85a8; font-style: italic; }',
834
+ '.role { font-weight: bold; color: #9d91e6; font-size: 0.85em; }',
835
+ '</style></head><body>',
836
+ '<h1>Chat Export</h1>',
837
+ "<p>Exported: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}</p>"
838
+ ]
839
+ @message_stream.messages.each do |msg|
840
+ role = msg[:role].to_s
841
+ content = escape_html(msg[:content].to_s).gsub("\n", '<br>')
842
+ lines << "<div class='msg #{role}'>"
843
+ lines << "<span class='role'>#{role.capitalize}</span>"
844
+ lines << "<p>#{content}</p>"
845
+ lines << '</div>'
846
+ end
847
+ lines << '</body></html>'
848
+ File.write(path, lines.join("\n"))
849
+ end
850
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
851
+
852
+ def escape_html(text)
853
+ text.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('"', '&quot;')
854
+ end
855
+
818
856
  def build_default_input_bar
819
857
  cfg = safe_config
820
858
  name = cfg[:name] || 'User'
@@ -100,7 +100,7 @@ module Legion
100
100
  @viewing_file = true
101
101
  end
102
102
 
103
- def edit_selected_key # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
103
+ def edit_selected_key # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
104
104
  keys = @file_data.keys
105
105
  return unless keys[@selected_key]
106
106
 
@@ -115,11 +115,21 @@ module Legion
115
115
  return if new_val.nil? || new_val == '********'
116
116
 
117
117
  @file_data[key] = new_val
118
+ return unless validate_config(@file_data)
119
+
118
120
  save_current_file
119
121
  rescue ::TTY::Reader::InputInterrupt, Interrupt
120
122
  nil
121
123
  end
122
124
 
125
+ def validate_config(data)
126
+ ::JSON.generate(data)
127
+ true
128
+ rescue StandardError => e
129
+ @messages = ["Invalid JSON: #{e.message}"]
130
+ false
131
+ end
132
+
123
133
  def save_current_file
124
134
  return unless @files[@selected_file]
125
135
 
@@ -42,10 +42,11 @@ module Legion
42
42
  else
43
43
  list_lines(height - 4)
44
44
  end
45
- lines += ['', Theme.c(:muted, ' Enter=detail q=back')]
45
+ lines += ['', Theme.c(:muted, ' Enter=detail o=open q=back')]
46
46
  pad_lines(lines, height)
47
47
  end
48
48
 
49
+ # rubocop:disable Metrics/MethodLength
49
50
  def handle_input(key)
50
51
  case key
51
52
  when :up
@@ -57,6 +58,9 @@ module Legion
57
58
  when :enter
58
59
  @detail = !@detail
59
60
  :handled
61
+ when 'o'
62
+ open_homepage
63
+ :handled
60
64
  when 'q', :escape
61
65
  if @detail
62
66
  @detail = false
@@ -68,6 +72,7 @@ module Legion
68
72
  :pass
69
73
  end
70
74
  end
75
+ # rubocop:enable Metrics/MethodLength
71
76
 
72
77
  private
73
78
 
@@ -127,6 +132,29 @@ module Legion
127
132
  ]
128
133
  end
129
134
 
135
+ def open_homepage
136
+ entry = current_gem
137
+ return unless entry && entry[:homepage]
138
+
139
+ system_open(entry[:homepage])
140
+ rescue StandardError
141
+ nil
142
+ end
143
+
144
+ def system_open(url)
145
+ case RUBY_PLATFORM
146
+ when /darwin/ then system('open', url)
147
+ when /linux/ then system('xdg-open', url)
148
+ when /mingw|mswin/ then system('start', url)
149
+ end
150
+ end
151
+
152
+ def current_gem
153
+ return nil if @gems.empty?
154
+
155
+ @gems[@selected]
156
+ end
157
+
130
158
  def pad_lines(lines, height)
131
159
  lines + Array.new([height - lines.size, 0].max, '')
132
160
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.4.7'
5
+ VERSION = '0.4.8'
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.7
4
+ version: 0.4.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity