legion-tty 0.4.17 → 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 +16 -0
- data/README.md +25 -6
- data/lib/legion/tty/screens/chat/custom_commands.rb +36 -0
- data/lib/legion/tty/screens/chat/message_commands.rb +48 -0
- data/lib/legion/tty/screens/chat/model_commands.rb +18 -0
- data/lib/legion/tty/screens/chat/session_commands.rb +77 -0
- data/lib/legion/tty/screens/chat/ui_commands.rb +82 -0
- data/lib/legion/tty/screens/chat.rb +36 -11
- 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,21 @@
|
|
|
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
|
+
|
|
11
|
+
## [0.4.18] - 2026-03-19
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `/focus` command: toggle minimal UI mode (hides status bar for distraction-free writing)
|
|
15
|
+
- `/retry` command: resend last user message to LLM, replacing previous assistant response
|
|
16
|
+
- `/merge <session>` command: import messages from another saved session into current conversation
|
|
17
|
+
- `/sort [length|role]` command: display messages sorted by character length or grouped by role
|
|
18
|
+
|
|
3
19
|
## [0.4.17] - 2026-03-19
|
|
4
20
|
|
|
5
21
|
### 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
|
|
@@ -299,6 +299,54 @@ module Legion
|
|
|
299
299
|
end
|
|
300
300
|
end
|
|
301
301
|
|
|
302
|
+
def handle_sort(input)
|
|
303
|
+
arg = input.split(nil, 2)[1]
|
|
304
|
+
if arg == 'role'
|
|
305
|
+
sort_by_role
|
|
306
|
+
else
|
|
307
|
+
sort_by_length
|
|
308
|
+
end
|
|
309
|
+
:handled
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def sort_by_length
|
|
313
|
+
msgs = @message_stream.messages
|
|
314
|
+
if msgs.empty?
|
|
315
|
+
@message_stream.add_message(role: :system, content: 'No messages to sort.')
|
|
316
|
+
return
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
sorted = msgs.sort_by { |m| -m[:content].to_s.length }.first(10)
|
|
320
|
+
lines = sorted.map { |m| format_length_line(m) }
|
|
321
|
+
@message_stream.add_message(
|
|
322
|
+
role: :system,
|
|
323
|
+
content: "Messages by length (top #{sorted.size}):\n#{lines.join("\n")}"
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def format_length_line(msg)
|
|
328
|
+
len = msg[:content].to_s.length
|
|
329
|
+
preview = truncate_text(msg[:content].to_s, 60)
|
|
330
|
+
" [#{msg[:role]}] (#{len} chars) #{preview}"
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def sort_by_role
|
|
334
|
+
msgs = @message_stream.messages
|
|
335
|
+
if msgs.empty?
|
|
336
|
+
@message_stream.add_message(role: :system, content: 'No messages to sort.')
|
|
337
|
+
return
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
counts = msgs.group_by { |m| m[:role] }
|
|
341
|
+
.transform_values(&:size)
|
|
342
|
+
.sort_by { |_, count| -count }
|
|
343
|
+
lines = counts.map { |role, count| " #{role}: #{count}" }
|
|
344
|
+
@message_stream.add_message(
|
|
345
|
+
role: :system,
|
|
346
|
+
content: "Messages by role:\n#{lines.join("\n")}"
|
|
347
|
+
)
|
|
348
|
+
end
|
|
349
|
+
|
|
302
350
|
def favorites_file
|
|
303
351
|
File.expand_path('~/.legionio/favorites.json')
|
|
304
352
|
end
|
|
@@ -4,6 +4,7 @@ module Legion
|
|
|
4
4
|
module TTY
|
|
5
5
|
module Screens
|
|
6
6
|
class Chat < Base
|
|
7
|
+
# rubocop:disable Metrics/ModuleLength
|
|
7
8
|
module ModelCommands
|
|
8
9
|
private
|
|
9
10
|
|
|
@@ -116,7 +117,24 @@ module Legion
|
|
|
116
117
|
@message_stream.add_message(role: :system, content: "Personality set to: #{name} (no active LLM)")
|
|
117
118
|
end
|
|
118
119
|
end
|
|
120
|
+
|
|
121
|
+
def handle_retry
|
|
122
|
+
unless @last_user_input
|
|
123
|
+
@message_stream.add_message(role: :system, content: 'Nothing to retry.')
|
|
124
|
+
return :handled
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
msgs = @message_stream.messages
|
|
128
|
+
last_assistant_idx = msgs.rindex { |m| m[:role] == :assistant }
|
|
129
|
+
msgs.delete_at(last_assistant_idx) if last_assistant_idx
|
|
130
|
+
|
|
131
|
+
@status_bar.notify(message: 'Retrying...', level: :info, ttl: 2)
|
|
132
|
+
@message_stream.add_message(role: :assistant, content: '')
|
|
133
|
+
send_to_llm(@last_user_input)
|
|
134
|
+
:handled
|
|
135
|
+
end
|
|
119
136
|
end
|
|
137
|
+
# rubocop:enable Metrics/ModuleLength
|
|
120
138
|
end
|
|
121
139
|
end
|
|
122
140
|
end
|
|
@@ -156,6 +156,83 @@ module Legion
|
|
|
156
156
|
rescue StandardError
|
|
157
157
|
nil
|
|
158
158
|
end
|
|
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
|
+
|
|
214
|
+
def handle_merge(input)
|
|
215
|
+
name = input.split(nil, 2)[1]
|
|
216
|
+
unless name
|
|
217
|
+
@message_stream.add_message(role: :system, content: 'Usage: /merge <session-name>')
|
|
218
|
+
return :handled
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
data = @session_store.load(name)
|
|
222
|
+
unless data
|
|
223
|
+
@message_stream.add_message(role: :system, content: 'Session not found.')
|
|
224
|
+
return :handled
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
imported = data[:messages]
|
|
228
|
+
@message_stream.messages.concat(imported)
|
|
229
|
+
@status_bar.update(message_count: @message_stream.messages.size)
|
|
230
|
+
@message_stream.add_message(
|
|
231
|
+
role: :system,
|
|
232
|
+
content: "Merged #{imported.size} messages from '#{name}'."
|
|
233
|
+
)
|
|
234
|
+
:handled
|
|
235
|
+
end
|
|
159
236
|
end
|
|
160
237
|
# rubocop:enable Metrics/ModuleLength
|
|
161
238
|
end
|
|
@@ -303,6 +303,88 @@ module Legion
|
|
|
303
303
|
)
|
|
304
304
|
:handled
|
|
305
305
|
end
|
|
306
|
+
|
|
307
|
+
def handle_focus
|
|
308
|
+
@focus_mode = !@focus_mode
|
|
309
|
+
if @focus_mode
|
|
310
|
+
@status_bar.notify(message: 'Focus mode ON', level: :info, ttl: 2)
|
|
311
|
+
else
|
|
312
|
+
@status_bar.notify(message: 'Focus mode OFF', level: :info, ttl: 2)
|
|
313
|
+
end
|
|
314
|
+
:handled
|
|
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
|
|
306
388
|
end
|
|
307
389
|
# rubocop:enable Metrics/ModuleLength
|
|
308
390
|
end
|
|
@@ -30,7 +30,9 @@ module Legion
|
|
|
30
30
|
/theme /search /grep /stats /personality /undo /history /pin /pins /rename
|
|
31
31
|
/context /alias /snippet /debug /uptime /time /bookmark /welcome /tips
|
|
32
32
|
/wc /import /mute /autosave /react /macro /tag /tags /repeat /count
|
|
33
|
-
/template /fav /favs /log /version
|
|
33
|
+
/template /fav /favs /log /version
|
|
34
|
+
/focus /retry /merge /sort
|
|
35
|
+
/chain /info /scroll /summary].freeze
|
|
34
36
|
|
|
35
37
|
PERSONALITIES = {
|
|
36
38
|
'default' => 'You are Legion, an async cognition engine and AI assistant. Be helpful and concise.',
|
|
@@ -68,6 +70,8 @@ module Legion
|
|
|
68
70
|
@recording_macro = nil
|
|
69
71
|
@macro_buffer = []
|
|
70
72
|
@last_command = nil
|
|
73
|
+
@focus_mode = false
|
|
74
|
+
@last_user_input = nil
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
@@ -131,6 +135,7 @@ module Legion
|
|
|
131
135
|
end
|
|
132
136
|
|
|
133
137
|
def handle_user_message(input)
|
|
138
|
+
@last_user_input = input
|
|
134
139
|
@message_stream.add_message(role: :user, content: input)
|
|
135
140
|
if @plan_mode
|
|
136
141
|
@message_stream.add_message(role: :system, content: '(bookmarked)')
|
|
@@ -160,16 +165,9 @@ module Legion
|
|
|
160
165
|
end
|
|
161
166
|
|
|
162
167
|
def render(width, height)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
extra_rows = dbg ? 1 : 0
|
|
167
|
-
stream_height = [height - 2 - extra_rows, 1].max
|
|
168
|
-
stream_lines = @message_stream.render(width: width, height: stream_height)
|
|
169
|
-
@status_bar.update(scroll: @message_stream.scroll_position)
|
|
170
|
-
lines = stream_lines + [divider, bar_line]
|
|
171
|
-
lines << dbg if dbg
|
|
172
|
-
lines
|
|
168
|
+
return render_focus(width, height) if @focus_mode
|
|
169
|
+
|
|
170
|
+
render_normal(width, height)
|
|
173
171
|
end
|
|
174
172
|
|
|
175
173
|
def handle_input(key)
|
|
@@ -187,6 +185,25 @@ module Legion
|
|
|
187
185
|
|
|
188
186
|
private
|
|
189
187
|
|
|
188
|
+
def render_focus(width, height)
|
|
189
|
+
stream_lines = @message_stream.render(width: width, height: [height, 1].max)
|
|
190
|
+
@status_bar.update(scroll: @message_stream.scroll_position)
|
|
191
|
+
stream_lines
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def render_normal(width, height)
|
|
195
|
+
bar_line = @status_bar.render(width: width)
|
|
196
|
+
divider = Theme.c(:muted, '-' * width)
|
|
197
|
+
dbg = debug_segment
|
|
198
|
+
extra_rows = dbg ? 1 : 0
|
|
199
|
+
stream_height = [height - 2 - extra_rows, 1].max
|
|
200
|
+
stream_lines = @message_stream.render(width: width, height: stream_height)
|
|
201
|
+
@status_bar.update(scroll: @message_stream.scroll_position)
|
|
202
|
+
lines = stream_lines + [divider, bar_line]
|
|
203
|
+
lines << dbg if dbg
|
|
204
|
+
lines
|
|
205
|
+
end
|
|
206
|
+
|
|
190
207
|
def record_macro_step(input, cmd, result)
|
|
191
208
|
return unless @recording_macro
|
|
192
209
|
return if cmd == '/macro'
|
|
@@ -385,6 +402,14 @@ module Legion
|
|
|
385
402
|
when '/favs' then handle_favs
|
|
386
403
|
when '/log' then handle_log(input)
|
|
387
404
|
when '/version' then handle_version
|
|
405
|
+
when '/focus' then handle_focus
|
|
406
|
+
when '/retry' then handle_retry
|
|
407
|
+
when '/merge' then handle_merge(input)
|
|
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
|
|
388
413
|
else :handled
|
|
389
414
|
end
|
|
390
415
|
end
|
data/lib/legion/tty/version.rb
CHANGED