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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6faa2820fd40c949a40eb6e336cf75067e3ab400d9631980fc49fda2f0b5dc29
4
- data.tar.gz: 35c674132cb7f41f0c2c8d4150afe10c99f4ac30fed392e78477fb074c383d67
3
+ metadata.gz: 34931409185817b874f6234bb8e81b3fb9144764b53273b990e6c64168f0f087
4
+ data.tar.gz: 84e0e03f8dffb27b927792c5d53d49843206fc9a0b88a859b5f6cab190df29e0
5
5
  SHA512:
6
- metadata.gz: 3766b9312e3fa871e39cfc1b42eac013c82ab680509f10f6f034474fda23381d1bfa4bc67e9e7a95d1557ca546ae3cd6d209818d4fe814111b33cc35b167f46f
7
- data.tar.gz: c0644da99c0c8cd8b0002366e2e9f46edf23d99a157a8e5652165e3112442516da2178e16ca91596afdbef04a57fe379a347bd6f5c6883903bae24f599a294dd
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.12
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 40 slash commands, operational dashboard, extensions browser, config editor, and session persistence - all rendered with the [tty-ruby](https://ttytoolkit.org/) gem ecosystem.
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 40 slash commands, tab completion, markdown rendering, and tool panels
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 + 40 slash commands
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 # 836 examples, 0 failures
198
- bundle exec rubocop # 92 files, 0 offenses
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].freeze
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.4.18'
5
+ VERSION = '0.4.19'
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.18
4
+ version: 0.4.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity