klaude-code 1.2.23__py3-none-any.whl → 1.2.24__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.
- klaude_code/command/prompt-jj-describe.md +32 -0
- klaude_code/{const/__init__.py → const.py} +4 -1
- klaude_code/core/executor.py +1 -1
- klaude_code/core/tool/file/read_tool.py +23 -1
- klaude_code/core/tool/file/write_tool.py +7 -3
- klaude_code/llm/openai_compatible/client.py +29 -102
- klaude_code/llm/openai_compatible/stream.py +272 -0
- klaude_code/llm/openrouter/client.py +29 -109
- klaude_code/llm/openrouter/{reasoning_handler.py → reasoning.py} +24 -2
- klaude_code/protocol/model.py +13 -1
- klaude_code/ui/core/stage_manager.py +0 -3
- klaude_code/ui/modes/repl/event_handler.py +94 -46
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +13 -3
- klaude_code/ui/modes/repl/renderer.py +24 -17
- klaude_code/ui/renderers/assistant.py +1 -1
- klaude_code/ui/renderers/metadata.py +2 -6
- klaude_code/ui/renderers/sub_agent.py +28 -5
- klaude_code/ui/renderers/thinking.py +16 -10
- klaude_code/ui/renderers/tools.py +26 -2
- klaude_code/ui/rich/markdown.py +18 -3
- klaude_code/ui/rich/status.py +14 -6
- klaude_code/ui/rich/theme.py +63 -12
- {klaude_code-1.2.23.dist-info → klaude_code-1.2.24.dist-info}/METADATA +1 -1
- {klaude_code-1.2.23.dist-info → klaude_code-1.2.24.dist-info}/RECORD +26 -25
- klaude_code/llm/openai_compatible/stream_processor.py +0 -83
- {klaude_code-1.2.23.dist-info → klaude_code-1.2.24.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.23.dist-info → klaude_code-1.2.24.dist-info}/entry_points.txt +0 -0
|
@@ -5,14 +5,13 @@ from contextlib import contextmanager
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from rich import box
|
|
9
|
-
from rich.box import Box
|
|
10
8
|
from rich.console import Console
|
|
11
9
|
from rich.spinner import Spinner
|
|
12
10
|
from rich.status import Status
|
|
13
11
|
from rich.style import Style, StyleType
|
|
14
12
|
from rich.text import Text
|
|
15
13
|
|
|
14
|
+
from klaude_code import const
|
|
16
15
|
from klaude_code.protocol import events, model
|
|
17
16
|
from klaude_code.ui.renderers import assistant as r_assistant
|
|
18
17
|
from klaude_code.ui.renderers import developer as r_developer
|
|
@@ -32,6 +31,7 @@ from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
|
32
31
|
@dataclass
|
|
33
32
|
class SessionStatus:
|
|
34
33
|
color: Style | None = None
|
|
34
|
+
color_index: int | None = None
|
|
35
35
|
sub_agent_state: model.SubAgentState | None = None
|
|
36
36
|
|
|
37
37
|
|
|
@@ -43,9 +43,9 @@ class REPLRenderer:
|
|
|
43
43
|
self.console: Console = Console(theme=self.themes.app_theme)
|
|
44
44
|
self.console.push_theme(self.themes.markdown_theme)
|
|
45
45
|
self._spinner: Status = self.console.status(
|
|
46
|
-
ShimmerStatusText(
|
|
46
|
+
ShimmerStatusText(const.STATUS_DEFAULT_TEXT),
|
|
47
47
|
spinner=r_status.spinner_name(),
|
|
48
|
-
spinner_style=ThemeKey.
|
|
48
|
+
spinner_style=ThemeKey.STATUS_SPINNER,
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
self.session_map: dict[str, SessionStatus] = {}
|
|
@@ -57,7 +57,9 @@ class REPLRenderer:
|
|
|
57
57
|
sub_agent_state=sub_agent_state,
|
|
58
58
|
)
|
|
59
59
|
if sub_agent_state is not None:
|
|
60
|
-
|
|
60
|
+
color, color_index = self.pick_sub_agent_color()
|
|
61
|
+
session_status.color = color
|
|
62
|
+
session_status.color_index = color_index
|
|
61
63
|
self.session_map[session_id] = session_status
|
|
62
64
|
|
|
63
65
|
def is_sub_agent_session(self, session_id: str) -> bool:
|
|
@@ -70,12 +72,12 @@ class REPLRenderer:
|
|
|
70
72
|
return
|
|
71
73
|
self.sub_agent_color_index = (self.sub_agent_color_index + 1) % palette_size
|
|
72
74
|
|
|
73
|
-
def pick_sub_agent_color(self) -> Style:
|
|
75
|
+
def pick_sub_agent_color(self) -> tuple[Style, int]:
|
|
74
76
|
self._advance_sub_agent_color_index()
|
|
75
77
|
palette = self.themes.sub_agent_colors
|
|
76
78
|
if not palette:
|
|
77
|
-
return Style()
|
|
78
|
-
return palette[self.sub_agent_color_index]
|
|
79
|
+
return Style(), 0
|
|
80
|
+
return palette[self.sub_agent_color_index], self.sub_agent_color_index
|
|
79
81
|
|
|
80
82
|
def get_session_sub_agent_color(self, session_id: str) -> Style:
|
|
81
83
|
status = self.session_map.get(session_id)
|
|
@@ -83,8 +85,12 @@ class REPLRenderer:
|
|
|
83
85
|
return status.color
|
|
84
86
|
return Style()
|
|
85
87
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
+
def get_session_sub_agent_background(self, session_id: str) -> Style:
|
|
89
|
+
status = self.session_map.get(session_id)
|
|
90
|
+
backgrounds = self.themes.sub_agent_backgrounds
|
|
91
|
+
if status and status.color_index is not None and backgrounds:
|
|
92
|
+
return backgrounds[status.color_index]
|
|
93
|
+
return Style()
|
|
88
94
|
|
|
89
95
|
@contextmanager
|
|
90
96
|
def session_print_context(self, session_id: str) -> Iterator[None]:
|
|
@@ -114,7 +120,7 @@ class REPLRenderer:
|
|
|
114
120
|
def display_tool_call_result(self, e: events.ToolResultEvent) -> None:
|
|
115
121
|
if r_tools.is_sub_agent_tool(e.tool_name):
|
|
116
122
|
return
|
|
117
|
-
renderable = r_tools.render_tool_result(e)
|
|
123
|
+
renderable = r_tools.render_tool_result(e, code_theme=self.themes.code_theme)
|
|
118
124
|
if renderable is not None:
|
|
119
125
|
self.print(renderable)
|
|
120
126
|
|
|
@@ -152,7 +158,6 @@ class REPLRenderer:
|
|
|
152
158
|
case events.ThinkingEvent() as e:
|
|
153
159
|
if is_sub_agent:
|
|
154
160
|
continue
|
|
155
|
-
self.display_thinking_prefix()
|
|
156
161
|
self.display_thinking(e.content)
|
|
157
162
|
case events.DeveloperMessageEvent() as e:
|
|
158
163
|
self.display_developer_message(e)
|
|
@@ -196,7 +201,7 @@ class REPLRenderer:
|
|
|
196
201
|
self.print()
|
|
197
202
|
|
|
198
203
|
def display_welcome(self, event: events.WelcomeEvent) -> None:
|
|
199
|
-
self.print(r_metadata.render_welcome(event
|
|
204
|
+
self.print(r_metadata.render_welcome(event))
|
|
200
205
|
|
|
201
206
|
def display_user_message(self, event: events.UserMessageEvent) -> None:
|
|
202
207
|
self.print(r_user_input.render_user_input(event.content))
|
|
@@ -229,12 +234,17 @@ class REPLRenderer:
|
|
|
229
234
|
|
|
230
235
|
def display_task_finish(self, event: events.TaskFinishEvent) -> None:
|
|
231
236
|
if self.is_sub_agent_session(event.session_id):
|
|
237
|
+
session_status = self.session_map.get(event.session_id)
|
|
238
|
+
description = session_status.sub_agent_state.sub_agent_desc if session_status and session_status.sub_agent_state else None
|
|
239
|
+
panel_style = self.get_session_sub_agent_background(event.session_id)
|
|
232
240
|
with self.session_print_context(event.session_id):
|
|
233
241
|
self.print(
|
|
234
242
|
r_sub_agent.render_sub_agent_result(
|
|
235
243
|
event.task_result,
|
|
236
244
|
code_theme=self.themes.code_theme,
|
|
237
245
|
has_structured_output=event.has_structured_output,
|
|
246
|
+
description=description,
|
|
247
|
+
panel_style=panel_style,
|
|
238
248
|
)
|
|
239
249
|
)
|
|
240
250
|
|
|
@@ -249,9 +259,6 @@ class REPLRenderer:
|
|
|
249
259
|
)
|
|
250
260
|
)
|
|
251
261
|
|
|
252
|
-
def display_thinking_prefix(self) -> None:
|
|
253
|
-
self.print(r_thinking.thinking_prefix())
|
|
254
|
-
|
|
255
262
|
# -------------------------------------------------------------------------
|
|
256
263
|
# Spinner control methods
|
|
257
264
|
# -------------------------------------------------------------------------
|
|
@@ -266,7 +273,7 @@ class REPLRenderer:
|
|
|
266
273
|
|
|
267
274
|
def spinner_update(self, status_text: str | Text, right_text: Text | None = None) -> None:
|
|
268
275
|
"""Update the spinner status text with optional right-aligned text."""
|
|
269
|
-
self._spinner.update(ShimmerStatusText(status_text,
|
|
276
|
+
self._spinner.update(ShimmerStatusText(status_text, right_text))
|
|
270
277
|
|
|
271
278
|
def spinner_renderable(self) -> Spinner:
|
|
272
279
|
"""Return the spinner's renderable for embedding in other components."""
|
|
@@ -6,7 +6,7 @@ from klaude_code.ui.renderers.common import create_grid
|
|
|
6
6
|
from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
7
7
|
|
|
8
8
|
# UI markers
|
|
9
|
-
ASSISTANT_MESSAGE_MARK = "
|
|
9
|
+
ASSISTANT_MESSAGE_MARK = "◆"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def render_assistant_message(content: str, *, code_theme: str) -> RenderableType | None:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from importlib.metadata import version
|
|
2
2
|
|
|
3
3
|
from rich import box
|
|
4
|
-
from rich.box import Box
|
|
5
4
|
from rich.console import Group, RenderableType
|
|
6
5
|
from rich.padding import Padding
|
|
7
6
|
from rich.panel import Panel
|
|
@@ -165,11 +164,8 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
|
165
164
|
return Group(*renderables)
|
|
166
165
|
|
|
167
166
|
|
|
168
|
-
def render_welcome(e: events.WelcomeEvent
|
|
167
|
+
def render_welcome(e: events.WelcomeEvent) -> RenderableType:
|
|
169
168
|
"""Render the welcome panel with model info and settings."""
|
|
170
|
-
if box_style is None:
|
|
171
|
-
box_style = box.ROUNDED
|
|
172
|
-
|
|
173
169
|
debug_mode = is_debug_enabled()
|
|
174
170
|
|
|
175
171
|
# First line: Klaude Code version
|
|
@@ -219,6 +215,6 @@ def render_welcome(e: events.WelcomeEvent, *, box_style: Box | None = None) -> R
|
|
|
219
215
|
|
|
220
216
|
border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
|
|
221
217
|
return Group(
|
|
222
|
-
Panel.fit(panel_content, border_style=border_style, box=
|
|
218
|
+
Panel.fit(panel_content, border_style=border_style, box=box.ROUNDED),
|
|
223
219
|
"", # empty line
|
|
224
220
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Any, cast
|
|
3
3
|
|
|
4
|
+
from rich import box
|
|
4
5
|
from rich.console import Group, RenderableType
|
|
5
6
|
from rich.json import JSON
|
|
6
7
|
from rich.panel import Panel
|
|
@@ -58,10 +59,22 @@ def render_sub_agent_call(e: model.SubAgentState, style: Style | None = None) ->
|
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
def render_sub_agent_result(
|
|
61
|
-
result: str,
|
|
62
|
+
result: str,
|
|
63
|
+
*,
|
|
64
|
+
code_theme: str,
|
|
65
|
+
style: Style | None = None,
|
|
66
|
+
has_structured_output: bool = False,
|
|
67
|
+
description: str | None = None,
|
|
68
|
+
panel_style: Style | None = None,
|
|
62
69
|
) -> RenderableType:
|
|
63
70
|
stripped_result = result.strip()
|
|
64
71
|
|
|
72
|
+
# Add markdown heading if description is provided
|
|
73
|
+
if description:
|
|
74
|
+
stripped_result = f"# {description}\n\n{stripped_result}"
|
|
75
|
+
|
|
76
|
+
result_panel_style = panel_style or ThemeKey.SUB_AGENT_RESULT_PANEL
|
|
77
|
+
|
|
65
78
|
# Use rich JSON for structured output
|
|
66
79
|
if has_structured_output:
|
|
67
80
|
try:
|
|
@@ -73,7 +86,9 @@ def render_sub_agent_result(
|
|
|
73
86
|
),
|
|
74
87
|
JSON(stripped_result),
|
|
75
88
|
),
|
|
89
|
+
box=box.SIMPLE,
|
|
76
90
|
border_style=ThemeKey.LINES,
|
|
91
|
+
style=result_panel_style,
|
|
77
92
|
)
|
|
78
93
|
except json.JSONDecodeError:
|
|
79
94
|
# Fall back to markdown if not valid JSON
|
|
@@ -82,20 +97,28 @@ def render_sub_agent_result(
|
|
|
82
97
|
lines = stripped_result.splitlines()
|
|
83
98
|
if len(lines) > const.SUB_AGENT_RESULT_MAX_LINES:
|
|
84
99
|
hidden_count = len(lines) - const.SUB_AGENT_RESULT_MAX_LINES
|
|
85
|
-
|
|
100
|
+
head_count = const.SUB_AGENT_RESULT_MAX_LINES // 2
|
|
101
|
+
tail_count = const.SUB_AGENT_RESULT_MAX_LINES - head_count
|
|
102
|
+
head_text = "\n".join(lines[:head_count])
|
|
103
|
+
tail_text = "\n".join(lines[-tail_count:])
|
|
86
104
|
return Panel.fit(
|
|
87
105
|
Group(
|
|
106
|
+
NoInsetMarkdown(head_text, code_theme=code_theme, style=style or ""),
|
|
88
107
|
Text(
|
|
89
|
-
f"… more {hidden_count} lines — use /export to view full output",
|
|
90
|
-
style=ThemeKey.
|
|
108
|
+
f"\n… more {hidden_count} lines — use /export to view full output\n",
|
|
109
|
+
style=ThemeKey.TOOL_RESULT_TRUNCATED,
|
|
91
110
|
),
|
|
92
|
-
NoInsetMarkdown(
|
|
111
|
+
NoInsetMarkdown(tail_text, code_theme=code_theme, style=style or ""),
|
|
93
112
|
),
|
|
113
|
+
box=box.SIMPLE,
|
|
94
114
|
border_style=ThemeKey.LINES,
|
|
115
|
+
style=result_panel_style,
|
|
95
116
|
)
|
|
96
117
|
return Panel.fit(
|
|
97
118
|
NoInsetMarkdown(stripped_result, code_theme=code_theme),
|
|
119
|
+
box=box.SIMPLE,
|
|
98
120
|
border_style=ThemeKey.LINES,
|
|
121
|
+
style=result_panel_style,
|
|
99
122
|
)
|
|
100
123
|
|
|
101
124
|
|
|
@@ -4,12 +4,13 @@ from rich.console import RenderableType
|
|
|
4
4
|
from rich.padding import Padding
|
|
5
5
|
from rich.text import Text
|
|
6
6
|
|
|
7
|
+
from klaude_code import const
|
|
8
|
+
from klaude_code.ui.renderers.common import create_grid
|
|
7
9
|
from klaude_code.ui.rich.markdown import ThinkingMarkdown
|
|
8
10
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return Text.from_markup("[not italic]⸫[/not italic] Thinking …", style=ThemeKey.THINKING_BOLD)
|
|
12
|
+
# UI markers
|
|
13
|
+
THINKING_MESSAGE_MARK = "⠶"
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def normalize_thinking_content(content: str) -> str:
|
|
@@ -37,7 +38,7 @@ def normalize_thinking_content(content: str) -> str:
|
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableType | None:
|
|
40
|
-
"""Render thinking content as
|
|
41
|
+
"""Render thinking content as markdown with left mark.
|
|
41
42
|
|
|
42
43
|
Returns None if content is empty.
|
|
43
44
|
Note: Caller should push thinking_markdown_theme before printing.
|
|
@@ -45,11 +46,16 @@ def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableT
|
|
|
45
46
|
if len(content.strip()) == 0:
|
|
46
47
|
return None
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
grid = create_grid()
|
|
50
|
+
grid.add_row(
|
|
51
|
+
Text(THINKING_MESSAGE_MARK, style=ThemeKey.THINKING),
|
|
52
|
+
Padding(
|
|
53
|
+
ThinkingMarkdown(
|
|
54
|
+
normalize_thinking_content(content),
|
|
55
|
+
code_theme=code_theme,
|
|
56
|
+
style=style,
|
|
57
|
+
),
|
|
58
|
+
(0, const.MARKDOWN_RIGHT_MARGIN, 0, 0),
|
|
53
59
|
),
|
|
54
|
-
level=2,
|
|
55
60
|
)
|
|
61
|
+
return grid
|
|
@@ -2,8 +2,10 @@ import json
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Any, cast
|
|
4
4
|
|
|
5
|
+
from rich import box
|
|
5
6
|
from rich.console import Group, RenderableType
|
|
6
7
|
from rich.padding import Padding
|
|
8
|
+
from rich.panel import Panel
|
|
7
9
|
from rich.text import Text
|
|
8
10
|
|
|
9
11
|
from klaude_code import const
|
|
@@ -11,6 +13,7 @@ from klaude_code.protocol import events, model, tools
|
|
|
11
13
|
from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
|
|
12
14
|
from klaude_code.ui.renderers import diffs as r_diffs
|
|
13
15
|
from klaude_code.ui.renderers.common import create_grid, truncate_display
|
|
16
|
+
from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
14
17
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
15
18
|
|
|
16
19
|
# Tool markers (Unicode symbols for UI display)
|
|
@@ -528,7 +531,23 @@ def _extract_diff(ui_extra: model.ToolResultUIExtra | None) -> model.DiffUIExtra
|
|
|
528
531
|
return None
|
|
529
532
|
|
|
530
533
|
|
|
531
|
-
def
|
|
534
|
+
def _extract_markdown_doc(ui_extra: model.ToolResultUIExtra | None) -> model.MarkdownDocUIExtra | None:
|
|
535
|
+
if isinstance(ui_extra, model.MarkdownDocUIExtra):
|
|
536
|
+
return ui_extra
|
|
537
|
+
return None
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def render_markdown_doc(md_ui: model.MarkdownDocUIExtra, *, code_theme: str) -> RenderableType:
|
|
541
|
+
"""Render markdown document content in a panel."""
|
|
542
|
+
return Panel.fit(
|
|
543
|
+
NoInsetMarkdown(md_ui.content, code_theme=code_theme),
|
|
544
|
+
box=box.SIMPLE,
|
|
545
|
+
border_style=ThemeKey.LINES,
|
|
546
|
+
style=ThemeKey.WRITE_MARKDOWN_PANEL,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def render_tool_result(e: events.ToolResultEvent, *, code_theme: str = "monokai") -> RenderableType | None:
|
|
532
551
|
"""Unified entry point for rendering tool results.
|
|
533
552
|
|
|
534
553
|
Returns a Rich Renderable or None if the tool result should not be rendered.
|
|
@@ -549,11 +568,16 @@ def render_tool_result(e: events.ToolResultEvent) -> RenderableType | None:
|
|
|
549
568
|
return Group(render_truncation_info(truncation_info), render_generic_tool_result(e.result))
|
|
550
569
|
|
|
551
570
|
diff_ui = _extract_diff(e.ui_extra)
|
|
571
|
+
md_ui = _extract_markdown_doc(e.ui_extra)
|
|
552
572
|
|
|
553
573
|
match e.tool_name:
|
|
554
574
|
case tools.READ:
|
|
555
575
|
return None
|
|
556
|
-
case tools.EDIT
|
|
576
|
+
case tools.EDIT:
|
|
577
|
+
return Padding.indent(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""), level=2)
|
|
578
|
+
case tools.WRITE:
|
|
579
|
+
if md_ui:
|
|
580
|
+
return Padding.indent(render_markdown_doc(md_ui, code_theme=code_theme), level=2)
|
|
557
581
|
return Padding.indent(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""), level=2)
|
|
558
582
|
case tools.APPLY_PATCH:
|
|
559
583
|
if diff_ui:
|
klaude_code/ui/rich/markdown.py
CHANGED
|
@@ -12,7 +12,7 @@ from rich.live import Live
|
|
|
12
12
|
from rich.markdown import CodeBlock, Heading, Markdown, MarkdownElement
|
|
13
13
|
from rich.rule import Rule
|
|
14
14
|
from rich.spinner import Spinner
|
|
15
|
-
from rich.style import Style
|
|
15
|
+
from rich.style import Style, StyleType
|
|
16
16
|
from rich.syntax import Syntax
|
|
17
17
|
from rich.text import Text
|
|
18
18
|
from rich.theme import Theme
|
|
@@ -64,7 +64,7 @@ class LeftHeading(Heading):
|
|
|
64
64
|
yield h1_text
|
|
65
65
|
elif self.tag == "h2":
|
|
66
66
|
text.stylize(Style(bold=True, underline=False))
|
|
67
|
-
yield Rule(title=text, characters="
|
|
67
|
+
yield Rule(title=text, characters="·", style="markdown.h2.border", align="left")
|
|
68
68
|
else:
|
|
69
69
|
yield text
|
|
70
70
|
|
|
@@ -108,6 +108,7 @@ class MarkdownStream:
|
|
|
108
108
|
console: Console | None = None,
|
|
109
109
|
spinner: Spinner | None = None,
|
|
110
110
|
mark: str | None = None,
|
|
111
|
+
mark_style: StyleType | None = None,
|
|
111
112
|
left_margin: int = 0,
|
|
112
113
|
right_margin: int = const.MARKDOWN_RIGHT_MARGIN,
|
|
113
114
|
markdown_class: Callable[..., Markdown] | None = None,
|
|
@@ -119,6 +120,7 @@ class MarkdownStream:
|
|
|
119
120
|
theme (Theme, optional): Theme for rendering markdown
|
|
120
121
|
console (Console, optional): External console to use for rendering
|
|
121
122
|
mark (str | None, optional): Marker shown before the first non-empty line when left_margin >= 2
|
|
123
|
+
mark_style (StyleType | None, optional): Style to apply to the mark
|
|
122
124
|
left_margin (int, optional): Number of columns to reserve on the left side
|
|
123
125
|
right_margin (int, optional): Number of columns to reserve on the right side
|
|
124
126
|
markdown_class: Markdown class to use for rendering (defaults to NoInsetMarkdown)
|
|
@@ -142,6 +144,7 @@ class MarkdownStream:
|
|
|
142
144
|
self.console = console
|
|
143
145
|
self.spinner: Spinner | None = spinner
|
|
144
146
|
self.mark: str | None = mark
|
|
147
|
+
self.mark_style: StyleType | None = mark_style
|
|
145
148
|
|
|
146
149
|
self.left_margin: int = max(left_margin, 0)
|
|
147
150
|
|
|
@@ -194,12 +197,24 @@ class MarkdownStream:
|
|
|
194
197
|
mark_applied = False
|
|
195
198
|
use_mark = bool(self.mark) and self.left_margin >= 2
|
|
196
199
|
|
|
200
|
+
# Pre-render styled mark if needed
|
|
201
|
+
styled_mark: str | None = None
|
|
202
|
+
if use_mark and self.mark:
|
|
203
|
+
if self.mark_style:
|
|
204
|
+
mark_text = Text(self.mark, style=self.mark_style)
|
|
205
|
+
mark_buffer = io.StringIO()
|
|
206
|
+
mark_console = Console(file=mark_buffer, force_terminal=True, theme=self.theme)
|
|
207
|
+
mark_console.print(mark_text, end="")
|
|
208
|
+
styled_mark = mark_buffer.getvalue()
|
|
209
|
+
else:
|
|
210
|
+
styled_mark = self.mark
|
|
211
|
+
|
|
197
212
|
for line in lines:
|
|
198
213
|
stripped = line.rstrip()
|
|
199
214
|
|
|
200
215
|
# Apply mark to the first non-empty line only when left_margin is at least 2.
|
|
201
216
|
if use_mark and not mark_applied and stripped:
|
|
202
|
-
stripped = f"{
|
|
217
|
+
stripped = f"{styled_mark} {stripped}"
|
|
203
218
|
mark_applied = True
|
|
204
219
|
elif indent_prefix:
|
|
205
220
|
stripped = indent_prefix + stripped
|
klaude_code/ui/rich/status.py
CHANGED
|
@@ -169,9 +169,19 @@ class ShimmerStatusText:
|
|
|
169
169
|
Supports optional right-aligned text that stays fixed at the right edge.
|
|
170
170
|
"""
|
|
171
171
|
|
|
172
|
-
def __init__(
|
|
173
|
-
self
|
|
174
|
-
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
main_text: str | Text,
|
|
175
|
+
right_text: Text | None = None,
|
|
176
|
+
main_style: ThemeKey = ThemeKey.STATUS_TEXT,
|
|
177
|
+
) -> None:
|
|
178
|
+
if isinstance(main_text, Text):
|
|
179
|
+
text = main_text.copy()
|
|
180
|
+
if not text.style:
|
|
181
|
+
text.style = str(main_style)
|
|
182
|
+
self._main_text = text
|
|
183
|
+
else:
|
|
184
|
+
self._main_text = Text(main_text, style=main_style)
|
|
175
185
|
self._hint_text = Text(const.STATUS_HINT_TEXT)
|
|
176
186
|
self._hint_style = ThemeKey.STATUS_HINT
|
|
177
187
|
self._right_text = right_text
|
|
@@ -193,13 +203,11 @@ class ShimmerStatusText:
|
|
|
193
203
|
def _render_left_text(self, console: Console) -> Text:
|
|
194
204
|
"""Render the left part with shimmer effect on main text only."""
|
|
195
205
|
result = Text()
|
|
196
|
-
main_style = console.get_style(str(self._main_style))
|
|
197
206
|
hint_style = console.get_style(str(self._hint_style))
|
|
198
207
|
|
|
199
208
|
# Apply shimmer only to main text
|
|
200
209
|
for index, (ch, intensity) in enumerate(_shimmer_profile(self._main_text.plain)):
|
|
201
|
-
|
|
202
|
-
base_style = main_style + char_style
|
|
210
|
+
base_style = self._main_text.get_style_at_offset(console, index)
|
|
203
211
|
style = _shimmer_style(console, base_style, intensity)
|
|
204
212
|
result.append(ch, style=style)
|
|
205
213
|
|
klaude_code/ui/rich/theme.py
CHANGED
|
@@ -25,7 +25,18 @@ class Palette:
|
|
|
25
25
|
diff_remove: str
|
|
26
26
|
diff_remove_char: str
|
|
27
27
|
code_theme: str
|
|
28
|
-
|
|
28
|
+
code_background: str
|
|
29
|
+
green_background: str
|
|
30
|
+
blue_grey_background: str
|
|
31
|
+
# Sub-agent backgrounds (corresponding to sub_agent_colors order)
|
|
32
|
+
cyan_background: str
|
|
33
|
+
green_sub_background: str
|
|
34
|
+
blue_sub_background: str
|
|
35
|
+
purple_background: str
|
|
36
|
+
orange_background: str
|
|
37
|
+
red_background: str
|
|
38
|
+
grey_background: str
|
|
39
|
+
yellow_background: str
|
|
29
40
|
|
|
30
41
|
|
|
31
42
|
LIGHT_PALETTE = Palette(
|
|
@@ -47,7 +58,17 @@ LIGHT_PALETTE = Palette(
|
|
|
47
58
|
diff_remove="#82071e on #ffecec",
|
|
48
59
|
diff_remove_char="#82071e on #ffcfcf",
|
|
49
60
|
code_theme="ansi_light",
|
|
50
|
-
|
|
61
|
+
code_background="#e0e0e0",
|
|
62
|
+
green_background="#e8f1e9",
|
|
63
|
+
blue_grey_background="#e8e9f1",
|
|
64
|
+
cyan_background="#e0f0f0",
|
|
65
|
+
green_sub_background="#e0f0e0",
|
|
66
|
+
blue_sub_background="#e0e8f5",
|
|
67
|
+
purple_background="#ede0f5",
|
|
68
|
+
orange_background="#f5ebe0",
|
|
69
|
+
red_background="#f5e0e0",
|
|
70
|
+
grey_background="#e8e8e8",
|
|
71
|
+
yellow_background="#f5f5e0",
|
|
51
72
|
)
|
|
52
73
|
|
|
53
74
|
DARK_PALETTE = Palette(
|
|
@@ -69,12 +90,26 @@ DARK_PALETTE = Palette(
|
|
|
69
90
|
diff_remove="#ffcdd2 on #3d1f23",
|
|
70
91
|
diff_remove_char="#ffcdd2 on #7a3a42",
|
|
71
92
|
code_theme="ansi_dark",
|
|
72
|
-
|
|
93
|
+
code_background="#2f3440",
|
|
94
|
+
green_background="#23342c",
|
|
95
|
+
blue_grey_background="#313848",
|
|
96
|
+
cyan_background="#1a3333",
|
|
97
|
+
green_sub_background="#1b3928",
|
|
98
|
+
blue_sub_background="#1a2a3d",
|
|
99
|
+
purple_background="#2a2640",
|
|
100
|
+
orange_background="#3d2a1a",
|
|
101
|
+
red_background="#3d1f23",
|
|
102
|
+
grey_background="#2a2d30",
|
|
103
|
+
yellow_background="#3d3a1a",
|
|
73
104
|
)
|
|
74
105
|
|
|
75
106
|
|
|
76
107
|
class ThemeKey(str, Enum):
|
|
77
108
|
LINES = "lines"
|
|
109
|
+
|
|
110
|
+
# PANEL
|
|
111
|
+
SUB_AGENT_RESULT_PANEL = "panel.sub_agent_result"
|
|
112
|
+
WRITE_MARKDOWN_PANEL = "panel.write_markdown"
|
|
78
113
|
# DIFF
|
|
79
114
|
DIFF_FILE_NAME = "diff.file_name"
|
|
80
115
|
DIFF_REMOVE = "diff.remove"
|
|
@@ -92,9 +127,9 @@ class ThemeKey(str, Enum):
|
|
|
92
127
|
METADATA_DIM = "metadata.dim"
|
|
93
128
|
METADATA_BOLD = "metadata.bold"
|
|
94
129
|
# SPINNER_STATUS
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
STATUS_SPINNER = "spinner.status"
|
|
131
|
+
STATUS_TEXT = "spinner.status.text"
|
|
132
|
+
STATUS_TEXT_BOLD = "spinner.status.text.bold"
|
|
98
133
|
# STATUS
|
|
99
134
|
STATUS_HINT = "status.hint"
|
|
100
135
|
# USER_INPUT
|
|
@@ -162,6 +197,7 @@ class Themes:
|
|
|
162
197
|
thinking_markdown_theme: Theme
|
|
163
198
|
code_theme: str
|
|
164
199
|
sub_agent_colors: list[Style]
|
|
200
|
+
sub_agent_backgrounds: list[Style]
|
|
165
201
|
|
|
166
202
|
|
|
167
203
|
def get_theme(theme: str | None = None) -> Themes:
|
|
@@ -170,6 +206,9 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
170
206
|
app_theme=Theme(
|
|
171
207
|
styles={
|
|
172
208
|
ThemeKey.LINES.value: palette.grey3,
|
|
209
|
+
# PANEL
|
|
210
|
+
ThemeKey.SUB_AGENT_RESULT_PANEL.value: f"on {palette.blue_grey_background}",
|
|
211
|
+
ThemeKey.WRITE_MARKDOWN_PANEL.value: f"on {palette.green_background}",
|
|
173
212
|
# DIFF
|
|
174
213
|
ThemeKey.DIFF_FILE_NAME.value: palette.blue,
|
|
175
214
|
ThemeKey.DIFF_REMOVE.value: palette.diff_remove,
|
|
@@ -183,7 +222,7 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
183
222
|
ThemeKey.ERROR_BOLD.value: "bold " + palette.red,
|
|
184
223
|
ThemeKey.INTERRUPT.value: "reverse bold " + palette.red,
|
|
185
224
|
# USER_INPUT
|
|
186
|
-
ThemeKey.USER_INPUT.value: palette.magenta,
|
|
225
|
+
ThemeKey.USER_INPUT.value: "bold " + palette.magenta,
|
|
187
226
|
ThemeKey.USER_INPUT_PROMPT.value: "bold " + palette.magenta,
|
|
188
227
|
ThemeKey.USER_INPUT_AT_PATTERN.value: palette.purple,
|
|
189
228
|
ThemeKey.USER_INPUT_SLASH_COMMAND.value: "bold reverse " + palette.blue,
|
|
@@ -192,11 +231,10 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
192
231
|
ThemeKey.METADATA.value: palette.lavender,
|
|
193
232
|
ThemeKey.METADATA_DIM.value: "dim " + palette.lavender,
|
|
194
233
|
ThemeKey.METADATA_BOLD.value: "bold " + palette.lavender,
|
|
195
|
-
# SPINNER_STATUS
|
|
196
|
-
ThemeKey.SPINNER_STATUS.value: palette.blue,
|
|
197
|
-
ThemeKey.SPINNER_STATUS_TEXT.value: palette.blue,
|
|
198
|
-
ThemeKey.SPINNER_STATUS_TEXT_BOLD.value: "bold " + palette.blue,
|
|
199
234
|
# STATUS
|
|
235
|
+
ThemeKey.STATUS_SPINNER.value: palette.blue,
|
|
236
|
+
ThemeKey.STATUS_TEXT.value: palette.blue,
|
|
237
|
+
ThemeKey.STATUS_TEXT_BOLD.value: "bold italic " + palette.blue,
|
|
200
238
|
ThemeKey.STATUS_HINT.value: palette.grey2,
|
|
201
239
|
# REMINDER
|
|
202
240
|
ThemeKey.REMINDER.value: palette.grey1,
|
|
@@ -265,7 +303,10 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
265
303
|
),
|
|
266
304
|
thinking_markdown_theme=Theme(
|
|
267
305
|
styles={
|
|
268
|
-
|
|
306
|
+
# THINKING (used for left-side mark in thinking output)
|
|
307
|
+
ThemeKey.THINKING.value: "italic " + palette.grey2,
|
|
308
|
+
ThemeKey.THINKING_BOLD.value: "bold italic " + palette.grey1,
|
|
309
|
+
"markdown.code": palette.grey1 + " italic on " + palette.code_background,
|
|
269
310
|
"markdown.code.block": palette.grey1,
|
|
270
311
|
"markdown.code.border": palette.grey3,
|
|
271
312
|
"markdown.h1": "bold reverse",
|
|
@@ -292,4 +333,14 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
292
333
|
Style(color=palette.grey1),
|
|
293
334
|
Style(color=palette.yellow),
|
|
294
335
|
],
|
|
336
|
+
sub_agent_backgrounds=[
|
|
337
|
+
Style(bgcolor=palette.cyan_background),
|
|
338
|
+
Style(bgcolor=palette.green_sub_background),
|
|
339
|
+
Style(bgcolor=palette.blue_sub_background),
|
|
340
|
+
Style(bgcolor=palette.purple_background),
|
|
341
|
+
Style(bgcolor=palette.orange_background),
|
|
342
|
+
Style(bgcolor=palette.red_background),
|
|
343
|
+
Style(bgcolor=palette.grey_background),
|
|
344
|
+
Style(bgcolor=palette.yellow_background),
|
|
345
|
+
],
|
|
295
346
|
)
|