legion-tty 0.4.11 → 0.4.13

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: 3e2b8a679110725634b3db870bb623fc8f5cef89706890f9c57c579490c85114
4
- data.tar.gz: 6487e27647ed48406731a9c0c1bf1664b64831b60139dd698ae794d2dc8d3871
3
+ metadata.gz: bc958211b7004e0392a9e96cf2a874d6c54d6f0c6f183d87d80f428e16d3b014
4
+ data.tar.gz: 13264f1761391724054983f250767d9308aab1a397d0dc1577292268a0f7e031
5
5
  SHA512:
6
- metadata.gz: 9c89fb3dbcb807c628889b2b60566b3b23cad1356c26de42cd1d02d75a2977e7856528b0193e8bb9dd630ee4cf52b2b55e06e009c8fc3e34550a1712d6768b68
7
- data.tar.gz: ae426495d1eb8708723a5c2804a4a77e4a557480324c23b370b0ebe324ea1a7ece957665b133f7810952f03f586cb0e87dcef3e1c78e9fcde81fe13e0e6e4760
6
+ metadata.gz: fd92069e863ebb5bd1185068adf3f50323fb276a2ae0479a8253e0220c1e4bc22a76abb7b8eb73a4c4a22c84be9f22cddf6eea22d510d7772fd459d3d74a1ff9
7
+ data.tar.gz: afcce634192c2e16f133b0d237cd3720536dfce07e2cae17089f208867546d82d75a58bb352887da1d0d29c0e651d047b79596fea0428d35f152e48f17707069
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.13] - 2026-03-19
4
+
5
+ ### Added
6
+ - Help overlay: `/help` now renders as a categorized overlay (SESSION/CHAT/LLM/NAV/DISPLAY/TOOLS) via screen manager
7
+ - Session message count in status bar ("N msgs" segment)
8
+ - `/welcome` command: redisplay the welcome message
9
+ - `/tips` command: show random usage tips (15 tips covering commands, hotkeys, features)
10
+
11
+ ## [0.4.12] - 2026-03-19
12
+
13
+ ### Added
14
+ - `/grep <pattern>` command: regex search across message history (case-insensitive, with RegexpError handling)
15
+ - `/time` command: display current date, time, and timezone
16
+
17
+ ### Changed
18
+ - Refactored Chat screen into 6 concern modules (chat.rb 1220 -> 466 lines):
19
+ - `chat/session_commands.rb` — save/load/sessions/delete/rename
20
+ - `chat/export_commands.rb` — export/bookmark/html/json/markdown
21
+ - `chat/message_commands.rb` — compact/copy/diff/search/grep/undo/pin/pins
22
+ - `chat/ui_commands.rb` — help/clear/dashboard/hotkeys/palette/context/stats/debug/history/uptime/time
23
+ - `chat/model_commands.rb` — model/system/personality switching
24
+ - `chat/custom_commands.rb` — alias/snippet management
25
+
3
26
  ## [0.4.11] - 2026-03-19
4
27
 
5
28
  ### Added
data/README.md CHANGED
@@ -2,30 +2,36 @@
2
2
 
3
3
  Rich terminal UI for the LegionIO async cognition engine.
4
4
 
5
- **Version**: 0.4.8
5
+ **Version**: 0.4.12
6
6
 
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.
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.
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 27 slash commands, tab completion, markdown rendering, and tool panels
14
- - **Operational dashboard** - Service status, extension inventory, system info, recent activity (Ctrl+D or `/dashboard`)
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
13
+ - **AI chat shell** - Streaming LLM chat with 40 slash commands, tab completion, markdown rendering, and tool panels
14
+ - **Operational dashboard** - Service/LLM status, extension inventory, system info, panel navigation (Ctrl+D or `/dashboard`)
15
+ - **Extensions browser** - Browse installed LEX gems by category with detail view and homepage opener ('o' key)
16
+ - **Config viewer/editor** - View and edit `~/.legionio/settings/*.json` with vault:// masking and JSON validation
17
17
  - **Command palette** - Fuzzy-search overlay for all commands, screens, and sessions (Ctrl+K or `/palette`)
18
18
  - **Model picker** - Switch LLM providers interactively
19
- - **Session management** - Auto-save on quit, `/save`, `/load`, `/sessions`, session picker (Ctrl+S)
19
+ - **Session management** - Auto-save on quit, `/save`, `/load`, `/sessions`, `/rename`, session picker (Ctrl+S)
20
20
  - **Token tracking** - Per-model pricing for 9 models across 8 providers via `/cost`
21
21
  - **Plan mode** - Bookmark messages without sending to LLM (`/plan`)
22
22
  - **Personality styles** - Switch between default, concise, detailed, friendly, technical (`/personality`)
23
23
  - **Theme selection** - Four built-in themes: purple (default), green, blue, amber (`/theme`)
24
- - **Conversation tools** - `/compact`, `/copy`, `/diff`, `/search`, `/stats`
24
+ - **Conversation tools** - `/compact`, `/copy`, `/diff`, `/search`, `/grep`, `/stats`, `/undo`
25
+ - **Message pinning** - Pin important messages (`/pin`), view pins (`/pins`), export bookmarks (`/bookmark`)
26
+ - **Command aliases** - Create custom shortcuts for frequently used commands (`/alias`)
27
+ - **Code snippets** - Save and load reusable text snippets (`/snippet`)
28
+ - **Debug mode** - Toggle internal state display (`/debug`)
29
+ - **Session context** - View active settings summary (`/context`)
30
+ - **Toast notifications** - Transient status bar messages for save/load/export/theme actions
25
31
  - **Hotkey navigation** - Ctrl+D (dashboard), Ctrl+K (palette), Ctrl+S (sessions), Escape (back)
26
32
  - **Tab completion** - Type `/` and Tab to auto-complete slash commands
27
- - **Input history** - Up/down arrow to navigate previous inputs
28
- - **Progress panel** - Visual progress bars for long operations (extension scanning, gem ops)
33
+ - **Input history** - Up/down arrow to navigate previous inputs, `/history` to view
34
+ - **Progress panel** - Visual progress bars for long operations
29
35
  - **Animated spinner** - Status bar spinner during LLM thinking
30
36
  - **Daemon routing** - Routes through LegionIO daemon when available, falls back to direct
31
37
  - **Second-run flow** - Skips onboarding, re-scans environment, drops into chat
@@ -74,9 +80,9 @@ legion chat prompt "explain async cognition"
74
80
  | `/quit` | Exit (auto-saves session) |
75
81
  | `/clear` | Clear message history |
76
82
  | `/model <name>` | Switch LLM model at runtime |
77
- | `/session <name>` | Rename current session |
83
+ | `/session <name>` | Set session name |
78
84
  | `/cost` | Show token usage and estimated cost |
79
- | `/export [md\|json]` | Export chat history to file |
85
+ | `/export [md\|json\|html]` | Export chat history to file |
80
86
  | `/tools` | List discovered LEX extensions |
81
87
  | `/dashboard` | Toggle operational dashboard |
82
88
  | `/hotkeys` | Show registered hotkey bindings |
@@ -91,11 +97,24 @@ legion chat prompt "explain async cognition"
91
97
  | `/config` | View and edit settings files |
92
98
  | `/theme [name]` | Switch color theme (purple/green/blue/amber) |
93
99
  | `/search <text>` | Search message history |
100
+ | `/grep <pattern>` | Regex search across messages |
94
101
  | `/compact [N]` | Keep last N message pairs, remove older |
95
102
  | `/copy` | Copy last assistant response to clipboard |
96
103
  | `/diff` | Show new messages since last session load |
97
104
  | `/stats` | Show conversation statistics |
98
105
  | `/personality [style]` | Switch personality (default/concise/detailed/friendly/technical) |
106
+ | `/undo` | Remove last user+assistant message pair |
107
+ | `/history` | Show input history |
108
+ | `/pin [N]` | Pin a message (last assistant or by index) |
109
+ | `/pins` | Show all pinned messages |
110
+ | `/rename <name>` | Rename current session (moves saved file) |
111
+ | `/context` | Show active session state summary |
112
+ | `/alias [name] [cmd]` | Create or list command aliases |
113
+ | `/snippet <action>` | Save/load/list/delete code snippets |
114
+ | `/debug` | Toggle debug mode |
115
+ | `/uptime` | Show session elapsed time |
116
+ | `/bookmark` | Export pinned messages to file |
117
+ | `/time` | Show current date and time |
99
118
 
100
119
  ## Hotkeys
101
120
 
@@ -120,16 +139,22 @@ legion-tty
120
139
 
121
140
  Screens/
122
141
  Onboarding # First-run wizard (rain -> intro -> wizard -> reveal)
123
- Chat # AI chat REPL with streaming + slash commands
124
- Dashboard # Operational status panels
125
- Extensions # LEX gem browser by category
126
- Config # Settings file viewer/editor
142
+ Chat # AI chat REPL with streaming + 40 slash commands
143
+ SessionCommands # save/load/sessions/delete/rename
144
+ ExportCommands # export/bookmark/html/json/markdown
145
+ MessageCommands # compact/copy/diff/search/grep/undo/pin/pins
146
+ UiCommands # help/clear/dashboard/hotkeys/palette/context/stats/debug/history/uptime/time
147
+ ModelCommands # model/system/personality switching
148
+ CustomCommands # alias/snippet management
149
+ Dashboard # Service/LLM status, panel navigation (j/k/1-5)
150
+ Extensions # LEX gem browser by category with homepage opener
151
+ Config # Settings file viewer/editor with JSON validation
127
152
 
128
153
  Components/
129
154
  DigitalRain # Matrix-style falling characters
130
- InputBar # Prompt line with tab completion + thinking indicator
131
- MessageStream # Scrollable message history with markdown rendering
132
- StatusBar # Model, plan mode, thinking, tokens, cost, session
155
+ InputBar # Prompt line with tab completion + input history
156
+ MessageStream # Scrollable message history with markdown + timestamps
157
+ StatusBar # Model, plan, debug, notifications, thinking, tokens, cost, session, scroll
133
158
  ToolPanel # Expandable tool use panels
134
159
  MarkdownView # TTY::Markdown rendering
135
160
  WizardPrompt # TTY::Prompt wrappers
@@ -161,6 +186,7 @@ Identity and credentials are stored in `~/.legionio/settings/`:
161
186
  - `credentials.json` - LLM provider and API key (chmod 600)
162
187
 
163
188
  Sessions are stored in `~/.legionio/sessions/`.
189
+ Snippets are stored in `~/.legionio/snippets/`.
164
190
  Exports go to `~/.legionio/exports/`.
165
191
  Boot logs go to `~/.legionio/logs/tty-boot.log`.
166
192
 
@@ -168,8 +194,8 @@ Boot logs go to `~/.legionio/logs/tty-boot.log`.
168
194
 
169
195
  ```bash
170
196
  bundle install
171
- bundle exec rspec # 653 examples, 0 failures
172
- bundle exec rubocop # 77 files, 0 offenses
197
+ bundle exec rspec # 836 examples, 0 failures
198
+ bundle exec rubocop # 92 files, 0 offenses
173
199
  ```
174
200
 
175
201
  ## License
@@ -6,10 +6,11 @@ require_relative 'notification'
6
6
  module Legion
7
7
  module TTY
8
8
  module Components
9
+ # rubocop:disable Metrics/ClassLength
9
10
  class StatusBar
10
11
  def initialize
11
12
  @state = { model: nil, tokens: 0, cost: 0.0, session: 'default', thinking: false, plan_mode: false,
12
- debug_mode: false }
13
+ debug_mode: false, message_count: 0 }
13
14
  @notifications = []
14
15
  end
15
16
 
@@ -46,6 +47,7 @@ module Legion
46
47
  notification_segment,
47
48
  tokens_segment,
48
49
  cost_segment,
50
+ message_count_segment,
49
51
  session_segment,
50
52
  scroll_segment
51
53
  ].compact
@@ -90,6 +92,11 @@ module Legion
90
92
  Theme.c(:success, format('$%.3f', @state[:cost])) if @state[:cost].to_f.positive?
91
93
  end
92
94
 
95
+ def message_count_segment
96
+ count = @state[:message_count].to_i
97
+ Theme.c(:muted, "#{count} msgs") if count.positive?
98
+ end
99
+
93
100
  def session_segment
94
101
  Theme.c(:muted, @state[:session]) if @state[:session]
95
102
  end
@@ -132,6 +139,7 @@ module Legion
132
139
  result
133
140
  end
134
141
  end
142
+ # rubocop:enable Metrics/ClassLength
135
143
  end
136
144
  end
137
145
  end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module TTY
5
+ module Screens
6
+ class Chat < Base
7
+ # rubocop:disable Metrics/ModuleLength
8
+ module CustomCommands
9
+ private
10
+
11
+ def handle_alias(input)
12
+ parts = input.split(nil, 3)
13
+ if parts.size < 2
14
+ if @aliases.empty?
15
+ @message_stream.add_message(role: :system, content: 'No aliases defined.')
16
+ else
17
+ lines = @aliases.map { |k, v| " #{k} => #{v}" }
18
+ @message_stream.add_message(role: :system, content: "Aliases:\n#{lines.join("\n")}")
19
+ end
20
+ return :handled
21
+ end
22
+
23
+ shortname = parts[1]
24
+ expansion = parts[2]
25
+ unless expansion
26
+ @message_stream.add_message(role: :system, content: 'Usage: /alias <shortname> <command and args>')
27
+ return :handled
28
+ end
29
+
30
+ alias_key = shortname.start_with?('/') ? shortname : "/#{shortname}"
31
+ @aliases[alias_key] = expansion
32
+ @message_stream.add_message(role: :system, content: "Alias created: #{alias_key} => #{expansion}")
33
+ :handled
34
+ end
35
+
36
+ def handle_snippet(input)
37
+ parts = input.split(nil, 3)
38
+ subcommand = parts[1]
39
+ name = parts[2]
40
+
41
+ case subcommand
42
+ when 'save'
43
+ snippet_save(name)
44
+ when 'load'
45
+ snippet_load(name)
46
+ when 'list'
47
+ snippet_list
48
+ when 'delete'
49
+ snippet_delete(name)
50
+ else
51
+ @message_stream.add_message(
52
+ role: :system,
53
+ content: 'Usage: /snippet save|load|list|delete <name>'
54
+ )
55
+ end
56
+ :handled
57
+ end
58
+
59
+ def snippet_dir
60
+ File.expand_path('~/.legionio/snippets')
61
+ end
62
+
63
+ # rubocop:disable Metrics/AbcSize
64
+ def snippet_save(name)
65
+ unless name
66
+ @message_stream.add_message(role: :system, content: 'Usage: /snippet save <name>')
67
+ return
68
+ end
69
+
70
+ last_assistant = @message_stream.messages.reverse.find { |m| m[:role] == :assistant }
71
+ unless last_assistant
72
+ @message_stream.add_message(role: :system, content: 'No assistant message to save as snippet.')
73
+ return
74
+ end
75
+
76
+ require 'fileutils'
77
+ FileUtils.mkdir_p(snippet_dir)
78
+ path = File.join(snippet_dir, "#{name}.txt")
79
+ File.write(path, last_assistant[:content].to_s)
80
+ @snippets[name] = last_assistant[:content].to_s
81
+ @message_stream.add_message(role: :system, content: "Snippet '#{name}' saved.")
82
+ end
83
+ # rubocop:enable Metrics/AbcSize
84
+
85
+ def snippet_load(name)
86
+ unless name
87
+ @message_stream.add_message(role: :system, content: 'Usage: /snippet load <name>')
88
+ return
89
+ end
90
+
91
+ content = @snippets[name]
92
+ if content.nil?
93
+ path = File.join(snippet_dir, "#{name}.txt")
94
+ content = File.read(path) if File.exist?(path)
95
+ end
96
+
97
+ unless content
98
+ @message_stream.add_message(role: :system, content: "Snippet '#{name}' not found.")
99
+ return
100
+ end
101
+
102
+ @snippets[name] = content
103
+ @message_stream.add_message(role: :user, content: content)
104
+ @message_stream.add_message(role: :system, content: "Snippet '#{name}' inserted.")
105
+ end
106
+
107
+ # rubocop:disable Metrics/AbcSize
108
+ def snippet_list
109
+ disk_snippets = Dir.glob(File.join(snippet_dir, '*.txt')).map { |f| File.basename(f, '.txt') }
110
+ all_names = (@snippets.keys + disk_snippets).uniq.sort
111
+
112
+ if all_names.empty?
113
+ @message_stream.add_message(role: :system, content: 'No snippets saved.')
114
+ return
115
+ end
116
+
117
+ lines = all_names.map do |sname|
118
+ content = @snippets[sname] || begin
119
+ path = File.join(snippet_dir, "#{sname}.txt")
120
+ File.exist?(path) ? File.read(path) : ''
121
+ end
122
+ " #{sname}: #{truncate_text(content.to_s, 60)}"
123
+ end
124
+ @message_stream.add_message(role: :system, content: "Snippets (#{all_names.size}):\n#{lines.join("\n")}")
125
+ end
126
+ # rubocop:enable Metrics/AbcSize
127
+
128
+ def snippet_delete(name)
129
+ unless name
130
+ @message_stream.add_message(role: :system, content: 'Usage: /snippet delete <name>')
131
+ return
132
+ end
133
+
134
+ @snippets.delete(name)
135
+ path = File.join(snippet_dir, "#{name}.txt")
136
+ if File.exist?(path)
137
+ File.delete(path)
138
+ @message_stream.add_message(role: :system, content: "Snippet '#{name}' deleted.")
139
+ else
140
+ @message_stream.add_message(role: :system, content: "Snippet '#{name}' not found.")
141
+ end
142
+ end
143
+ end
144
+ # rubocop:enable Metrics/ModuleLength
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module TTY
5
+ module Screens
6
+ class Chat < Base
7
+ module ExportCommands
8
+ private
9
+
10
+ def handle_export(input)
11
+ require 'fileutils'
12
+ path = build_export_path(input)
13
+ dispatch_export(path, input.split[1]&.downcase)
14
+ @status_bar.notify(message: 'Exported', level: :success, ttl: 3)
15
+ @message_stream.add_message(role: :system, content: "Exported to: #{path}")
16
+ :handled
17
+ rescue StandardError => e
18
+ @message_stream.add_message(role: :system, content: "Export failed: #{e.message}")
19
+ :handled
20
+ end
21
+
22
+ def build_export_path(input)
23
+ format = input.split[1]&.downcase
24
+ format = 'md' unless %w[json md html].include?(format)
25
+ exports_dir = File.expand_path('~/.legionio/exports')
26
+ FileUtils.mkdir_p(exports_dir)
27
+ timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
28
+ ext = { 'json' => 'json', 'md' => 'md', 'html' => 'html' }[format]
29
+ File.join(exports_dir, "chat-#{timestamp}.#{ext}")
30
+ end
31
+
32
+ def dispatch_export(path, format)
33
+ if format == 'json'
34
+ export_json(path)
35
+ elsif format == 'html'
36
+ export_html(path)
37
+ else
38
+ export_markdown(path)
39
+ end
40
+ end
41
+
42
+ def export_markdown(path)
43
+ lines = ["# Chat Export\n", "_Exported: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}_\n\n---\n"]
44
+ @message_stream.messages.each do |msg|
45
+ role_label = msg[:role].to_s.capitalize
46
+ lines << "\n**#{role_label}**\n\n#{msg[:content]}\n"
47
+ end
48
+ File.write(path, lines.join)
49
+ end
50
+
51
+ def export_json(path)
52
+ require 'json'
53
+ data = {
54
+ exported_at: Time.now.iso8601,
55
+ token_summary: @token_tracker.summary,
56
+ messages: @message_stream.messages.map { |m| { role: m[:role].to_s, content: m[:content] } }
57
+ }
58
+ File.write(path, ::JSON.pretty_generate(data))
59
+ end
60
+
61
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
62
+ def export_html(path)
63
+ lines = [
64
+ '<!DOCTYPE html><html><head>',
65
+ '<meta charset="utf-8">',
66
+ '<title>Chat Export</title>',
67
+ '<style>',
68
+ 'body { font-family: system-ui; max-width: 800px; margin: 0 auto; ' \
69
+ 'padding: 20px; background: #1e1b2e; color: #d0cce6; }',
70
+ '.msg { margin: 12px 0; padding: 8px 12px; border-radius: 8px; }',
71
+ '.user { background: #2a2640; }',
72
+ '.assistant { background: #1a1730; }',
73
+ '.system { background: #25223a; color: #8b85a8; font-style: italic; }',
74
+ '.role { font-weight: bold; color: #9d91e6; font-size: 0.85em; }',
75
+ '</style></head><body>',
76
+ '<h1>Chat Export</h1>',
77
+ "<p>Exported: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}</p>"
78
+ ]
79
+ @message_stream.messages.each do |msg|
80
+ role = msg[:role].to_s
81
+ content = escape_html(msg[:content].to_s).gsub("\n", '<br>')
82
+ lines << "<div class='msg #{role}'>"
83
+ lines << "<span class='role'>#{role.capitalize}</span>"
84
+ lines << "<p>#{content}</p>"
85
+ lines << '</div>'
86
+ end
87
+ lines << '</body></html>'
88
+ File.write(path, lines.join("\n"))
89
+ end
90
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
91
+
92
+ def escape_html(text)
93
+ text.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('"', '&quot;')
94
+ end
95
+
96
+ # rubocop:disable Metrics/AbcSize
97
+ def handle_bookmark
98
+ require 'fileutils'
99
+ if @pinned_messages.empty?
100
+ @message_stream.add_message(role: :system, content: 'No pinned messages to export.')
101
+ return :handled
102
+ end
103
+
104
+ exports_dir = File.expand_path('~/.legionio/exports')
105
+ FileUtils.mkdir_p(exports_dir)
106
+ timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
107
+ path = File.join(exports_dir, "bookmarks-#{timestamp}.md")
108
+ lines = ["# Pinned Messages\n", "_Exported: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}_\n\n---\n"]
109
+ @pinned_messages.each_with_index do |msg, i|
110
+ role_label = msg[:role].to_s.capitalize
111
+ lines << "\n## Bookmark #{i + 1} (#{role_label})\n\n#{msg[:content]}\n"
112
+ end
113
+ File.write(path, lines.join)
114
+ @message_stream.add_message(role: :system, content: "Bookmarks exported to: #{path}")
115
+ :handled
116
+ rescue StandardError => e
117
+ @message_stream.add_message(role: :system, content: "Bookmark export failed: #{e.message}")
118
+ :handled
119
+ end
120
+ # rubocop:enable Metrics/AbcSize
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end