legion-tty 0.4.0 → 0.4.2
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 +22 -0
- data/README.md +50 -27
- data/lib/legion/tty/components/input_bar.rb +38 -2
- data/lib/legion/tty/components/message_stream.rb +29 -0
- data/lib/legion/tty/components/progress_panel.rb +70 -0
- data/lib/legion/tty/screens/chat.rb +77 -13
- data/lib/legion/tty/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3985cb489efcaf4eaf208514bee79ff9650b1f841a00cd76a32468a0734e4198
|
|
4
|
+
data.tar.gz: fa7c612dac3248efe49762a4bd6b869b8348c4388ee569fdbd60c030501db918
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9909b4d373d5b22892611dd3c302d02cb4960bfec8524da602c117b62de5d8fbacb9f39a777b97e5eabe9c379e4918fb79aab313d2f67e5c4129ff1214153d32
|
|
7
|
+
data.tar.gz: 3b8e072e3ad9cb3bd73b9e7e7bb5b6fb6baa86527e4da6e0045564c40851ec77e5e67a89cf7409d1280b59dc1a34831780859d56632e6e4c9a7b6095991a555f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.2] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Multi-provider model switching: `/model <provider>` creates new Legion::LLM.chat instance
|
|
7
|
+
- Model picker integration: `open_model_picker` for interactive provider/model selection
|
|
8
|
+
- ToolPanel wiring in MessageStream: `add_tool_call`, `update_tool_call` methods
|
|
9
|
+
- Tool call rendering in chat messages (`:tool` role with panel display)
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Flaky table_view_spec: added explicit `require 'tty-table'` to prevent test ordering failures
|
|
13
|
+
|
|
14
|
+
## [0.4.1] - 2026-03-19
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Progress panel component wrapping tty-progressbar for long operations
|
|
18
|
+
- Tab completion for slash commands (type `/` + Tab to cycle through matches)
|
|
19
|
+
- InputBar now accepts `completions:` parameter for configurable auto-complete
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- README.md updated to reflect 0.4.x features, hotkeys, architecture
|
|
23
|
+
- CLAUDE.md updated to reflect current version, components, LLM integration notes
|
|
24
|
+
|
|
3
25
|
## [0.4.0] - 2026-03-19
|
|
4
26
|
|
|
5
27
|
### Fixed
|
data/README.md
CHANGED
|
@@ -2,19 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Rich terminal UI for the LegionIO async cognition engine.
|
|
4
4
|
|
|
5
|
-
**Version**: 0.
|
|
5
|
+
**Version**: 0.4.1
|
|
6
6
|
|
|
7
|
-
Think Claude Code meets Codex CLI, but for LegionIO: onboarding wizard with identity detection, streaming AI chat shell, operational dashboard, 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, 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 slash commands,
|
|
13
|
+
- **AI chat shell** - Streaming LLM chat with 19 slash commands, tab completion, markdown rendering, and tool panels
|
|
14
14
|
- **Operational dashboard** - Service status, extension inventory, system info, recent activity (Ctrl+D or `/dashboard`)
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
15
|
+
- **Extensions browser** - Browse installed LEX gems by category (core, agentic, service, AI, other) with detail view
|
|
16
|
+
- **Config viewer/editor** - View and edit `~/.legionio/settings/*.json` with vault:// masking
|
|
17
|
+
- **Command palette** - Fuzzy-search overlay for all commands, screens, and sessions (Ctrl+K or `/palette`)
|
|
18
|
+
- **Model picker** - Switch LLM providers interactively
|
|
19
|
+
- **Session management** - Auto-save on quit, `/save`, `/load`, `/sessions`, session picker (Ctrl+S)
|
|
20
|
+
- **Token tracking** - Per-model pricing for 9 models across 8 providers via `/cost`
|
|
21
|
+
- **Plan mode** - Bookmark messages without sending to LLM (`/plan`)
|
|
22
|
+
- **Hotkey navigation** - Ctrl+D (dashboard), Ctrl+K (palette), Ctrl+S (sessions), Escape (back)
|
|
23
|
+
- **Tab completion** - Type `/` and Tab to auto-complete slash commands
|
|
24
|
+
- **Progress panel** - Visual progress bars for long operations (extension scanning, gem ops)
|
|
18
25
|
- **Second-run flow** - Skips onboarding, re-scans environment, drops into chat
|
|
19
26
|
|
|
20
27
|
## Installation
|
|
@@ -35,6 +42,7 @@ brew install legion
|
|
|
35
42
|
|
|
36
43
|
```bash
|
|
37
44
|
legion-tty
|
|
45
|
+
legion-tty --skip-rain # skip digital rain animation
|
|
38
46
|
```
|
|
39
47
|
|
|
40
48
|
### Via LegionIO CLI
|
|
@@ -56,7 +64,7 @@ legion chat prompt "explain async cognition"
|
|
|
56
64
|
|
|
57
65
|
| Command | Description |
|
|
58
66
|
|---------|-------------|
|
|
59
|
-
| `/help` | Show all commands |
|
|
67
|
+
| `/help` | Show all commands and hotkeys |
|
|
60
68
|
| `/quit` | Exit (auto-saves session) |
|
|
61
69
|
| `/clear` | Clear message history |
|
|
62
70
|
| `/model <name>` | Switch LLM model at runtime |
|
|
@@ -69,6 +77,23 @@ legion chat prompt "explain async cognition"
|
|
|
69
77
|
| `/save [name]` | Save current session |
|
|
70
78
|
| `/load <name>` | Load a saved session |
|
|
71
79
|
| `/sessions` | List all saved sessions |
|
|
80
|
+
| `/system <prompt>` | Set or override system prompt |
|
|
81
|
+
| `/delete <session>` | Delete a saved session |
|
|
82
|
+
| `/plan` | Toggle read-only bookmark mode |
|
|
83
|
+
| `/palette` | Open command palette (fuzzy search) |
|
|
84
|
+
| `/extensions` | Browse installed LEX extensions |
|
|
85
|
+
| `/config` | View and edit settings files |
|
|
86
|
+
|
|
87
|
+
## Hotkeys
|
|
88
|
+
|
|
89
|
+
| Key | Action |
|
|
90
|
+
|-----|--------|
|
|
91
|
+
| Ctrl+D | Toggle dashboard |
|
|
92
|
+
| Ctrl+K | Open command palette |
|
|
93
|
+
| Ctrl+S | Open session picker |
|
|
94
|
+
| Ctrl+L | Refresh screen |
|
|
95
|
+
| Escape | Go back / dismiss overlay |
|
|
96
|
+
| Tab | Auto-complete slash commands |
|
|
72
97
|
|
|
73
98
|
## Architecture
|
|
74
99
|
|
|
@@ -84,16 +109,23 @@ legion-tty
|
|
|
84
109
|
Onboarding # First-run wizard (rain -> intro -> wizard -> reveal)
|
|
85
110
|
Chat # AI chat REPL with streaming + slash commands
|
|
86
111
|
Dashboard # Operational status panels
|
|
112
|
+
Extensions # LEX gem browser by category
|
|
113
|
+
Config # Settings file viewer/editor
|
|
87
114
|
|
|
88
115
|
Components/
|
|
89
116
|
DigitalRain # Matrix-style falling characters
|
|
90
|
-
InputBar # Prompt line with thinking indicator
|
|
91
|
-
MessageStream # Scrollable message history
|
|
92
|
-
StatusBar # Model, tokens, cost, session
|
|
117
|
+
InputBar # Prompt line with tab completion + thinking indicator
|
|
118
|
+
MessageStream # Scrollable message history with markdown rendering
|
|
119
|
+
StatusBar # Model, plan mode, thinking, tokens, cost, session
|
|
93
120
|
ToolPanel # Expandable tool use panels
|
|
94
121
|
MarkdownView # TTY::Markdown rendering
|
|
95
122
|
WizardPrompt # TTY::Prompt wrappers
|
|
96
|
-
TokenTracker #
|
|
123
|
+
TokenTracker # Per-model token counting and cost estimation
|
|
124
|
+
CommandPalette # Fuzzy-search command/screen/session overlay
|
|
125
|
+
ModelPicker # LLM provider/model selection
|
|
126
|
+
SessionPicker # Session list and selection
|
|
127
|
+
TableView # TTY::Table wrapper
|
|
128
|
+
ProgressPanel # TTY::ProgressBar wrapper
|
|
97
129
|
|
|
98
130
|
Background/
|
|
99
131
|
Scanner # Service port probing, git repo discovery, shell history
|
|
@@ -101,20 +133,11 @@ legion-tty
|
|
|
101
133
|
KerberosProbe # klist + LDAP profile resolution
|
|
102
134
|
```
|
|
103
135
|
|
|
104
|
-
##
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
| Streaming chat | Yes | Yes | Yes |
|
|
110
|
-
| Tool use panels | Yes | Yes | Yes |
|
|
111
|
-
| Dashboard | Yes (services, extensions) | No | No |
|
|
112
|
-
| Session persistence | Yes | Yes (conversations) | No |
|
|
113
|
-
| Environment scanning | Yes (services, repos, history) | Yes (git context) | Yes (git context) |
|
|
114
|
-
| Extension ecosystem | Yes (LEX gems) | Yes (MCP servers) | Yes (tools) |
|
|
115
|
-
| Identity probing | Yes (Kerberos, GitHub, LDAP) | No | No |
|
|
116
|
-
| Token/cost tracking | Yes | Yes | Yes |
|
|
117
|
-
| Hotkey navigation | Yes | Yes | No |
|
|
136
|
+
## LLM Integration
|
|
137
|
+
|
|
138
|
+
legion-tty uses **Legion::LLM exclusively** for all LLM operations. No direct RubyLLM calls. If Legion::LLM is not available or not started, the chat shell runs without LLM (commands still work, messages show "LLM not configured").
|
|
139
|
+
|
|
140
|
+
The boot sequence mirrors `Legion::Service`: logging -> settings -> crypt -> resolve_secrets -> LLM merge -> start.
|
|
118
141
|
|
|
119
142
|
## Configuration
|
|
120
143
|
|
|
@@ -131,8 +154,8 @@ Boot logs go to `~/.legionio/logs/tty-boot.log`.
|
|
|
131
154
|
|
|
132
155
|
```bash
|
|
133
156
|
bundle install
|
|
134
|
-
bundle exec rspec
|
|
135
|
-
bundle exec rubocop
|
|
157
|
+
bundle exec rspec # 598 examples, 0 failures
|
|
158
|
+
bundle exec rubocop # 68 files, 0 offenses
|
|
136
159
|
```
|
|
137
160
|
|
|
138
161
|
## License
|
|
@@ -6,8 +6,11 @@ module Legion
|
|
|
6
6
|
module TTY
|
|
7
7
|
module Components
|
|
8
8
|
class InputBar
|
|
9
|
-
|
|
9
|
+
attr_reader :completions
|
|
10
|
+
|
|
11
|
+
def initialize(name: 'User', reader: nil, completions: [])
|
|
10
12
|
@name = name
|
|
13
|
+
@completions = completions
|
|
11
14
|
@reader = reader || build_default_reader
|
|
12
15
|
@thinking = false
|
|
13
16
|
end
|
|
@@ -32,14 +35,47 @@ module Legion
|
|
|
32
35
|
@thinking
|
|
33
36
|
end
|
|
34
37
|
|
|
38
|
+
def complete(partial)
|
|
39
|
+
return [] if partial.nil? || partial.empty?
|
|
40
|
+
|
|
41
|
+
@completions.select { |c| c.start_with?(partial) }.sort
|
|
42
|
+
end
|
|
43
|
+
|
|
35
44
|
private
|
|
36
45
|
|
|
37
46
|
def build_default_reader
|
|
38
47
|
require 'tty-reader'
|
|
39
|
-
::TTY::Reader.new
|
|
48
|
+
reader = ::TTY::Reader.new
|
|
49
|
+
register_tab_completion(reader)
|
|
50
|
+
reader
|
|
40
51
|
rescue LoadError
|
|
41
52
|
nil
|
|
42
53
|
end
|
|
54
|
+
|
|
55
|
+
def register_tab_completion(reader)
|
|
56
|
+
return if @completions.empty?
|
|
57
|
+
|
|
58
|
+
@tab_matches = []
|
|
59
|
+
@tab_index = 0
|
|
60
|
+
|
|
61
|
+
reader.on(:keypress) do |event|
|
|
62
|
+
handle_tab(event) if event.value == "\t"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_tab(event)
|
|
67
|
+
line = event.line.text.to_s
|
|
68
|
+
matches = complete(line)
|
|
69
|
+
return if matches.empty?
|
|
70
|
+
|
|
71
|
+
if matches.size == 1
|
|
72
|
+
event.line.replace(matches.first)
|
|
73
|
+
else
|
|
74
|
+
@tab_matches = matches unless @tab_matches == matches
|
|
75
|
+
event.line.replace(@tab_matches[@tab_index % @tab_matches.size])
|
|
76
|
+
@tab_index += 1
|
|
77
|
+
end
|
|
78
|
+
end
|
|
43
79
|
end
|
|
44
80
|
end
|
|
45
81
|
end
|
|
@@ -29,6 +29,21 @@ module Legion
|
|
|
29
29
|
@messages.last[:tool_panels] << panel
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def add_tool_call(name:, args: {}, status: :running)
|
|
33
|
+
require_relative 'tool_panel'
|
|
34
|
+
panel = ToolPanel.new(name: name, args: args, status: status)
|
|
35
|
+
@messages << { role: :tool, content: panel, tool_panel: true }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def update_tool_call(name:, status:, duration: nil, result: nil, error: nil)
|
|
39
|
+
tool_msg = @messages.reverse.find do |m|
|
|
40
|
+
m[:tool_panel] && m[:content].is_a?(ToolPanel) && m[:content].instance_variable_get(:@name) == name
|
|
41
|
+
end
|
|
42
|
+
return unless tool_msg
|
|
43
|
+
|
|
44
|
+
apply_tool_panel_update(tool_msg[:content], status: status, duration: duration, result: result, error: error)
|
|
45
|
+
end
|
|
46
|
+
|
|
32
47
|
def scroll_up(lines = 1)
|
|
33
48
|
@scroll_offset += lines
|
|
34
49
|
end
|
|
@@ -60,6 +75,7 @@ module Legion
|
|
|
60
75
|
when :user then user_lines(msg)
|
|
61
76
|
when :assistant then assistant_lines(msg, width)
|
|
62
77
|
when :system then system_lines(msg)
|
|
78
|
+
when :tool then tool_call_lines(msg, width)
|
|
63
79
|
else []
|
|
64
80
|
end
|
|
65
81
|
end
|
|
@@ -85,9 +101,22 @@ module Legion
|
|
|
85
101
|
msg[:content].split("\n").map { |l| " #{Theme.c(:muted, l)}" }
|
|
86
102
|
end
|
|
87
103
|
|
|
104
|
+
def tool_call_lines(msg, width)
|
|
105
|
+
return [] unless msg[:tool_panel] && msg[:content].respond_to?(:render)
|
|
106
|
+
|
|
107
|
+
msg[:content].render(width: width).split("\n")
|
|
108
|
+
end
|
|
109
|
+
|
|
88
110
|
def panel_lines(msg, width)
|
|
89
111
|
msg[:tool_panels].flat_map { |panel| panel.render(width: width).split("\n") }
|
|
90
112
|
end
|
|
113
|
+
|
|
114
|
+
def apply_tool_panel_update(panel, status:, duration:, result:, error:)
|
|
115
|
+
panel.instance_variable_set(:@status, status)
|
|
116
|
+
panel.instance_variable_set(:@duration, duration) if duration
|
|
117
|
+
panel.instance_variable_set(:@result, result) if result
|
|
118
|
+
panel.instance_variable_set(:@error, error) if error
|
|
119
|
+
end
|
|
91
120
|
end
|
|
92
121
|
end
|
|
93
122
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../theme'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module TTY
|
|
7
|
+
module Components
|
|
8
|
+
class ProgressPanel
|
|
9
|
+
attr_reader :title, :total, :current
|
|
10
|
+
|
|
11
|
+
def initialize(title:, total:, output: $stdout)
|
|
12
|
+
@title = title
|
|
13
|
+
@total = total
|
|
14
|
+
@current = 0
|
|
15
|
+
@output = output
|
|
16
|
+
@bar = build_bar
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def advance(step = 1)
|
|
20
|
+
@current = [@current + step, @total].min
|
|
21
|
+
@bar&.advance(step)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def finish
|
|
25
|
+
remaining = @total - @current
|
|
26
|
+
@bar&.advance(remaining) if remaining.positive?
|
|
27
|
+
@current = @total
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def finished?
|
|
31
|
+
@current >= @total
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def percent
|
|
35
|
+
return 0 if @total.zero?
|
|
36
|
+
|
|
37
|
+
((@current.to_f / @total) * 100).round(1)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def render(width: 80)
|
|
41
|
+
pct = percent
|
|
42
|
+
label = Theme.c(:accent, @title)
|
|
43
|
+
bar = build_render_bar(width, pct)
|
|
44
|
+
"#{label} [#{bar}] #{Theme.c(:secondary, "#{pct}%")}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def build_render_bar(width, pct)
|
|
50
|
+
bar_width = [width - @title.length - 12, 10].max
|
|
51
|
+
filled = (bar_width * pct / 100.0).round
|
|
52
|
+
empty = bar_width - filled
|
|
53
|
+
Theme.c(:primary, '#' * filled) + Theme.c(:muted, '-' * empty)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_bar
|
|
57
|
+
require 'tty-progressbar'
|
|
58
|
+
::TTY::ProgressBar.new(
|
|
59
|
+
"#{@title} [:bar] :percent",
|
|
60
|
+
total: @total,
|
|
61
|
+
output: @output,
|
|
62
|
+
width: 40
|
|
63
|
+
)
|
|
64
|
+
rescue LoadError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -92,20 +92,16 @@ module Legion
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
def send_to_llm(message)
|
|
95
|
-
unless @llm_chat
|
|
95
|
+
unless @llm_chat || daemon_available?
|
|
96
96
|
@message_stream.append_streaming('LLM not configured. Use /help for commands.')
|
|
97
97
|
return
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
@message_stream.append_streaming(chunk.content) if chunk.content
|
|
105
|
-
render_screen
|
|
100
|
+
if daemon_available?
|
|
101
|
+
send_via_daemon(message)
|
|
102
|
+
else
|
|
103
|
+
send_via_direct(message)
|
|
106
104
|
end
|
|
107
|
-
@status_bar.update(thinking: false)
|
|
108
|
-
track_response_tokens(response)
|
|
109
105
|
rescue StandardError => e
|
|
110
106
|
@status_bar.update(thinking: false)
|
|
111
107
|
@message_stream.append_streaming("\n[Error: #{e.message}]")
|
|
@@ -142,6 +138,40 @@ module Legion
|
|
|
142
138
|
@llm_chat.with_instructions(prompt) if @llm_chat.respond_to?(:with_instructions)
|
|
143
139
|
end
|
|
144
140
|
|
|
141
|
+
def send_via_daemon(message)
|
|
142
|
+
result = Legion::LLM.ask(message: message)
|
|
143
|
+
|
|
144
|
+
case result&.dig(:status)
|
|
145
|
+
when :done
|
|
146
|
+
@message_stream.append_streaming(result[:response])
|
|
147
|
+
when :error
|
|
148
|
+
err = result.dig(:error, :message) || 'Unknown error'
|
|
149
|
+
@message_stream.append_streaming("\n[Daemon error: #{err}]")
|
|
150
|
+
else
|
|
151
|
+
send_via_direct(message)
|
|
152
|
+
end
|
|
153
|
+
rescue StandardError
|
|
154
|
+
send_via_direct(message)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def send_via_direct(message)
|
|
158
|
+
return unless @llm_chat
|
|
159
|
+
|
|
160
|
+
@status_bar.update(thinking: true)
|
|
161
|
+
render_screen
|
|
162
|
+
response = @llm_chat.ask(message) do |chunk|
|
|
163
|
+
@status_bar.update(thinking: false)
|
|
164
|
+
@message_stream.append_streaming(chunk.content) if chunk.content
|
|
165
|
+
render_screen
|
|
166
|
+
end
|
|
167
|
+
@status_bar.update(thinking: false)
|
|
168
|
+
track_response_tokens(response)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def daemon_available?
|
|
172
|
+
!!(defined?(Legion::LLM::DaemonClient) && Legion::LLM::DaemonClient.available?)
|
|
173
|
+
end
|
|
174
|
+
|
|
145
175
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
146
176
|
def build_system_prompt(cfg)
|
|
147
177
|
lines = ['You are Legion, an async cognition engine and AI assistant.']
|
|
@@ -282,16 +312,50 @@ module Legion
|
|
|
282
312
|
return
|
|
283
313
|
end
|
|
284
314
|
|
|
285
|
-
|
|
315
|
+
apply_model_switch(name)
|
|
316
|
+
rescue StandardError => e
|
|
317
|
+
@message_stream.add_message(role: :system, content: "Failed to switch model: #{e.message}")
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def apply_model_switch(name)
|
|
321
|
+
new_chat = try_provider_switch(name)
|
|
322
|
+
if new_chat
|
|
323
|
+
@llm_chat = new_chat
|
|
324
|
+
@status_bar.update(model: name)
|
|
325
|
+
@token_tracker.update_model(name)
|
|
326
|
+
@message_stream.add_message(role: :system, content: "Switched to provider: #{name}")
|
|
327
|
+
elsif @llm_chat.respond_to?(:with_model)
|
|
286
328
|
@llm_chat.with_model(name)
|
|
287
329
|
@status_bar.update(model: name)
|
|
330
|
+
@token_tracker.update_model(name)
|
|
288
331
|
@message_stream.add_message(role: :system, content: "Model switched to: #{name}")
|
|
289
332
|
else
|
|
290
333
|
@status_bar.update(model: name)
|
|
291
334
|
@message_stream.add_message(role: :system, content: "Model set to: #{name}")
|
|
292
335
|
end
|
|
293
|
-
|
|
294
|
-
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def try_provider_switch(name)
|
|
339
|
+
return nil unless defined?(Legion::LLM)
|
|
340
|
+
|
|
341
|
+
providers = Legion::LLM.settings[:providers]
|
|
342
|
+
return nil unless providers.is_a?(Hash) && providers.key?(name.to_sym)
|
|
343
|
+
|
|
344
|
+
Legion::LLM.chat(provider: name)
|
|
345
|
+
rescue StandardError
|
|
346
|
+
nil
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def open_model_picker
|
|
350
|
+
require_relative '../components/model_picker'
|
|
351
|
+
picker = Components::ModelPicker.new(
|
|
352
|
+
current_provider: safe_config[:provider],
|
|
353
|
+
current_model: @llm_chat.respond_to?(:model) ? @llm_chat.model.to_s : nil
|
|
354
|
+
)
|
|
355
|
+
selection = picker.select_with_prompt(output: @output)
|
|
356
|
+
return unless selection
|
|
357
|
+
|
|
358
|
+
switch_model(selection[:provider])
|
|
295
359
|
end
|
|
296
360
|
|
|
297
361
|
def show_current_model
|
|
@@ -551,7 +615,7 @@ module Legion
|
|
|
551
615
|
def build_default_input_bar
|
|
552
616
|
cfg = safe_config
|
|
553
617
|
name = cfg[:name] || 'User'
|
|
554
|
-
Components::InputBar.new(name: name)
|
|
618
|
+
Components::InputBar.new(name: name, completions: SLASH_COMMANDS)
|
|
555
619
|
end
|
|
556
620
|
|
|
557
621
|
def terminal_width
|
data/lib/legion/tty/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -207,6 +207,7 @@ files:
|
|
|
207
207
|
- lib/legion/tty/components/markdown_view.rb
|
|
208
208
|
- lib/legion/tty/components/message_stream.rb
|
|
209
209
|
- lib/legion/tty/components/model_picker.rb
|
|
210
|
+
- lib/legion/tty/components/progress_panel.rb
|
|
210
211
|
- lib/legion/tty/components/session_picker.rb
|
|
211
212
|
- lib/legion/tty/components/status_bar.rb
|
|
212
213
|
- lib/legion/tty/components/table_view.rb
|