legion-tty 0.4.15 → 0.4.16
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 +8 -0
- data/lib/legion/tty/screens/chat/message_commands.rb +22 -0
- data/lib/legion/tty/screens/chat.rb +14 -1
- data/lib/legion/tty/screens/config.rb +21 -1
- data/lib/legion/tty/screens/extensions.rb +54 -19
- 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: 70299176c70666c5c4a4e7a1e6ac8ae1a03050525e299b7fcc4cf6a4cb8146c4
|
|
4
|
+
data.tar.gz: c3cf40f5acfa8e14da112380c93931340985d937aafd6667c638bdb4b3ed0913
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8df88d8c600985633a63d34c18c3d1366dd9868d4109c2240c7522fa905511d04d865a8a259af71009840571ec627dae22f0def660043ecbc63701a4a0564dcb
|
|
7
|
+
data.tar.gz: 7b607c3304efb10d94866d23eabd122b1aee84e0cc6cb8ecd7329129ad6b18277867b4e74825788d0bbc171563ea6a60c73d75fd2bdeeb7be1e9227ec26515a0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.16] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Extensions screen category filter: 'f' cycles through Core/AI/Service/Agentic/Other, 'c' clears
|
|
7
|
+
- Config screen backup: 'b' creates .bak copy, auto-backup before edits
|
|
8
|
+
- `/repeat` command: re-execute the last slash command
|
|
9
|
+
- `/count <pattern>` command: count messages matching a pattern with per-role breakdown
|
|
10
|
+
|
|
3
11
|
## [0.4.15] - 2026-03-19
|
|
4
12
|
|
|
5
13
|
### Added
|
|
@@ -226,6 +226,28 @@ module Legion
|
|
|
226
226
|
:handled
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
+
def handle_count(input)
|
|
230
|
+
query = input.split(nil, 2)[1]
|
|
231
|
+
unless query
|
|
232
|
+
@message_stream.add_message(role: :system, content: 'Usage: /count <pattern>')
|
|
233
|
+
return :handled
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
results = search_messages(query)
|
|
237
|
+
if results.empty?
|
|
238
|
+
@message_stream.add_message(role: :system, content: "0 messages matching '#{query}'.")
|
|
239
|
+
else
|
|
240
|
+
breakdown = results.group_by { |m| m[:role] }
|
|
241
|
+
.map { |role, msgs| "#{role}: #{msgs.size}" }
|
|
242
|
+
.join(', ')
|
|
243
|
+
@message_stream.add_message(
|
|
244
|
+
role: :system,
|
|
245
|
+
content: "#{results.size} message(s) matching '#{query}' (#{breakdown})."
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
:handled
|
|
249
|
+
end
|
|
250
|
+
|
|
229
251
|
def search_messages(query)
|
|
230
252
|
pattern = query.downcase
|
|
231
253
|
@message_stream.messages.select do |msg|
|
|
@@ -29,7 +29,7 @@ module Legion
|
|
|
29
29
|
/hotkeys /save /load /sessions /system /delete /plan /palette /extensions /config
|
|
30
30
|
/theme /search /grep /stats /personality /undo /history /pin /pins /rename
|
|
31
31
|
/context /alias /snippet /debug /uptime /time /bookmark /welcome /tips
|
|
32
|
-
/wc /import /mute /autosave /react /macro /tag /tags].freeze
|
|
32
|
+
/wc /import /mute /autosave /react /macro /tag /tags /repeat /count].freeze
|
|
33
33
|
|
|
34
34
|
PERSONALITIES = {
|
|
35
35
|
'default' => 'You are Legion, an async cognition engine and AI assistant. Be helpful and concise.',
|
|
@@ -66,6 +66,7 @@ module Legion
|
|
|
66
66
|
@last_autosave = Time.now
|
|
67
67
|
@recording_macro = nil
|
|
68
68
|
@macro_buffer = []
|
|
69
|
+
@last_command = nil
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
@@ -123,6 +124,7 @@ module Legion
|
|
|
123
124
|
end
|
|
124
125
|
|
|
125
126
|
result = dispatch_slash(cmd, input)
|
|
127
|
+
@last_command = input if cmd != '/repeat'
|
|
126
128
|
record_macro_step(input, cmd, result)
|
|
127
129
|
result
|
|
128
130
|
end
|
|
@@ -375,11 +377,22 @@ module Legion
|
|
|
375
377
|
when '/macro' then handle_macro(input)
|
|
376
378
|
when '/tag' then handle_tag(input)
|
|
377
379
|
when '/tags' then handle_tags(input)
|
|
380
|
+
when '/repeat' then handle_repeat
|
|
381
|
+
when '/count' then handle_count(input)
|
|
378
382
|
else :handled
|
|
379
383
|
end
|
|
380
384
|
end
|
|
381
385
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
382
386
|
|
|
387
|
+
def handle_repeat
|
|
388
|
+
unless @last_command
|
|
389
|
+
@message_stream.add_message(role: :system, content: 'No previous command to repeat.')
|
|
390
|
+
return :handled
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
dispatch_slash(@last_command.split.first, @last_command)
|
|
394
|
+
end
|
|
395
|
+
|
|
383
396
|
def handle_cost
|
|
384
397
|
@message_stream.add_message(role: :system, content: @token_tracker.summary)
|
|
385
398
|
:handled
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
3
4
|
require 'json'
|
|
4
5
|
require_relative 'base'
|
|
5
6
|
require_relative '../theme'
|
|
@@ -41,7 +42,8 @@ module Legion
|
|
|
41
42
|
else
|
|
42
43
|
file_list_lines(height - 4)
|
|
43
44
|
end
|
|
44
|
-
|
|
45
|
+
hint = @viewing_file ? ' Enter=edit b=backup q=back' : ' Enter=view e=edit q=back'
|
|
46
|
+
lines += ['', Theme.c(:muted, hint)]
|
|
45
47
|
pad_lines(lines, height)
|
|
46
48
|
end
|
|
47
49
|
|
|
@@ -79,6 +81,9 @@ module Legion
|
|
|
79
81
|
:handled
|
|
80
82
|
when 'e', :enter then edit_selected_key
|
|
81
83
|
:handled
|
|
84
|
+
when 'b'
|
|
85
|
+
backup_current_file
|
|
86
|
+
:handled
|
|
82
87
|
when 'q', :escape
|
|
83
88
|
@viewing_file = false
|
|
84
89
|
@selected_key = 0
|
|
@@ -130,10 +135,25 @@ module Legion
|
|
|
130
135
|
false
|
|
131
136
|
end
|
|
132
137
|
|
|
138
|
+
def backup_config(path)
|
|
139
|
+
return unless File.exist?(path)
|
|
140
|
+
|
|
141
|
+
FileUtils.cp(path, "#{path}.bak")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def backup_current_file
|
|
145
|
+
return unless @files[@selected_file]
|
|
146
|
+
|
|
147
|
+
path = @files[@selected_file][:path]
|
|
148
|
+
backup_config(path)
|
|
149
|
+
@backup_notice = "Backed up to #{File.basename(path)}.bak"
|
|
150
|
+
end
|
|
151
|
+
|
|
133
152
|
def save_current_file
|
|
134
153
|
return unless @files[@selected_file]
|
|
135
154
|
|
|
136
155
|
path = @files[@selected_file][:path]
|
|
156
|
+
backup_config(path)
|
|
137
157
|
File.write(path, ::JSON.pretty_generate(@file_data))
|
|
138
158
|
end
|
|
139
159
|
|
|
@@ -17,12 +17,15 @@ module Legion
|
|
|
17
17
|
SERVICE = %w[lex-http lex-vault lex-github lex-consul lex-kerberos lex-tfe
|
|
18
18
|
lex-redis lex-memcached lex-elasticsearch lex-s3].freeze
|
|
19
19
|
|
|
20
|
+
CATEGORIES = [nil, 'Core', 'AI', 'Service', 'Agentic', 'Other'].freeze
|
|
21
|
+
|
|
20
22
|
def initialize(app, output: $stdout)
|
|
21
23
|
super(app)
|
|
22
24
|
@output = output
|
|
23
25
|
@gems = []
|
|
24
26
|
@selected = 0
|
|
25
27
|
@detail = false
|
|
28
|
+
@filter = nil
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def activate
|
|
@@ -36,45 +39,64 @@ module Legion
|
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
def render(_width, height)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
filter_label = @filter ? Theme.c(:warning, " filter: #{@filter}") : ''
|
|
43
|
+
header = [Theme.c(:accent, ' LEX Extensions'), filter_label].reject(&:empty?)
|
|
44
|
+
lines = header + ['']
|
|
45
|
+
lines += if @detail && current_gems[@selected]
|
|
46
|
+
detail_lines(current_gems[@selected])
|
|
42
47
|
else
|
|
43
48
|
list_lines(height - 4)
|
|
44
49
|
end
|
|
45
|
-
lines += ['', Theme.c(:muted, ' Enter=detail o=open q=back')]
|
|
50
|
+
lines += ['', Theme.c(:muted, ' Enter=detail o=open f=filter c=clear q=back')]
|
|
46
51
|
pad_lines(lines, height)
|
|
47
52
|
end
|
|
48
53
|
|
|
49
|
-
# rubocop:disable Metrics/MethodLength
|
|
50
54
|
def handle_input(key)
|
|
51
55
|
case key
|
|
52
56
|
when :up
|
|
53
57
|
@selected = [(@selected - 1), 0].max
|
|
54
58
|
:handled
|
|
55
59
|
when :down
|
|
56
|
-
|
|
60
|
+
max = [current_gems.size - 1, 0].max
|
|
61
|
+
@selected = [(@selected + 1), max].min
|
|
57
62
|
:handled
|
|
58
63
|
when :enter
|
|
59
64
|
@detail = !@detail
|
|
60
65
|
:handled
|
|
66
|
+
when 'q', :escape
|
|
67
|
+
handle_back_key
|
|
68
|
+
else
|
|
69
|
+
handle_action_key(key)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def handle_back_key
|
|
76
|
+
if @detail
|
|
77
|
+
@detail = false
|
|
78
|
+
:handled
|
|
79
|
+
else
|
|
80
|
+
:pop_screen
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def handle_action_key(key)
|
|
85
|
+
case key
|
|
61
86
|
when 'o'
|
|
62
87
|
open_homepage
|
|
63
88
|
:handled
|
|
64
|
-
when '
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
when 'f'
|
|
90
|
+
cycle_filter
|
|
91
|
+
:handled
|
|
92
|
+
when 'c'
|
|
93
|
+
@filter = nil
|
|
94
|
+
@selected = 0
|
|
95
|
+
:handled
|
|
71
96
|
else
|
|
72
97
|
:pass
|
|
73
98
|
end
|
|
74
99
|
end
|
|
75
|
-
# rubocop:enable Metrics/MethodLength
|
|
76
|
-
|
|
77
|
-
private
|
|
78
100
|
|
|
79
101
|
def build_entry(spec)
|
|
80
102
|
loaded = $LOADED_FEATURES.any? { |f| f.include?(spec.name.tr('-', '/')) }
|
|
@@ -100,7 +122,7 @@ module Legion
|
|
|
100
122
|
|
|
101
123
|
# rubocop:disable Metrics/AbcSize
|
|
102
124
|
def list_lines(max_height)
|
|
103
|
-
grouped =
|
|
125
|
+
grouped = current_gems.group_by { |g| g[:category] }
|
|
104
126
|
lines = []
|
|
105
127
|
idx = 0
|
|
106
128
|
grouped.each do |cat, gems|
|
|
@@ -132,6 +154,18 @@ module Legion
|
|
|
132
154
|
]
|
|
133
155
|
end
|
|
134
156
|
|
|
157
|
+
def cycle_filter
|
|
158
|
+
idx = CATEGORIES.index(@filter) || 0
|
|
159
|
+
@filter = CATEGORIES[(idx + 1) % CATEGORIES.size]
|
|
160
|
+
@selected = 0
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def current_gems
|
|
164
|
+
return @gems unless @filter
|
|
165
|
+
|
|
166
|
+
@gems.select { |g| g[:category] == @filter }
|
|
167
|
+
end
|
|
168
|
+
|
|
135
169
|
def open_homepage
|
|
136
170
|
entry = current_gem
|
|
137
171
|
return unless entry && entry[:homepage]
|
|
@@ -150,9 +184,10 @@ module Legion
|
|
|
150
184
|
end
|
|
151
185
|
|
|
152
186
|
def current_gem
|
|
153
|
-
|
|
187
|
+
gems = current_gems
|
|
188
|
+
return nil if gems.empty?
|
|
154
189
|
|
|
155
|
-
|
|
190
|
+
gems[@selected]
|
|
156
191
|
end
|
|
157
192
|
|
|
158
193
|
def pad_lines(lines, height)
|
data/lib/legion/tty/version.rb
CHANGED