Module: OllamaAgent::Console
- Defined in:
- lib/ollama_agent/console.rb
Overview
ANSI styling for TTY output. Respects no-color.org/ via NO_COLOR. Assistant replies use tty-markdown when enabled (headings, lists, bold, code blocks). rubocop:disable Metrics/ModuleLength – single-responsibility output module; methods are all short
Constant Summary collapse
- THINKING_MARKDOWN_THEME =
Muted tty-markdown palette so “Thinking” stays visually distinct from the main reply (default TTY::Markdown theme uses cyan/yellow like normal assistant output).
{ em: :bright_black, header: %i[bright_black bold], hr: :bright_black, link: %i[bright_black underline], list: :bright_black, strong: %i[bright_black bold], table: :bright_black, quote: :bright_black, image: :bright_black, note: :bright_black, comment: :bright_black }.freeze
- THINKING_FRAME_WIDTH =
44
Class Method Summary collapse
- .apply_prompt(text) ⇒ Object
- .assistant_output(text) ⇒ Object
- .assistant_reply_heading ⇒ Object
- .bold(text) ⇒ Object
-
.close_streaming_thinking_if_still_open! ⇒ Object
When the model returns only thinking (no content), close ANSI state on stream end.
- .color_enabled? ⇒ Boolean
- .cyan(text) ⇒ Object
- .dim(text) ⇒ Object
- .dim_indent_body(text) ⇒ Object
- .error_line(text) ⇒ Object
-
.finalize_streaming_thinking_before_content! ⇒ Object
Call before the first streamed content token: closes dim reasoning, optional Assistant heading.
-
.format_assistant(text) ⇒ Object
Renders Markdown to the terminal (bold, lists, fenced code) when enabled; otherwise plain green text.
- .format_thinking(text) ⇒ Object
-
.format_thinking_compact_merge(text) ⇒ Object
Later thinking in the same run (tool rounds, new chat chunks): same block, separated by a blank line.
- .format_thinking_compact_open(text) ⇒ Object
- .green(text) ⇒ Object
- .magenta(text) ⇒ Object
- .mark_thinking_shown_in_session! ⇒ Object
- .markdown_enabled? ⇒ Boolean
- .patch_title(text) ⇒ Object
- .prompt_prefix ⇒ Object
-
.puts_assistant_message(message) ⇒ Object
Prints thinking (if any) then main content; duck-types #thinking and #content.
- .red(text) ⇒ Object
- .reset_thinking_session! ⇒ Object
- .style(text, *codes) ⇒ Object
- .thinking_already_shown_in_session? ⇒ Boolean
- .thinking_compact_body(text) ⇒ Object
- .thinking_frame_line ⇒ Object
-
.thinking_framed_style? ⇒ Boolean
compact(default): one “Thinking” label per agent run; later reasoning uses blank lines only (Cursor-like). -
.thinking_markdown_enabled? ⇒ Boolean
Thinking uses dim plain text by default so it stays visually separate from the main reply.
- .tool_call_line(name, args) ⇒ Object
- .tool_result_line(name, result) ⇒ Object
- .welcome_banner(text) ⇒ Object
- .write_stream_token(fragment) ⇒ Object
-
.write_streaming_thinking_fragment(fragment) ⇒ Object
Print one dim “Thinking” label, then stream fragments in dim until content tokens arrive.
- .yellow(text) ⇒ Object
Class Method Details
.apply_prompt(text) ⇒ Object
289 290 291 |
# File 'lib/ollama_agent/console.rb', line 289 def apply_prompt(text) yellow(text) end |
.assistant_output(text) ⇒ Object
177 178 179 |
# File 'lib/ollama_agent/console.rb', line 177 def assistant_output(text) green(text) end |
.assistant_reply_heading ⇒ Object
229 230 231 |
# File 'lib/ollama_agent/console.rb', line 229 def assistant_reply_heading bold(green("Assistant")) end |
.bold(text) ⇒ Object
161 |
# File 'lib/ollama_agent/console.rb', line 161 def bold(text) = style(text, 1) |
.close_streaming_thinking_if_still_open! ⇒ Object
When the model returns only thinking (no content), close ANSI state on stream end.
99 100 101 102 103 104 105 106 107 |
# File 'lib/ollama_agent/console.rb', line 99 def close_streaming_thinking_if_still_open! return unless Thread.current[:ollama_agent_stream_thinking_open] Thread.current[:ollama_agent_stream_thinking_open] = false Thread.current[:ollama_agent_stream_had_thinking] = false Thread.current[:ollama_agent_stream_thinking_buffer] = nil print "\e[0m" if color_enabled? puts end |
.color_enabled? ⇒ Boolean
28 29 30 |
# File 'lib/ollama_agent/console.rb', line 28 def color_enabled? $stdout.tty? && ENV["NO_COLOR"].to_s.empty? && ENV["OLLAMA_AGENT_COLOR"] != "0" end |
.cyan(text) ⇒ Object
163 |
# File 'lib/ollama_agent/console.rb', line 163 def cyan(text) = style(text, 36) |
.dim(text) ⇒ Object
162 |
# File 'lib/ollama_agent/console.rb', line 162 def dim(text) = style(text, 2) |
.dim_indent_body(text) ⇒ Object
221 222 223 224 225 226 227 |
# File 'lib/ollama_agent/console.rb', line 221 def dim_indent_body(text) s = text.to_s.rstrip return "" if s.empty? indent = " " dim(s.lines.map { |l| "#{indent}#{l.rstrip}" }.join("\n")) end |
.error_line(text) ⇒ Object
293 294 295 |
# File 'lib/ollama_agent/console.rb', line 293 def error_line(text) red(text) end |
.finalize_streaming_thinking_before_content! ⇒ Object
Call before the first streamed content token: closes dim reasoning, optional Assistant heading.
86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/ollama_agent/console.rb', line 86 def finalize_streaming_thinking_before_content! return unless Thread.current[:ollama_agent_stream_thinking_open] Thread.current[:ollama_agent_stream_thinking_open] = false had = Thread.current[:ollama_agent_stream_had_thinking] Thread.current[:ollama_agent_stream_had_thinking] = false Thread.current[:ollama_agent_stream_thinking_buffer] = nil print "\e[0m" if color_enabled? puts puts assistant_reply_heading if had end |
.format_assistant(text) ⇒ Object
Renders Markdown to the terminal (bold, lists, fenced code) when enabled; otherwise plain green text.
182 183 184 185 186 |
# File 'lib/ollama_agent/console.rb', line 182 def format_assistant(text) return assistant_output(text) unless markdown_enabled? markdown_parse(text) || assistant_output(text) end |
.format_thinking(text) ⇒ Object
188 189 190 191 192 193 194 195 196 197 |
# File 'lib/ollama_agent/console.rb', line 188 def format_thinking(text) line = thinking_frame_line header = "#{magenta(bold("Thinking"))}\n#{line}\n" body = if thinking_markdown_enabled? markdown_parse(text, thinking: true) || dim(text.to_s) else dim(text.to_s) end "#{header}#{body}\n#{line}" end |
.format_thinking_compact_merge(text) ⇒ Object
Later thinking in the same run (tool rounds, new chat chunks): same block, separated by a blank line.
206 207 208 |
# File 'lib/ollama_agent/console.rb', line 206 def format_thinking_compact_merge(text) "\n#{thinking_compact_body(text)}" end |
.format_thinking_compact_open(text) ⇒ Object
199 200 201 202 203 |
# File 'lib/ollama_agent/console.rb', line 199 def format_thinking_compact_open(text) label = color_enabled? ? dim("Thinking") : "Thinking" body = thinking_compact_body(text) "#{label}\n#{body}" end |
.green(text) ⇒ Object
164 |
# File 'lib/ollama_agent/console.rb', line 164 def green(text) = style(text, 32) |
.magenta(text) ⇒ Object
167 |
# File 'lib/ollama_agent/console.rb', line 167 def magenta(text) = style(text, 35) |
.mark_thinking_shown_in_session! ⇒ Object
60 61 62 |
# File 'lib/ollama_agent/console.rb', line 60 def mark_thinking_shown_in_session! Thread.current[:ollama_agent_thinking_shown] = true end |
.markdown_enabled? ⇒ Boolean
32 33 34 |
# File 'lib/ollama_agent/console.rb', line 32 def markdown_enabled? $stdout.tty? && ENV["NO_COLOR"].to_s.empty? && ENV["OLLAMA_AGENT_MARKDOWN"] != "0" end |
.patch_title(text) ⇒ Object
285 286 287 |
# File 'lib/ollama_agent/console.rb', line 285 def patch_title(text) bold(yellow(text)) end |
.prompt_prefix ⇒ Object
173 174 175 |
# File 'lib/ollama_agent/console.rb', line 173 def prompt_prefix cyan("> ") end |
.puts_assistant_message(message) ⇒ Object
Prints thinking (if any) then main content; duck-types #thinking and #content.
256 257 258 259 260 261 262 263 |
# File 'lib/ollama_agent/console.rb', line 256 def () thinking_present = (.thinking) if thinking_present puts thinking_output_chunk(.thinking) mark_thinking_shown_in_session! unless thinking_framed_style? end assistant_reply_if_present(.content, thinking_present) end |
.red(text) ⇒ Object
166 |
# File 'lib/ollama_agent/console.rb', line 166 def red(text) = style(text, 31) |
.reset_thinking_session! ⇒ Object
49 50 51 52 53 54 |
# File 'lib/ollama_agent/console.rb', line 49 def reset_thinking_session! Thread.current[:ollama_agent_thinking_shown] = false Thread.current[:ollama_agent_stream_thinking_open] = false Thread.current[:ollama_agent_stream_had_thinking] = false Thread.current[:ollama_agent_stream_thinking_buffer] = nil end |
.style(text, *codes) ⇒ Object
152 153 154 155 156 157 158 159 |
# File 'lib/ollama_agent/console.rb', line 152 def style(text, *codes) return text.to_s unless color_enabled? t = text.to_s return t if t.empty? || codes.flatten.compact.empty? "\e[#{codes.flatten.compact.join(";")}m#{t}\e[0m" end |
.thinking_already_shown_in_session? ⇒ Boolean
56 57 58 |
# File 'lib/ollama_agent/console.rb', line 56 def thinking_already_shown_in_session? Thread.current[:ollama_agent_thinking_shown] == true end |
.thinking_compact_body(text) ⇒ Object
210 211 212 213 214 215 216 217 218 219 |
# File 'lib/ollama_agent/console.rb', line 210 def thinking_compact_body(text) if thinking_markdown_enabled? parsed = markdown_parse(text, thinking: true) return parsed if parsed return dim_indent_body(text) end dim_indent_body(text) end |
.thinking_frame_line ⇒ Object
233 234 235 |
# File 'lib/ollama_agent/console.rb', line 233 def thinking_frame_line dim("-" * THINKING_FRAME_WIDTH) end |
.thinking_framed_style? ⇒ Boolean
compact (default): one “Thinking” label per agent run; later reasoning uses blank lines only (Cursor-like). framed: repeat the full banner + rulers on every assistant message (legacy).
44 45 46 |
# File 'lib/ollama_agent/console.rb', line 44 def thinking_framed_style? ENV.fetch("OLLAMA_AGENT_THINKING_STYLE", "compact").to_s.strip.downcase == "framed" end |
.thinking_markdown_enabled? ⇒ Boolean
Thinking uses dim plain text by default so it stays visually separate from the main reply. Set OLLAMA_AGENT_THINKING_MARKDOWN=1 to render thinking through tty-markdown (muted theme).
38 39 40 |
# File 'lib/ollama_agent/console.rb', line 38 def thinking_markdown_enabled? markdown_enabled? && ENV["OLLAMA_AGENT_THINKING_MARKDOWN"] == "1" end |
.tool_call_line(name, args) ⇒ Object
297 298 299 300 |
# File 'lib/ollama_agent/console.rb', line 297 def tool_call_line(name, args) keys = args.is_a?(Hash) ? args.keys.first(2).join(", ") : "" cyan("▶ #{name}(#{keys})") end |
.tool_result_line(name, result) ⇒ Object
302 303 304 305 |
# File 'lib/ollama_agent/console.rb', line 302 def tool_result_line(name, result) preview = result.to_s[0, 60].gsub(/\s+/, " ") dim("◀ #{name}: #{preview}") end |
.welcome_banner(text) ⇒ Object
169 170 171 |
# File 'lib/ollama_agent/console.rb', line 169 def (text) bold(cyan(text)) end |
.write_stream_token(fragment) ⇒ Object
80 81 82 83 |
# File 'lib/ollama_agent/console.rb', line 80 def write_stream_token(fragment) print utf8_for_stream(fragment) $stdout.flush end |
.write_streaming_thinking_fragment(fragment) ⇒ Object
Print one dim “Thinking” label, then stream fragments in dim until content tokens arrive. Handles both cumulative thinking strings (common from Ollama) and plain deltas; sanitizes UTF-8.
68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/ollama_agent/console.rb', line 68 def write_streaming_thinking_fragment(fragment) text = utf8_for_stream(fragment) return if text.empty? open_streaming_thinking_section_if_needed to_print = streaming_thinking_increment_to_print(text) return if to_print.empty? print to_print $stdout.flush end |
.yellow(text) ⇒ Object
165 |
# File 'lib/ollama_agent/console.rb', line 165 def yellow(text) = style(text, 33) |