klaude-code 1.2.16__py3-none-any.whl → 1.2.18__py3-none-any.whl

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.
Files changed (70) hide show
  1. klaude_code/cli/config_cmd.py +1 -1
  2. klaude_code/cli/debug.py +1 -1
  3. klaude_code/cli/main.py +3 -9
  4. klaude_code/cli/runtime.py +20 -13
  5. klaude_code/command/__init__.py +7 -1
  6. klaude_code/command/clear_cmd.py +2 -7
  7. klaude_code/command/command_abc.py +33 -5
  8. klaude_code/command/debug_cmd.py +79 -0
  9. klaude_code/command/diff_cmd.py +2 -6
  10. klaude_code/command/export_cmd.py +7 -7
  11. klaude_code/command/export_online_cmd.py +145 -0
  12. klaude_code/command/help_cmd.py +4 -9
  13. klaude_code/command/model_cmd.py +10 -6
  14. klaude_code/command/prompt_command.py +2 -6
  15. klaude_code/command/refresh_cmd.py +2 -7
  16. klaude_code/command/registry.py +2 -4
  17. klaude_code/command/release_notes_cmd.py +2 -6
  18. klaude_code/command/status_cmd.py +2 -7
  19. klaude_code/command/terminal_setup_cmd.py +2 -6
  20. klaude_code/command/thinking_cmd.py +13 -8
  21. klaude_code/config/config.py +16 -17
  22. klaude_code/config/select_model.py +81 -5
  23. klaude_code/const/__init__.py +1 -1
  24. klaude_code/core/executor.py +236 -109
  25. klaude_code/core/manager/__init__.py +2 -4
  26. klaude_code/core/manager/sub_agent_manager.py +1 -1
  27. klaude_code/core/prompts/prompt-claude-code.md +1 -1
  28. klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -1
  29. klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
  30. klaude_code/core/reminders.py +9 -35
  31. klaude_code/core/task.py +8 -0
  32. klaude_code/core/tool/__init__.py +2 -0
  33. klaude_code/core/tool/file/read_tool.py +38 -10
  34. klaude_code/core/tool/report_back_tool.py +28 -2
  35. klaude_code/core/tool/shell/bash_tool.py +22 -2
  36. klaude_code/core/tool/tool_runner.py +26 -23
  37. klaude_code/core/tool/truncation.py +23 -9
  38. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  39. klaude_code/core/tool/web/web_fetch_tool.py +36 -1
  40. klaude_code/core/tool/web/web_search_tool.md +23 -0
  41. klaude_code/core/tool/web/web_search_tool.py +126 -0
  42. klaude_code/core/turn.py +28 -0
  43. klaude_code/protocol/commands.py +2 -0
  44. klaude_code/protocol/events.py +8 -0
  45. klaude_code/protocol/sub_agent/__init__.py +1 -1
  46. klaude_code/protocol/sub_agent/explore.py +1 -1
  47. klaude_code/protocol/sub_agent/web.py +79 -0
  48. klaude_code/protocol/tools.py +1 -0
  49. klaude_code/session/session.py +2 -2
  50. klaude_code/session/templates/export_session.html +123 -37
  51. klaude_code/trace/__init__.py +20 -2
  52. klaude_code/ui/modes/repl/completers.py +19 -2
  53. klaude_code/ui/modes/repl/event_handler.py +44 -15
  54. klaude_code/ui/modes/repl/renderer.py +3 -3
  55. klaude_code/ui/renderers/metadata.py +2 -4
  56. klaude_code/ui/renderers/sub_agent.py +14 -10
  57. klaude_code/ui/renderers/thinking.py +24 -8
  58. klaude_code/ui/renderers/tools.py +83 -20
  59. klaude_code/ui/rich/code_panel.py +112 -0
  60. klaude_code/ui/rich/markdown.py +3 -4
  61. klaude_code/ui/rich/status.py +30 -6
  62. klaude_code/ui/rich/theme.py +10 -1
  63. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/METADATA +126 -25
  64. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/RECORD +67 -63
  65. klaude_code/core/manager/agent_manager.py +0 -132
  66. klaude_code/core/prompts/prompt-sub-agent-webfetch.md +0 -46
  67. klaude_code/protocol/sub_agent/web_fetch.py +0 -74
  68. /klaude_code/{config → cli}/list_model.py +0 -0
  69. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/WHEEL +0 -0
  70. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/entry_points.txt +0 -0
@@ -16,7 +16,7 @@ from klaude_code.ui.rich.theme import ThemeKey
16
16
 
17
17
  def _compact_schema_value(value: dict[str, Any]) -> str | list[Any] | dict[str, Any]:
18
18
  """Convert a JSON Schema value to compact representation."""
19
- value_type = value.get("type", "any")
19
+ value_type = value.get("type", "any").lower()
20
20
  desc = value.get("description", "")
21
21
 
22
22
  if value_type == "object":
@@ -64,16 +64,20 @@ def render_sub_agent_result(
64
64
 
65
65
  # Use rich JSON for structured output
66
66
  if has_structured_output:
67
- return Panel.fit(
68
- Group(
69
- Text(
70
- "use /export to view full output",
71
- style=ThemeKey.TOOL_RESULT,
67
+ try:
68
+ return Panel.fit(
69
+ Group(
70
+ Text(
71
+ "use /export to view full output",
72
+ style=ThemeKey.TOOL_RESULT,
73
+ ),
74
+ JSON(stripped_result),
72
75
  ),
73
- JSON(stripped_result),
74
- ),
75
- border_style=ThemeKey.LINES,
76
- )
76
+ border_style=ThemeKey.LINES,
77
+ )
78
+ except json.JSONDecodeError:
79
+ # Fall back to markdown if not valid JSON
80
+ pass
77
81
 
78
82
  lines = stripped_result.splitlines()
79
83
  if len(lines) > const.SUB_AGENT_RESULT_MAX_LINES:
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  from rich.console import RenderableType
2
4
  from rich.padding import Padding
3
5
  from rich.text import Text
@@ -10,14 +12,28 @@ def thinking_prefix() -> Text:
10
12
  return Text.from_markup("[not italic]⸫[/not italic] Thinking …", style=ThemeKey.THINKING)
11
13
 
12
14
 
13
- def _normalize_thinking_content(content: str) -> str:
15
+ def normalize_thinking_content(content: str) -> str:
14
16
  """Normalize thinking content for display."""
15
- return (
16
- content.rstrip()
17
- .replace("**\n\n", "** \n")
18
- .replace("\\n\\n\n\n", "") # Weird case of Gemini 3
19
- .replace("****", "**\n\n**") # Remove extra newlines after bold titles
20
- )
17
+ text = content.rstrip()
18
+
19
+ # Weird case of Gemini 3
20
+ text = text.replace("\\n\\n\n\n", "")
21
+
22
+ # Fix OpenRouter OpenAI reasoning formatting where segments like
23
+ # "text**Title**\n\n" lose the blank line between segments.
24
+ # We want: "text\n**Title**\n" so that each bold title starts on
25
+ # its own line and uses a single trailing newline.
26
+ text = re.sub(r"([^\n])(\*\*[^*]+?\*\*)\n\n", r"\1 \n\n\2 \n", text)
27
+
28
+ # Remove extra newlines between back-to-back bold titles, eg
29
+ # "**Title1****Title2**" -> "**Title1**\n\n**Title2**".
30
+ text = text.replace("****", "**\n\n**")
31
+
32
+ # Compact double-newline after bold so the body text follows
33
+ # directly after the title line, using a markdown line break.
34
+ text = text.replace("**\n\n", "** \n")
35
+
36
+ return text
21
37
 
22
38
 
23
39
  def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableType | None:
@@ -31,7 +47,7 @@ def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableT
31
47
 
32
48
  return Padding.indent(
33
49
  ThinkingMarkdown(
34
- _normalize_thinking_content(content),
50
+ normalize_thinking_content(content),
35
51
  code_theme=code_theme,
36
52
  style=style,
37
53
  ),
@@ -2,7 +2,7 @@ import json
2
2
  from pathlib import Path
3
3
  from typing import Any, cast
4
4
 
5
- from rich.console import RenderableType
5
+ from rich.console import Group, RenderableType
6
6
  from rich.padding import Padding
7
7
  from rich.text import Text
8
8
 
@@ -186,14 +186,7 @@ def render_write_tool_call(arguments: str) -> RenderableType:
186
186
  try:
187
187
  json_dict = json.loads(arguments)
188
188
  file_path = json_dict.get("file_path")
189
- op_label = "Create"
190
- if isinstance(file_path, str):
191
- abs_path = Path(file_path)
192
- if not abs_path.is_absolute():
193
- abs_path = (Path().cwd() / abs_path).resolve()
194
- if abs_path.exists():
195
- op_label = "Overwrite"
196
- tool_name_column = Text.assemble(("→", ThemeKey.TOOL_MARK), " ", (op_label, ThemeKey.TOOL_NAME))
189
+ tool_name_column = Text.assemble(("", ThemeKey.TOOL_MARK), " ", ("Write", ThemeKey.TOOL_NAME))
197
190
  arguments_column = render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
198
191
  except json.JSONDecodeError:
199
192
  tool_name_column = Text.assemble(("→", ThemeKey.TOOL_MARK), " ", ("Write", ThemeKey.TOOL_NAME))
@@ -386,6 +379,75 @@ def render_mermaid_tool_call(arguments: str) -> RenderableType:
386
379
  return grid
387
380
 
388
381
 
382
+ def _truncate_url(url: str, max_length: int = 400) -> str:
383
+ """Truncate URL for display, preserving domain and path structure."""
384
+ if len(url) <= max_length:
385
+ return url
386
+ # Remove protocol for display
387
+ display_url = url
388
+ for prefix in ("https://", "http://"):
389
+ if display_url.startswith(prefix):
390
+ display_url = display_url[len(prefix) :]
391
+ break
392
+ if len(display_url) <= max_length:
393
+ return display_url
394
+ # Truncate with ellipsis
395
+ return display_url[: max_length - 3] + "..."
396
+
397
+
398
+ def render_web_fetch_tool_call(arguments: str) -> RenderableType:
399
+ grid = create_grid()
400
+ tool_name_column = Text.assemble(("↓", ThemeKey.TOOL_MARK), " ", ("Fetch", ThemeKey.TOOL_NAME))
401
+
402
+ try:
403
+ payload: dict[str, str] = json.loads(arguments)
404
+ except json.JSONDecodeError:
405
+ summary = Text(
406
+ arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
407
+ style=ThemeKey.INVALID_TOOL_CALL_ARGS,
408
+ )
409
+ grid.add_row(tool_name_column, summary)
410
+ return grid
411
+
412
+ url = payload.get("url", "")
413
+ summary = Text(_truncate_url(url), ThemeKey.TOOL_PARAM_FILE_PATH) if url else Text("(no url)", ThemeKey.TOOL_PARAM)
414
+
415
+ grid.add_row(tool_name_column, summary)
416
+ return grid
417
+
418
+
419
+ def render_web_search_tool_call(arguments: str) -> RenderableType:
420
+ grid = create_grid()
421
+ tool_name_column = Text.assemble(("◉", ThemeKey.TOOL_MARK), " ", ("Search", ThemeKey.TOOL_NAME))
422
+
423
+ try:
424
+ payload: dict[str, Any] = json.loads(arguments)
425
+ except json.JSONDecodeError:
426
+ summary = Text(
427
+ arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
428
+ style=ThemeKey.INVALID_TOOL_CALL_ARGS,
429
+ )
430
+ grid.add_row(tool_name_column, summary)
431
+ return grid
432
+
433
+ query = payload.get("query", "")
434
+ max_results = payload.get("max_results")
435
+
436
+ summary = Text("", ThemeKey.TOOL_PARAM)
437
+ if query:
438
+ # Truncate long queries
439
+ display_query = query if len(query) <= 80 else query[:77] + "..."
440
+ summary.append(display_query, ThemeKey.TOOL_PARAM)
441
+ else:
442
+ summary.append("(no query)", ThemeKey.TOOL_PARAM)
443
+
444
+ if isinstance(max_results, int) and max_results != 10:
445
+ summary.append(f" (max {max_results})", ThemeKey.TOOL_TIMEOUT)
446
+
447
+ grid.add_row(tool_name_column, summary)
448
+ return grid
449
+
450
+
389
451
  def render_mermaid_tool_result(tr: events.ToolResultEvent) -> RenderableType:
390
452
  from klaude_code.ui.terminal import supports_osc8_hyperlinks
391
453
 
@@ -416,16 +478,12 @@ def _extract_truncation(
416
478
 
417
479
  def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
418
480
  """Render truncation info for the user."""
419
- original_kb = ui_extra.original_length / 1024
420
481
  truncated_kb = ui_extra.truncated_length / 1024
482
+
421
483
  text = Text.assemble(
422
- ("Output truncated: ", ThemeKey.TOOL_RESULT),
423
- (f"{original_kb:.1f}KB", ThemeKey.TOOL_RESULT),
424
- (" total, ", ThemeKey.TOOL_RESULT),
425
- (f"{truncated_kb:.1f}KB", ThemeKey.TOOL_RESULT_BOLD),
426
- (" hidden\nFull output saved to ", ThemeKey.TOOL_RESULT),
427
- (ui_extra.saved_file_path, ThemeKey.TOOL_RESULT),
428
- ("\nUse Read with limit+offset or rg/grep to inspect", ThemeKey.TOOL_RESULT),
484
+ ("Offload context to ", ThemeKey.TOOL_RESULT_TRUNCATED),
485
+ (ui_extra.saved_file_path, ThemeKey.TOOL_RESULT_TRUNCATED),
486
+ (f", {truncated_kb:.1f}KB truncated", ThemeKey.TOOL_RESULT_TRUNCATED),
429
487
  )
430
488
  return Padding.indent(text, level=2)
431
489
 
@@ -455,8 +513,9 @@ _TOOL_ACTIVE_FORM: dict[str, str] = {
455
513
  tools.SKILL: "Skilling",
456
514
  tools.MERMAID: "Diagramming",
457
515
  tools.MEMORY: "Memorizing",
458
- tools.WEB_FETCH: "Fetching",
459
- tools.REPORT_BACK: "Submitting Report",
516
+ tools.WEB_FETCH: "Fetching Web",
517
+ tools.WEB_SEARCH: "Searching Web",
518
+ tools.REPORT_BACK: "Reporting",
460
519
  }
461
520
 
462
521
 
@@ -512,6 +571,10 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
512
571
  return render_generic_tool_call(e.tool_name, e.arguments, "◈")
513
572
  case tools.REPORT_BACK:
514
573
  return render_report_back_tool_call()
574
+ case tools.WEB_FETCH:
575
+ return render_web_fetch_tool_call(e.arguments)
576
+ case tools.WEB_SEARCH:
577
+ return render_web_search_tool_call(e.arguments)
515
578
  case _:
516
579
  return render_generic_tool_call(e.tool_name, e.arguments)
517
580
 
@@ -540,7 +603,7 @@ def render_tool_result(e: events.ToolResultEvent) -> RenderableType | None:
540
603
  # Show truncation info if output was truncated and saved to file
541
604
  truncation_info = get_truncation_info(e)
542
605
  if truncation_info:
543
- return render_truncation_info(truncation_info)
606
+ return Group(render_truncation_info(truncation_info), render_generic_tool_result(e.result))
544
607
 
545
608
  diff_text = _extract_diff_text(e.ui_extra)
546
609
 
@@ -0,0 +1,112 @@
1
+ """A panel that only has top and bottom borders, no left/right borders or padding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from rich.console import ConsoleRenderable, RichCast
8
+ from rich.jupyter import JupyterMixin
9
+ from rich.measure import Measurement, measure_renderables
10
+ from rich.segment import Segment
11
+ from rich.style import StyleType
12
+
13
+ if TYPE_CHECKING:
14
+ from rich.console import Console, ConsoleOptions, RenderResult
15
+
16
+ # Box drawing characters
17
+ TOP_LEFT = "┌" # ┌
18
+ TOP_RIGHT = "┐" # ┐
19
+ BOTTOM_LEFT = "└" # └
20
+ BOTTOM_RIGHT = "┘" # ┘
21
+ HORIZONTAL = "─" # ─
22
+
23
+
24
+ class CodePanel(JupyterMixin):
25
+ """A panel with only top and bottom borders, no left/right borders.
26
+
27
+ This is designed for code blocks where you want easy copy-paste without
28
+ picking up border characters on the sides.
29
+
30
+ Example:
31
+ >>> console.print(CodePanel(Syntax(code, "python")))
32
+
33
+ Renders as:
34
+ ┌──────────────────────────┐
35
+ code line 1
36
+ code line 2
37
+ └──────────────────────────┘
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ renderable: ConsoleRenderable | RichCast | str,
43
+ *,
44
+ border_style: StyleType = "none",
45
+ expand: bool = False,
46
+ padding: int = 1,
47
+ ) -> None:
48
+ """Initialize the CodePanel.
49
+
50
+ Args:
51
+ renderable: A console renderable object.
52
+ border_style: The style of the border. Defaults to "none".
53
+ expand: If True, expand to fill available width. Defaults to False.
54
+ padding: Left/right padding for content. Defaults to 1.
55
+ """
56
+ self.renderable = renderable
57
+ self.border_style = border_style
58
+ self.expand = expand
59
+ self.padding = padding
60
+
61
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
62
+ border_style = console.get_style(self.border_style)
63
+ max_width = options.max_width
64
+ pad = self.padding
65
+
66
+ # Measure the content width (account for padding)
67
+ if self.expand:
68
+ content_width = max_width - pad * 2
69
+ else:
70
+ content_width = console.measure(self.renderable, options=options.update(width=max_width - pad * 2)).maximum
71
+ content_width = min(content_width, max_width - pad * 2)
72
+
73
+ # Render content lines
74
+ child_options = options.update(width=content_width)
75
+ lines = console.render_lines(self.renderable, child_options)
76
+
77
+ # Calculate border width based on content width + padding
78
+ border_width = content_width + pad * 2
79
+
80
+ new_line = Segment.line()
81
+ pad_segment = Segment(" " * pad) if pad > 0 else None
82
+
83
+ # Top border: ┌───...───┐
84
+ top_border = (
85
+ TOP_LEFT + (HORIZONTAL * (border_width - 2)) + TOP_RIGHT if border_width >= 2 else HORIZONTAL * border_width
86
+ )
87
+ yield Segment(top_border, border_style)
88
+ yield new_line
89
+
90
+ # Content lines with padding
91
+ for line in lines:
92
+ if pad_segment:
93
+ yield pad_segment
94
+ yield from line
95
+ if pad_segment:
96
+ yield pad_segment
97
+ yield new_line
98
+
99
+ # Bottom border: └───...───┘
100
+ bottom_border = (
101
+ BOTTOM_LEFT + (HORIZONTAL * (border_width - 2)) + BOTTOM_RIGHT
102
+ if border_width >= 2
103
+ else HORIZONTAL * border_width
104
+ )
105
+ yield Segment(bottom_border, border_style)
106
+ yield new_line
107
+
108
+ def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
109
+ if self.expand:
110
+ return Measurement(options.max_width, options.max_width)
111
+ width = measure_renderables(console, options, [self.renderable]).maximum + self.padding * 2
112
+ return Measurement(width, width)
@@ -7,11 +7,9 @@ import time
7
7
  from collections.abc import Callable
8
8
  from typing import Any, ClassVar
9
9
 
10
- from rich import box
11
10
  from rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
12
11
  from rich.live import Live
13
12
  from rich.markdown import CodeBlock, Heading, Markdown
14
- from rich.panel import Panel
15
13
  from rich.rule import Rule
16
14
  from rich.spinner import Spinner
17
15
  from rich.style import Style
@@ -20,6 +18,7 @@ from rich.text import Text
20
18
  from rich.theme import Theme
21
19
 
22
20
  from klaude_code import const
21
+ from klaude_code.ui.rich.code_panel import CodePanel
23
22
 
24
23
 
25
24
  class NoInsetCodeBlock(CodeBlock):
@@ -34,7 +33,7 @@ class NoInsetCodeBlock(CodeBlock):
34
33
  word_wrap=True,
35
34
  padding=(0, 0),
36
35
  )
37
- yield Panel.fit(syntax, padding=(0, 0), box=box.HORIZONTALS, border_style="markdown.code.border")
36
+ yield CodePanel(syntax, border_style="markdown.code.border")
38
37
 
39
38
 
40
39
  class ThinkingCodeBlock(CodeBlock):
@@ -43,7 +42,7 @@ class ThinkingCodeBlock(CodeBlock):
43
42
  def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
44
43
  code = str(self.text).rstrip()
45
44
  text = Text(code, "markdown.code.block")
46
- yield text
45
+ yield CodePanel(text, border_style="markdown.code.border")
47
46
 
48
47
 
49
48
  class LeftHeading(Heading):
@@ -21,15 +21,20 @@ from klaude_code.ui.terminal.color import get_last_terminal_background_rgb
21
21
  BREATHING_SPINNER_NAME = "dots"
22
22
 
23
23
  # Alternating glyphs for the breathing spinner - switches at each "transparent" point
24
- # All glyphs are center-symmetric (point-symmetric)
25
24
  _BREATHING_SPINNER_GLYPHS_BASE = [
26
- # Stars
27
25
  "✦",
28
26
  "✶",
29
27
  "✲",
30
- "⏺",
31
28
  "◆",
32
29
  "❖",
30
+ "✧",
31
+ "❋",
32
+ "✸",
33
+ "✻",
34
+ "◇",
35
+ "✴",
36
+ "✷",
37
+ "⟡",
33
38
  ]
34
39
 
35
40
  # Shuffle glyphs on module load for variety across sessions
@@ -172,15 +177,34 @@ def _breathing_style(console: Console, base_style: Style, intensity: float) -> S
172
177
 
173
178
 
174
179
  class ShimmerStatusText:
175
- """Renderable status line with shimmer effect on the main text and hint."""
180
+ """Renderable status line with shimmer effect on the main text and hint.
176
181
 
177
- def __init__(self, main_text: str | Text, main_style: ThemeKey) -> None:
182
+ Supports optional right-aligned text that stays fixed at the right edge.
183
+ """
184
+
185
+ def __init__(self, main_text: str | Text, main_style: ThemeKey, right_text: Text | None = None) -> None:
178
186
  self._main_text = main_text if isinstance(main_text, Text) else Text(main_text)
179
187
  self._main_style = main_style
180
188
  self._hint_text = Text(const.STATUS_HINT_TEXT)
181
189
  self._hint_style = ThemeKey.STATUS_HINT
190
+ self._right_text = right_text
182
191
 
183
192
  def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
193
+ left_text = self._render_left_text(console)
194
+
195
+ if self._right_text is None:
196
+ yield left_text
197
+ return
198
+
199
+ # Use Table.grid to create left-right aligned layout
200
+ table = Table.grid(expand=True)
201
+ table.add_column(justify="left", ratio=1)
202
+ table.add_column(justify="right")
203
+ table.add_row(left_text, self._right_text)
204
+ yield table
205
+
206
+ def _render_left_text(self, console: Console) -> Text:
207
+ """Render the left part with shimmer effect."""
184
208
  result = Text()
185
209
  main_style = console.get_style(str(self._main_style))
186
210
  hint_style = console.get_style(str(self._hint_style))
@@ -198,7 +222,7 @@ class ShimmerStatusText:
198
222
  style = _shimmer_style(console, base_style, intensity)
199
223
  result.append(ch, style=style)
200
224
 
201
- yield result
225
+ return result
202
226
 
203
227
 
204
228
  def spinner_name() -> str:
@@ -86,6 +86,7 @@ class ThemeKey(str, Enum):
86
86
  # SPINNER_STATUS
87
87
  SPINNER_STATUS = "spinner.status"
88
88
  SPINNER_STATUS_TEXT = "spinner.status.text"
89
+ SPINNER_STATUS_TEXT_BOLD = "spinner.status.text.bold"
89
90
  # STATUS
90
91
  STATUS_HINT = "status.hint"
91
92
  # USER_INPUT
@@ -103,6 +104,7 @@ class ThemeKey(str, Enum):
103
104
  TOOL_PARAM = "tool.param"
104
105
  TOOL_PARAM_BOLD = "tool.param.bold"
105
106
  TOOL_RESULT = "tool.result"
107
+ TOOL_RESULT_TRUNCATED = "tool.result.truncated"
106
108
  TOOL_RESULT_BOLD = "tool.result.bold"
107
109
  TOOL_MARK = "tool.mark"
108
110
  TOOL_APPROVED = "tool.approved"
@@ -181,6 +183,7 @@ def get_theme(theme: str | None = None) -> Themes:
181
183
  # SPINNER_STATUS
182
184
  ThemeKey.SPINNER_STATUS.value: palette.blue,
183
185
  ThemeKey.SPINNER_STATUS_TEXT.value: palette.blue,
186
+ ThemeKey.SPINNER_STATUS_TEXT_BOLD.value: "bold " + palette.blue,
184
187
  # STATUS
185
188
  ThemeKey.STATUS_HINT.value: palette.grey2,
186
189
  # REMINDER
@@ -194,6 +197,7 @@ def get_theme(theme: str | None = None) -> Themes:
194
197
  ThemeKey.TOOL_PARAM_BOLD.value: "bold " + palette.green,
195
198
  ThemeKey.TOOL_RESULT.value: palette.grey_green,
196
199
  ThemeKey.TOOL_RESULT_BOLD.value: "bold " + palette.grey_green,
200
+ ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.yellow,
197
201
  ThemeKey.TOOL_MARK.value: "bold",
198
202
  ThemeKey.TOOL_APPROVED.value: palette.green + " bold reverse",
199
203
  ThemeKey.TOOL_REJECTED.value: palette.red + " bold reverse",
@@ -243,11 +247,15 @@ def get_theme(theme: str | None = None) -> Themes:
243
247
  "markdown.hr": palette.grey3,
244
248
  "markdown.item.bullet": palette.grey2,
245
249
  "markdown.item.number": palette.grey2,
250
+ "markdown.link": "underline " + palette.blue,
251
+ "markdown.link_url": "underline " + palette.blue,
246
252
  }
247
253
  ),
248
254
  thinking_markdown_theme=Theme(
249
255
  styles={
250
256
  "markdown.code": palette.grey1 + " italic on " + palette.text_background,
257
+ "markdown.code.block": palette.grey1,
258
+ "markdown.code.border": palette.grey3,
251
259
  "markdown.h1": "bold reverse",
252
260
  "markdown.h1.border": palette.grey3,
253
261
  "markdown.h2.border": palette.grey3,
@@ -256,7 +264,8 @@ def get_theme(theme: str | None = None) -> Themes:
256
264
  "markdown.hr": palette.grey3,
257
265
  "markdown.item.bullet": palette.grey2,
258
266
  "markdown.item.number": palette.grey2,
259
- "markdown.code.block": palette.grey1,
267
+ "markdown.link": "underline " + palette.blue,
268
+ "markdown.link_url": "underline " + palette.blue,
260
269
  "markdown.strong": "bold italic " + palette.grey1,
261
270
  }
262
271
  ),