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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/lib/legion/tty/screens/chat.rb +40 -2
- data/lib/legion/tty/screens/config.rb +11 -1
- data/lib/legion/tty/screens/extensions.rb +29 -1
- 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: 49d5dd5b69b7fa3412226bc38e93b0adb0df6be7c3b0317955bf7f9698a0759d
|
|
4
|
+
data.tar.gz: ed73f018bbb85ee31fdf11aba24f5b6f332f28ef85970f9189ba5164402d8690
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
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('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"')
|
|
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
|
data/lib/legion/tty/version.rb
CHANGED