legion-tty 0.4.18 → 0.4.19
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/README.md +25 -6
- data/lib/legion/tty/screens/chat/custom_commands.rb +36 -0
- data/lib/legion/tty/screens/chat/session_commands.rb +54 -0
- data/lib/legion/tty/screens/chat/ui_commands.rb +72 -0
- data/lib/legion/tty/screens/chat.rb +6 -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: 34931409185817b874f6234bb8e81b3fb9144764b53273b990e6c64168f0f087
|
|
4
|
+
data.tar.gz: 84e0e03f8dffb27b927792c5d53d49843206fc9a0b88a859b5f6cab190df29e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2005ab12dbb4159e9ec13118de48de9a3dfc3b555f3bceb3ccde24cf4d2466836d33d157eb2558b3303f5e61b597e3894bdb910fe9d043cc2057f6b53647392
|
|
7
|
+
data.tar.gz: b9db6191d5b9425f1fa2eb16b222bf96937d702439432199a948a56b44a14a2b6826e14f5f49ca570885b3f3dd91dd3e56c5de378a337d8b719e887f62134823
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.19] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `/chain` command: send a sequence of pipe-separated prompts to the LLM sequentially
|
|
7
|
+
- `/info` command: comprehensive session info (modes, counts, aliases, snippets, macros, provider)
|
|
8
|
+
- `/scroll [top|bottom|N]` command: navigate to specific scroll position in message stream
|
|
9
|
+
- `/summary` command: generate a local conversation summary (topics, lengths, duration)
|
|
10
|
+
|
|
3
11
|
## [0.4.18] - 2026-03-19
|
|
4
12
|
|
|
5
13
|
### Added
|
data/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Rich terminal UI for the LegionIO async cognition engine.
|
|
4
4
|
|
|
5
|
-
**Version**: 0.4.
|
|
5
|
+
**Version**: 0.4.18
|
|
6
6
|
|
|
7
|
-
Think Claude Code meets Codex CLI, but for LegionIO: onboarding wizard with identity detection, streaming AI chat shell with
|
|
7
|
+
Think Claude Code meets Codex CLI, but for LegionIO: onboarding wizard with identity detection, streaming AI chat shell with 60 slash commands, operational dashboard, extensions browser, config editor, and session persistence - all rendered with the [tty-ruby](https://ttytoolkit.org/) gem ecosystem.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- **Onboarding wizard** - First-run setup with Kerberos identity detection, GitHub profile probing, environment scanning, and LLM provider selection
|
|
12
12
|
- **Digital rain intro** - Matrix-style rain using discovered LEX extension names
|
|
13
|
-
- **AI chat shell** - Streaming LLM chat with
|
|
13
|
+
- **AI chat shell** - Streaming LLM chat with 60 slash commands, tab completion, markdown rendering, and tool panels
|
|
14
14
|
- **Operational dashboard** - Service/LLM status, extension inventory, system info, panel navigation (Ctrl+D or `/dashboard`)
|
|
15
15
|
- **Extensions browser** - Browse installed LEX gems by category with detail view and homepage opener ('o' key)
|
|
16
16
|
- **Config viewer/editor** - View and edit `~/.legionio/settings/*.json` with vault:// masking and JSON validation
|
|
@@ -115,6 +115,25 @@ legion chat prompt "explain async cognition"
|
|
|
115
115
|
| `/uptime` | Show session elapsed time |
|
|
116
116
|
| `/bookmark` | Export pinned messages to file |
|
|
117
117
|
| `/time` | Show current date and time |
|
|
118
|
+
| `/autosave [N\|off]` | Toggle periodic auto-save with interval |
|
|
119
|
+
| `/react <emoji>` | Add emoji reaction to a message |
|
|
120
|
+
| `/macro <action>` | Record/stop/play/list/delete command macros |
|
|
121
|
+
| `/tag <label>` | Tag a message with a label |
|
|
122
|
+
| `/tags [label]` | Show tag statistics or filter by tag |
|
|
123
|
+
| `/repeat` | Re-execute the last slash command |
|
|
124
|
+
| `/count <pattern>` | Count messages matching a pattern |
|
|
125
|
+
| `/template [name]` | List or use prompt templates |
|
|
126
|
+
| `/fav [N]` | Favorite a message (persists to disk) |
|
|
127
|
+
| `/favs` | Show all favorited messages |
|
|
128
|
+
| `/log [N]` | View last N lines of boot log |
|
|
129
|
+
| `/version` | Show version and platform info |
|
|
130
|
+
| `/focus` | Toggle minimal UI (hide status bar) |
|
|
131
|
+
| `/retry` | Resend last message to LLM |
|
|
132
|
+
| `/merge <session>` | Merge another session into current |
|
|
133
|
+
| `/sort [length\|role]` | Show messages sorted by length or role |
|
|
134
|
+
| `/import <path>` | Import session from a JSON file |
|
|
135
|
+
| `/mute` | Toggle system message display |
|
|
136
|
+
| `/wc` | Show word count statistics |
|
|
118
137
|
|
|
119
138
|
## Hotkeys
|
|
120
139
|
|
|
@@ -139,7 +158,7 @@ legion-tty
|
|
|
139
158
|
|
|
140
159
|
Screens/
|
|
141
160
|
Onboarding # First-run wizard (rain -> intro -> wizard -> reveal)
|
|
142
|
-
Chat # AI chat REPL with streaming +
|
|
161
|
+
Chat # AI chat REPL with streaming + 60 slash commands
|
|
143
162
|
SessionCommands # save/load/sessions/delete/rename
|
|
144
163
|
ExportCommands # export/bookmark/html/json/markdown
|
|
145
164
|
MessageCommands # compact/copy/diff/search/grep/undo/pin/pins
|
|
@@ -194,8 +213,8 @@ Boot logs go to `~/.legionio/logs/tty-boot.log`.
|
|
|
194
213
|
|
|
195
214
|
```bash
|
|
196
215
|
bundle install
|
|
197
|
-
bundle exec rspec #
|
|
198
|
-
bundle exec rubocop #
|
|
216
|
+
bundle exec rspec # 1143 examples, 0 failures
|
|
217
|
+
bundle exec rubocop # 106 files, 0 offenses
|
|
199
218
|
```
|
|
200
219
|
|
|
201
220
|
## License
|
|
@@ -280,6 +280,42 @@ module Legion
|
|
|
280
280
|
@message_stream.add_message(role: :system, content: "Snippet '#{name}' not found.")
|
|
281
281
|
end
|
|
282
282
|
end
|
|
283
|
+
|
|
284
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
285
|
+
def handle_chain(input)
|
|
286
|
+
args = input.split(nil, 2)[1]
|
|
287
|
+
unless args
|
|
288
|
+
@message_stream.add_message(
|
|
289
|
+
role: :system,
|
|
290
|
+
content: 'Usage: /chain prompt1 | prompt2 | prompt3'
|
|
291
|
+
)
|
|
292
|
+
return :handled
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
unless @llm_chat || daemon_available?
|
|
296
|
+
@message_stream.add_message(role: :system, content: 'LLM not configured. Cannot run chain.')
|
|
297
|
+
return :handled
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
prompts = args.split('|').map(&:strip).reject(&:empty?)
|
|
301
|
+
if prompts.empty?
|
|
302
|
+
@message_stream.add_message(role: :system, content: 'Usage: /chain prompt1 | prompt2 | prompt3')
|
|
303
|
+
return :handled
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
prompts.each do |prompt|
|
|
307
|
+
@message_stream.add_message(role: :user, content: prompt)
|
|
308
|
+
@message_stream.add_message(role: :assistant, content: '')
|
|
309
|
+
send_to_llm(prompt)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
@message_stream.add_message(
|
|
313
|
+
role: :system,
|
|
314
|
+
content: "Chain complete: #{prompts.size} prompt#{'s' unless prompts.size == 1} sent."
|
|
315
|
+
)
|
|
316
|
+
:handled
|
|
317
|
+
end
|
|
318
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
283
319
|
end
|
|
284
320
|
# rubocop:enable Metrics/ModuleLength
|
|
285
321
|
end
|
|
@@ -157,6 +157,60 @@ module Legion
|
|
|
157
157
|
nil
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
161
|
+
def handle_info
|
|
162
|
+
cfg = safe_config
|
|
163
|
+
elapsed = Time.now - @session_start
|
|
164
|
+
hours = (elapsed / 3600).to_i
|
|
165
|
+
minutes = ((elapsed % 3600) / 60).to_i
|
|
166
|
+
seconds = (elapsed % 60).to_i
|
|
167
|
+
uptime_str = "#{hours}h #{minutes}m #{seconds}s"
|
|
168
|
+
|
|
169
|
+
msgs = @message_stream.messages
|
|
170
|
+
counts = %i[user assistant system tool].to_h { |r| [r, msgs.count { |m| m[:role] == r }] }
|
|
171
|
+
total_chars = msgs.sum { |m| m[:content].to_s.length }
|
|
172
|
+
avg_len = (total_chars.to_f / [msgs.size, 1].max).round
|
|
173
|
+
|
|
174
|
+
model_info = if @llm_chat.respond_to?(:model)
|
|
175
|
+
@llm_chat.model.to_s
|
|
176
|
+
else
|
|
177
|
+
cfg[:provider] || 'none'
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
tagged_count = msgs.count { |m| m[:tags]&.any? }
|
|
181
|
+
fav_count = msgs.count { |m| m[:favorited] }
|
|
182
|
+
|
|
183
|
+
lines = [
|
|
184
|
+
"Session: #{@session_name}",
|
|
185
|
+
"Started: #{@session_start.strftime('%Y-%m-%d %H:%M:%S')}",
|
|
186
|
+
"Uptime: #{uptime_str}",
|
|
187
|
+
'',
|
|
188
|
+
"Messages: #{msgs.size} total",
|
|
189
|
+
" User: #{counts[:user]}, Assistant: #{counts[:assistant]}, System: #{counts[:system]}",
|
|
190
|
+
" Tool: #{counts[:tool]}",
|
|
191
|
+
'',
|
|
192
|
+
"Total characters: #{total_chars}",
|
|
193
|
+
"Avg message length: #{avg_len} chars",
|
|
194
|
+
'',
|
|
195
|
+
"Pinned: #{@pinned_messages.size}",
|
|
196
|
+
"Tagged: #{tagged_count}",
|
|
197
|
+
"Favorited: #{fav_count}",
|
|
198
|
+
"Aliases: #{@aliases.size}",
|
|
199
|
+
"Snippets: #{@snippets.size}",
|
|
200
|
+
"Macros: #{@macros.size}",
|
|
201
|
+
'',
|
|
202
|
+
"Autosave: #{@autosave_enabled ? "ON (every #{@autosave_interval}s)" : 'OFF'}",
|
|
203
|
+
"Focus mode: #{@focus_mode ? 'on' : 'off'}",
|
|
204
|
+
"Muted system: #{@muted_system ? 'on' : 'off'}",
|
|
205
|
+
"Plan mode: #{@plan_mode ? 'on' : 'off'}",
|
|
206
|
+
"Debug mode: #{@debug_mode ? 'on' : 'off'}",
|
|
207
|
+
"LLM: #{model_info}"
|
|
208
|
+
]
|
|
209
|
+
@message_stream.add_message(role: :system, content: lines.join("\n"))
|
|
210
|
+
:handled
|
|
211
|
+
end
|
|
212
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
213
|
+
|
|
160
214
|
def handle_merge(input)
|
|
161
215
|
name = input.split(nil, 2)[1]
|
|
162
216
|
unless name
|
|
@@ -313,6 +313,78 @@ module Legion
|
|
|
313
313
|
end
|
|
314
314
|
:handled
|
|
315
315
|
end
|
|
316
|
+
|
|
317
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
318
|
+
def handle_scroll(input)
|
|
319
|
+
arg = input.split(nil, 2)[1]
|
|
320
|
+
unless arg
|
|
321
|
+
pos = @message_stream.scroll_position
|
|
322
|
+
@message_stream.add_message(
|
|
323
|
+
role: :system,
|
|
324
|
+
content: "Scroll position: offset=#{pos[:current]}, messages=#{pos[:total]}"
|
|
325
|
+
)
|
|
326
|
+
return :handled
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
case arg.strip
|
|
330
|
+
when 'top'
|
|
331
|
+
@message_stream.scroll_up(@message_stream.messages.size * 5)
|
|
332
|
+
@message_stream.add_message(role: :system, content: 'Scrolled to top.')
|
|
333
|
+
when 'bottom'
|
|
334
|
+
@message_stream.scroll_down(@message_stream.scroll_offset)
|
|
335
|
+
@message_stream.add_message(role: :system, content: 'Scrolled to bottom.')
|
|
336
|
+
else
|
|
337
|
+
idx = arg.strip.to_i
|
|
338
|
+
if idx >= 0 && idx < @message_stream.messages.size
|
|
339
|
+
@message_stream.scroll_down(@message_stream.scroll_offset)
|
|
340
|
+
target_offset = [@message_stream.messages.size - idx - 1, 0].max
|
|
341
|
+
@message_stream.scroll_up(target_offset)
|
|
342
|
+
@message_stream.add_message(role: :system, content: "Scrolled to message #{idx}.")
|
|
343
|
+
else
|
|
344
|
+
@message_stream.add_message(
|
|
345
|
+
role: :system,
|
|
346
|
+
content: 'Invalid index. Usage: /scroll top|bottom|<N>'
|
|
347
|
+
)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
:handled
|
|
351
|
+
end
|
|
352
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
353
|
+
|
|
354
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
355
|
+
def handle_summary
|
|
356
|
+
msgs = @message_stream.messages
|
|
357
|
+
elapsed = Time.now - @session_start
|
|
358
|
+
hours = (elapsed / 3600).to_i
|
|
359
|
+
minutes = ((elapsed % 3600) / 60).to_i
|
|
360
|
+
seconds = (elapsed % 60).to_i
|
|
361
|
+
uptime_str = "#{hours}h #{minutes}m #{seconds}s"
|
|
362
|
+
|
|
363
|
+
counts = %i[user assistant system].to_h { |r| [r, msgs.count { |m| m[:role] == r }] }
|
|
364
|
+
most_active = counts.max_by { |_, v| v }&.first || :none
|
|
365
|
+
|
|
366
|
+
user_msgs = msgs.select { |m| m[:role] == :user }
|
|
367
|
+
top_words = user_msgs.flat_map { |m| m[:content].to_s.split.first(1) }
|
|
368
|
+
.tally.sort_by { |_, c| -c }.first(5).map(&:first)
|
|
369
|
+
|
|
370
|
+
longest = msgs.max_by { |m| m[:content].to_s.length }
|
|
371
|
+
longest_preview = longest ? truncate_text(longest[:content].to_s, 60) : 'none'
|
|
372
|
+
|
|
373
|
+
last_user = user_msgs.last
|
|
374
|
+
recent_topic = last_user ? truncate_text(last_user[:content].to_s, 40) : 'none'
|
|
375
|
+
|
|
376
|
+
lines = [
|
|
377
|
+
'Conversation Summary',
|
|
378
|
+
" Messages: #{msgs.size}, Duration: #{uptime_str}",
|
|
379
|
+
" Most active role: #{most_active}",
|
|
380
|
+
" Top starting words: #{top_words.empty? ? 'none' : top_words.join(', ')}",
|
|
381
|
+
" Longest message: #{longest_preview}",
|
|
382
|
+
" Most recent topic: #{recent_topic}"
|
|
383
|
+
]
|
|
384
|
+
@message_stream.add_message(role: :system, content: lines.join("\n"))
|
|
385
|
+
:handled
|
|
386
|
+
end
|
|
387
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
316
388
|
end
|
|
317
389
|
# rubocop:enable Metrics/ModuleLength
|
|
318
390
|
end
|
|
@@ -31,7 +31,8 @@ module Legion
|
|
|
31
31
|
/context /alias /snippet /debug /uptime /time /bookmark /welcome /tips
|
|
32
32
|
/wc /import /mute /autosave /react /macro /tag /tags /repeat /count
|
|
33
33
|
/template /fav /favs /log /version
|
|
34
|
-
/focus /retry /merge /sort
|
|
34
|
+
/focus /retry /merge /sort
|
|
35
|
+
/chain /info /scroll /summary].freeze
|
|
35
36
|
|
|
36
37
|
PERSONALITIES = {
|
|
37
38
|
'default' => 'You are Legion, an async cognition engine and AI assistant. Be helpful and concise.',
|
|
@@ -405,6 +406,10 @@ module Legion
|
|
|
405
406
|
when '/retry' then handle_retry
|
|
406
407
|
when '/merge' then handle_merge(input)
|
|
407
408
|
when '/sort' then handle_sort(input)
|
|
409
|
+
when '/chain' then handle_chain(input)
|
|
410
|
+
when '/info' then handle_info
|
|
411
|
+
when '/scroll' then handle_scroll(input)
|
|
412
|
+
when '/summary' then handle_summary
|
|
408
413
|
else :handled
|
|
409
414
|
end
|
|
410
415
|
end
|
data/lib/legion/tty/version.rb
CHANGED