klaude-code 1.2.6__py3-none-any.whl → 1.8.0__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/auth/__init__.py +24 -0
- klaude_code/auth/codex/__init__.py +20 -0
- klaude_code/auth/codex/exceptions.py +17 -0
- klaude_code/auth/codex/jwt_utils.py +45 -0
- klaude_code/auth/codex/oauth.py +229 -0
- klaude_code/auth/codex/token_manager.py +84 -0
- klaude_code/cli/auth_cmd.py +73 -0
- klaude_code/cli/config_cmd.py +91 -0
- klaude_code/cli/cost_cmd.py +338 -0
- klaude_code/cli/debug.py +78 -0
- klaude_code/cli/list_model.py +307 -0
- klaude_code/cli/main.py +233 -134
- klaude_code/cli/runtime.py +309 -117
- klaude_code/{version.py → cli/self_update.py} +114 -5
- klaude_code/cli/session_cmd.py +37 -21
- klaude_code/command/__init__.py +88 -27
- klaude_code/command/clear_cmd.py +8 -7
- klaude_code/command/command_abc.py +31 -31
- klaude_code/command/debug_cmd.py +79 -0
- klaude_code/command/export_cmd.py +19 -53
- klaude_code/command/export_online_cmd.py +154 -0
- klaude_code/command/fork_session_cmd.py +267 -0
- klaude_code/command/help_cmd.py +7 -8
- klaude_code/command/model_cmd.py +60 -10
- klaude_code/command/model_select.py +84 -0
- klaude_code/command/prompt-jj-describe.md +32 -0
- klaude_code/command/prompt_command.py +19 -11
- klaude_code/command/refresh_cmd.py +8 -10
- klaude_code/command/registry.py +139 -40
- klaude_code/command/release_notes_cmd.py +84 -0
- klaude_code/command/resume_cmd.py +111 -0
- klaude_code/command/status_cmd.py +104 -60
- klaude_code/command/terminal_setup_cmd.py +7 -9
- klaude_code/command/thinking_cmd.py +98 -0
- klaude_code/config/__init__.py +14 -6
- klaude_code/config/assets/__init__.py +1 -0
- klaude_code/config/assets/builtin_config.yaml +303 -0
- klaude_code/config/builtin_config.py +38 -0
- klaude_code/config/config.py +378 -109
- klaude_code/config/select_model.py +117 -53
- klaude_code/config/thinking.py +269 -0
- klaude_code/{const/__init__.py → const.py} +50 -19
- klaude_code/core/agent.py +20 -28
- klaude_code/core/executor.py +327 -112
- klaude_code/core/manager/__init__.py +2 -4
- klaude_code/core/manager/llm_clients.py +1 -15
- klaude_code/core/manager/llm_clients_builder.py +10 -11
- klaude_code/core/manager/sub_agent_manager.py +37 -6
- klaude_code/core/prompt.py +63 -44
- klaude_code/core/prompts/prompt-claude-code.md +2 -13
- klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
- klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
- klaude_code/core/prompts/prompt-codex.md +9 -42
- klaude_code/core/prompts/prompt-minimal.md +12 -0
- klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
- klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
- klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
- klaude_code/core/reminders.py +283 -95
- klaude_code/core/task.py +113 -75
- klaude_code/core/tool/__init__.py +24 -31
- klaude_code/core/tool/file/_utils.py +36 -0
- klaude_code/core/tool/file/apply_patch.py +17 -25
- klaude_code/core/tool/file/apply_patch_tool.py +57 -77
- klaude_code/core/tool/file/diff_builder.py +151 -0
- klaude_code/core/tool/file/edit_tool.py +50 -63
- klaude_code/core/tool/file/move_tool.md +41 -0
- klaude_code/core/tool/file/move_tool.py +435 -0
- klaude_code/core/tool/file/read_tool.md +1 -1
- klaude_code/core/tool/file/read_tool.py +86 -86
- klaude_code/core/tool/file/write_tool.py +59 -69
- klaude_code/core/tool/report_back_tool.py +84 -0
- klaude_code/core/tool/shell/bash_tool.py +265 -22
- klaude_code/core/tool/shell/command_safety.py +3 -6
- klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
- klaude_code/core/tool/sub_agent_tool.py +13 -2
- klaude_code/core/tool/todo/todo_write_tool.md +0 -157
- klaude_code/core/tool/todo/todo_write_tool.py +1 -1
- klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
- klaude_code/core/tool/todo/update_plan_tool.py +1 -1
- klaude_code/core/tool/tool_abc.py +18 -0
- klaude_code/core/tool/tool_context.py +27 -12
- klaude_code/core/tool/tool_registry.py +7 -7
- klaude_code/core/tool/tool_runner.py +44 -36
- klaude_code/core/tool/truncation.py +29 -14
- klaude_code/core/tool/web/mermaid_tool.md +43 -0
- klaude_code/core/tool/web/mermaid_tool.py +2 -5
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +112 -22
- klaude_code/core/tool/web/web_search_tool.md +23 -0
- klaude_code/core/tool/web/web_search_tool.py +130 -0
- klaude_code/core/turn.py +168 -66
- klaude_code/llm/__init__.py +2 -10
- klaude_code/llm/anthropic/client.py +190 -178
- klaude_code/llm/anthropic/input.py +39 -15
- klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code/llm/bedrock/client.py +60 -0
- klaude_code/llm/client.py +7 -21
- klaude_code/llm/codex/__init__.py +5 -0
- klaude_code/llm/codex/client.py +149 -0
- klaude_code/llm/google/__init__.py +3 -0
- klaude_code/llm/google/client.py +309 -0
- klaude_code/llm/google/input.py +215 -0
- klaude_code/llm/input_common.py +3 -9
- klaude_code/llm/openai_compatible/client.py +72 -164
- klaude_code/llm/openai_compatible/input.py +6 -4
- klaude_code/llm/openai_compatible/stream.py +273 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- klaude_code/llm/openrouter/client.py +89 -160
- klaude_code/llm/openrouter/input.py +18 -30
- klaude_code/llm/openrouter/reasoning.py +118 -0
- klaude_code/llm/registry.py +39 -7
- klaude_code/llm/responses/client.py +184 -171
- klaude_code/llm/responses/input.py +20 -1
- klaude_code/llm/usage.py +17 -12
- klaude_code/protocol/commands.py +17 -1
- klaude_code/protocol/events.py +31 -4
- klaude_code/protocol/llm_param.py +13 -10
- klaude_code/protocol/model.py +232 -29
- klaude_code/protocol/op.py +90 -1
- klaude_code/protocol/op_handler.py +35 -1
- klaude_code/protocol/sub_agent/__init__.py +117 -0
- klaude_code/protocol/sub_agent/explore.py +63 -0
- klaude_code/protocol/sub_agent/oracle.py +91 -0
- klaude_code/protocol/sub_agent/task.py +61 -0
- klaude_code/protocol/sub_agent/web.py +79 -0
- klaude_code/protocol/tools.py +4 -2
- klaude_code/session/__init__.py +2 -2
- klaude_code/session/codec.py +71 -0
- klaude_code/session/export.py +293 -86
- klaude_code/session/selector.py +89 -67
- klaude_code/session/session.py +320 -309
- klaude_code/session/store.py +220 -0
- klaude_code/session/templates/export_session.html +595 -83
- klaude_code/session/templates/mermaid_viewer.html +926 -0
- klaude_code/skill/__init__.py +27 -0
- klaude_code/skill/assets/deslop/SKILL.md +17 -0
- klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
- klaude_code/skill/assets/handoff/SKILL.md +39 -0
- klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
- klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
- klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
- klaude_code/skill/manager.py +70 -0
- klaude_code/skill/system_skills.py +192 -0
- klaude_code/trace/__init__.py +20 -2
- klaude_code/trace/log.py +150 -5
- klaude_code/ui/__init__.py +4 -9
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +7 -7
- klaude_code/ui/modes/debug/display.py +2 -1
- klaude_code/ui/modes/repl/__init__.py +3 -48
- klaude_code/ui/modes/repl/clipboard.py +5 -5
- klaude_code/ui/modes/repl/completers.py +487 -123
- klaude_code/ui/modes/repl/display.py +5 -4
- klaude_code/ui/modes/repl/event_handler.py +370 -117
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
- klaude_code/ui/modes/repl/key_bindings.py +146 -23
- klaude_code/ui/modes/repl/renderer.py +189 -99
- klaude_code/ui/renderers/assistant.py +9 -2
- klaude_code/ui/renderers/bash_syntax.py +178 -0
- klaude_code/ui/renderers/common.py +78 -0
- klaude_code/ui/renderers/developer.py +104 -48
- klaude_code/ui/renderers/diffs.py +87 -6
- klaude_code/ui/renderers/errors.py +11 -6
- klaude_code/ui/renderers/mermaid_viewer.py +57 -0
- klaude_code/ui/renderers/metadata.py +112 -76
- klaude_code/ui/renderers/sub_agent.py +92 -7
- klaude_code/ui/renderers/thinking.py +40 -18
- klaude_code/ui/renderers/tools.py +405 -227
- klaude_code/ui/renderers/user_input.py +73 -13
- klaude_code/ui/rich/__init__.py +10 -1
- klaude_code/ui/rich/cjk_wrap.py +228 -0
- klaude_code/ui/rich/code_panel.py +131 -0
- klaude_code/ui/rich/live.py +17 -0
- klaude_code/ui/rich/markdown.py +305 -170
- klaude_code/ui/rich/searchable_text.py +10 -13
- klaude_code/ui/rich/status.py +190 -49
- klaude_code/ui/rich/theme.py +135 -39
- klaude_code/ui/terminal/__init__.py +55 -0
- klaude_code/ui/terminal/color.py +1 -1
- klaude_code/ui/terminal/control.py +13 -22
- klaude_code/ui/terminal/notifier.py +44 -4
- klaude_code/ui/terminal/selector.py +658 -0
- klaude_code/ui/utils/common.py +0 -18
- klaude_code-1.8.0.dist-info/METADATA +377 -0
- klaude_code-1.8.0.dist-info/RECORD +219 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
- klaude_code/command/diff_cmd.py +0 -138
- klaude_code/command/prompt-dev-docs-update.md +0 -56
- klaude_code/command/prompt-dev-docs.md +0 -46
- klaude_code/config/list_model.py +0 -162
- klaude_code/core/manager/agent_manager.py +0 -127
- klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
- klaude_code/core/tool/file/multi_edit_tool.md +0 -42
- klaude_code/core/tool/file/multi_edit_tool.py +0 -199
- klaude_code/core/tool/memory/memory_tool.md +0 -16
- klaude_code/core/tool/memory/memory_tool.py +0 -462
- klaude_code/llm/openrouter/reasoning_handler.py +0 -209
- klaude_code/protocol/sub_agent.py +0 -348
- klaude_code/ui/utils/debouncer.py +0 -42
- klaude_code-1.2.6.dist-info/METADATA +0 -178
- klaude_code-1.2.6.dist-info/RECORD +0 -167
- /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
- /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
- /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
from rich.console import RenderableType
|
|
2
|
+
from rich.padding import Padding
|
|
3
|
+
from rich.text import Text
|
|
2
4
|
|
|
5
|
+
from klaude_code import const
|
|
3
6
|
from klaude_code.ui.renderers.common import create_grid
|
|
4
7
|
from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
8
|
+
from klaude_code.ui.rich.theme import ThemeKey
|
|
9
|
+
|
|
10
|
+
# UI markers
|
|
11
|
+
ASSISTANT_MESSAGE_MARK = "•"
|
|
5
12
|
|
|
6
13
|
|
|
7
14
|
def render_assistant_message(content: str, *, code_theme: str) -> RenderableType | None:
|
|
@@ -15,7 +22,7 @@ def render_assistant_message(content: str, *, code_theme: str) -> RenderableType
|
|
|
15
22
|
|
|
16
23
|
grid = create_grid()
|
|
17
24
|
grid.add_row(
|
|
18
|
-
|
|
19
|
-
NoInsetMarkdown(stripped, code_theme=code_theme),
|
|
25
|
+
Text(ASSISTANT_MESSAGE_MARK, style=ThemeKey.ASSISTANT_MESSAGE_MARK),
|
|
26
|
+
Padding(NoInsetMarkdown(stripped, code_theme=code_theme), (0, const.MARKDOWN_RIGHT_MARGIN, 0, 0)),
|
|
20
27
|
)
|
|
21
28
|
return grid
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Bash command syntax highlighting for terminal display."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pygments.lexers import BashLexer # pyright: ignore[reportUnknownVariableType]
|
|
7
|
+
from pygments.token import Token
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
|
|
10
|
+
from klaude_code.ui.rich.theme import ThemeKey
|
|
11
|
+
|
|
12
|
+
# Token types for bash syntax highlighting
|
|
13
|
+
_STRING_TOKENS = frozenset(
|
|
14
|
+
{
|
|
15
|
+
Token.Literal.String,
|
|
16
|
+
Token.Literal.String.Double,
|
|
17
|
+
Token.Literal.String.Single,
|
|
18
|
+
Token.Literal.String.Backtick,
|
|
19
|
+
Token.Literal.String.Escape,
|
|
20
|
+
Token.Literal.String.Heredoc,
|
|
21
|
+
Token.Comment,
|
|
22
|
+
Token.Comment.Single,
|
|
23
|
+
Token.Comment.Hashbang,
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_OPERATOR_TOKENS = frozenset(
|
|
28
|
+
{
|
|
29
|
+
Token.Operator,
|
|
30
|
+
Token.Punctuation,
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Operators that start a new command context (next non-whitespace token is a command)
|
|
35
|
+
_COMMAND_STARTERS = frozenset({"&&", "||", "|", ";", "&"})
|
|
36
|
+
|
|
37
|
+
# Commands that have subcommands (e.g., git commit, docker run)
|
|
38
|
+
_SUBCOMMAND_COMMANDS = frozenset(
|
|
39
|
+
{
|
|
40
|
+
# Version control
|
|
41
|
+
"git",
|
|
42
|
+
"jj",
|
|
43
|
+
"hg",
|
|
44
|
+
"svn",
|
|
45
|
+
# Container & orchestration
|
|
46
|
+
"docker",
|
|
47
|
+
"docker-compose",
|
|
48
|
+
"podman",
|
|
49
|
+
"kubectl",
|
|
50
|
+
"helm",
|
|
51
|
+
# Package managers
|
|
52
|
+
"npm",
|
|
53
|
+
"yarn",
|
|
54
|
+
"pnpm",
|
|
55
|
+
"cargo",
|
|
56
|
+
"uv",
|
|
57
|
+
"pip",
|
|
58
|
+
"poetry",
|
|
59
|
+
"brew",
|
|
60
|
+
"apt",
|
|
61
|
+
"apt-get",
|
|
62
|
+
"dnf",
|
|
63
|
+
"yum",
|
|
64
|
+
"pacman",
|
|
65
|
+
# Cloud CLIs
|
|
66
|
+
"aws",
|
|
67
|
+
"gcloud",
|
|
68
|
+
"az",
|
|
69
|
+
# Language tools
|
|
70
|
+
"go",
|
|
71
|
+
"rustup",
|
|
72
|
+
"python",
|
|
73
|
+
"ruby",
|
|
74
|
+
# Other common tools
|
|
75
|
+
"gh",
|
|
76
|
+
"systemctl",
|
|
77
|
+
"launchctl",
|
|
78
|
+
"supervisorctl",
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
_LEXER: Any = BashLexer(ensurenl=False) # pyright: ignore[reportUnknownVariableType]
|
|
83
|
+
|
|
84
|
+
# Regex to match heredoc: << [-]? [space]? ['"]? DELIMITER ['"]? [extra] \n body \n DELIMITER
|
|
85
|
+
# Groups: (<<-?) (space) (quote) (delimiter) (quote) (extra on first line) (body) (end delimiter)
|
|
86
|
+
_HEREDOC_PATTERN = re.compile(
|
|
87
|
+
r"^(<<-?)(\s*)(['\"]?)(\w+)\3([^\n]*)(\n.*\n)(\4)$",
|
|
88
|
+
re.DOTALL,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _append_heredoc(result: Text, token_value: str) -> None:
|
|
93
|
+
"""Append heredoc token with delimiter highlighting."""
|
|
94
|
+
match = _HEREDOC_PATTERN.match(token_value)
|
|
95
|
+
if match:
|
|
96
|
+
operator, space, quote, delimiter, extra, body, end_delimiter = match.groups()
|
|
97
|
+
# << or <<-
|
|
98
|
+
result.append(operator, style=ThemeKey.BASH_OPERATOR)
|
|
99
|
+
# Optional space
|
|
100
|
+
if space:
|
|
101
|
+
result.append(space)
|
|
102
|
+
# Opening quote
|
|
103
|
+
if quote:
|
|
104
|
+
result.append(quote, style=ThemeKey.BASH_HEREDOC_DELIMITER)
|
|
105
|
+
# Delimiter name (e.g., EOF)
|
|
106
|
+
result.append(delimiter, style=ThemeKey.BASH_HEREDOC_DELIMITER)
|
|
107
|
+
# Closing quote
|
|
108
|
+
if quote:
|
|
109
|
+
result.append(quote, style=ThemeKey.BASH_HEREDOC_DELIMITER)
|
|
110
|
+
# Extra content on first line (e.g., "> file.py")
|
|
111
|
+
if extra:
|
|
112
|
+
result.append(extra, style=ThemeKey.BASH_ARGUMENT)
|
|
113
|
+
# Body content
|
|
114
|
+
result.append(body, style=ThemeKey.BASH_STRING)
|
|
115
|
+
# End delimiter
|
|
116
|
+
result.append(end_delimiter, style=ThemeKey.BASH_HEREDOC_DELIMITER)
|
|
117
|
+
else:
|
|
118
|
+
# Fallback: couldn't parse heredoc structure
|
|
119
|
+
result.append(token_value, style=ThemeKey.BASH_STRING)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def highlight_bash_command(command: str) -> Text:
|
|
123
|
+
"""Apply bash syntax highlighting to a command string, returning Rich Text.
|
|
124
|
+
|
|
125
|
+
Styling:
|
|
126
|
+
- Command names (first token after line start or operators): bold green
|
|
127
|
+
- Subcommands (for commands like git, docker): bold green
|
|
128
|
+
- Arguments: green
|
|
129
|
+
- Operators (&&, ||, |, ;): dim green
|
|
130
|
+
- Strings and comments: green
|
|
131
|
+
"""
|
|
132
|
+
result = Text()
|
|
133
|
+
token_type: Any
|
|
134
|
+
token_value: str
|
|
135
|
+
|
|
136
|
+
# Track whether next non-whitespace token is a command
|
|
137
|
+
expect_command = True
|
|
138
|
+
# Track whether next non-flag token is a subcommand
|
|
139
|
+
expect_subcommand = False
|
|
140
|
+
|
|
141
|
+
for token_type, token_value in _LEXER.get_tokens(command):
|
|
142
|
+
# Determine style based on token type and context
|
|
143
|
+
if token_type in _STRING_TOKENS:
|
|
144
|
+
# Check if this is a heredoc (starts with <<)
|
|
145
|
+
if token_value.startswith("<<"):
|
|
146
|
+
_append_heredoc(result, token_value)
|
|
147
|
+
else:
|
|
148
|
+
result.append(token_value, style=ThemeKey.BASH_STRING)
|
|
149
|
+
expect_subcommand = False
|
|
150
|
+
elif token_type in _OPERATOR_TOKENS:
|
|
151
|
+
result.append(token_value, style=ThemeKey.BASH_OPERATOR)
|
|
152
|
+
# After command-starting operators, next token is a command
|
|
153
|
+
if token_value in _COMMAND_STARTERS:
|
|
154
|
+
expect_command = True
|
|
155
|
+
expect_subcommand = False
|
|
156
|
+
elif token_type in (Token.Text.Whitespace,):
|
|
157
|
+
result.append(token_value)
|
|
158
|
+
elif token_type == Token.Name.Builtin:
|
|
159
|
+
# Built-in commands are always commands
|
|
160
|
+
result.append(token_value, style=ThemeKey.BASH_COMMAND)
|
|
161
|
+
expect_command = False
|
|
162
|
+
expect_subcommand = token_value in _SUBCOMMAND_COMMANDS
|
|
163
|
+
elif expect_command and token_value.strip():
|
|
164
|
+
# First non-whitespace token in command context
|
|
165
|
+
result.append(token_value, style=ThemeKey.BASH_COMMAND)
|
|
166
|
+
expect_command = False
|
|
167
|
+
expect_subcommand = token_value in _SUBCOMMAND_COMMANDS
|
|
168
|
+
elif expect_subcommand and token_value.strip() and not token_value.startswith("-"):
|
|
169
|
+
# Subcommand: non-flag token after a command that has subcommands
|
|
170
|
+
result.append(token_value, style=ThemeKey.BASH_COMMAND)
|
|
171
|
+
expect_subcommand = False
|
|
172
|
+
else:
|
|
173
|
+
# Regular arguments (including flags, which reset subcommand expectation)
|
|
174
|
+
result.append(token_value, style=ThemeKey.BASH_ARGUMENT)
|
|
175
|
+
if token_value.strip():
|
|
176
|
+
expect_subcommand = False
|
|
177
|
+
|
|
178
|
+
return result
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
from rich.style import Style
|
|
1
2
|
from rich.table import Table
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
|
|
5
|
+
from klaude_code import const
|
|
6
|
+
from klaude_code.ui.rich.theme import ThemeKey
|
|
2
7
|
|
|
3
8
|
|
|
4
9
|
def create_grid() -> Table:
|
|
@@ -6,3 +11,76 @@ def create_grid() -> Table:
|
|
|
6
11
|
grid.add_column(no_wrap=True)
|
|
7
12
|
grid.add_column(overflow="fold")
|
|
8
13
|
return grid
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def truncate_display(
|
|
17
|
+
text: str,
|
|
18
|
+
max_lines: int = const.TRUNCATE_DISPLAY_MAX_LINES,
|
|
19
|
+
max_line_length: int = const.TRUNCATE_DISPLAY_MAX_LINE_LENGTH,
|
|
20
|
+
*,
|
|
21
|
+
base_style: str | Style | None = None,
|
|
22
|
+
) -> Text:
|
|
23
|
+
"""Truncate long text for terminal display.
|
|
24
|
+
|
|
25
|
+
Applies `ThemeKey.TOOL_RESULT_TRUNCATED` style to truncation indicators.
|
|
26
|
+
"""
|
|
27
|
+
# Expand tabs to spaces to ensure correct alignment when Rich applies padding.
|
|
28
|
+
text = text.expandtabs(8)
|
|
29
|
+
|
|
30
|
+
if max_lines <= 0:
|
|
31
|
+
truncated_lines = text.split("\n")
|
|
32
|
+
remaining = max(0, len(truncated_lines))
|
|
33
|
+
return Text(f"… (more {remaining} lines)", style=ThemeKey.TOOL_RESULT_TRUNCATED)
|
|
34
|
+
|
|
35
|
+
lines = text.split("\n")
|
|
36
|
+
truncated_lines = 0
|
|
37
|
+
head_lines: list[str] = []
|
|
38
|
+
tail_lines: list[str] = []
|
|
39
|
+
|
|
40
|
+
if len(lines) > max_lines:
|
|
41
|
+
truncated_lines = len(lines) - max_lines
|
|
42
|
+
|
|
43
|
+
# If the hidden section is too small, show everything instead of inserting
|
|
44
|
+
# the "(more N lines)" indicator.
|
|
45
|
+
if truncated_lines < 5:
|
|
46
|
+
truncated_lines = 0
|
|
47
|
+
head_lines = lines
|
|
48
|
+
else:
|
|
49
|
+
head_count = max_lines // 2
|
|
50
|
+
tail_count = max_lines - head_count
|
|
51
|
+
head_lines = lines[:head_count]
|
|
52
|
+
tail_lines = lines[-tail_count:]
|
|
53
|
+
else:
|
|
54
|
+
head_lines = lines
|
|
55
|
+
|
|
56
|
+
def append_line(out: Text, line: str) -> None:
|
|
57
|
+
if len(line) > max_line_length:
|
|
58
|
+
extra_chars = len(line) - max_line_length
|
|
59
|
+
out.append(line[:max_line_length])
|
|
60
|
+
out.append_text(
|
|
61
|
+
Text(
|
|
62
|
+
f" … (more {extra_chars} characters in this line)",
|
|
63
|
+
style=ThemeKey.TOOL_RESULT_TRUNCATED,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
out.append(line)
|
|
68
|
+
|
|
69
|
+
out = Text()
|
|
70
|
+
if base_style is not None:
|
|
71
|
+
out.style = base_style
|
|
72
|
+
|
|
73
|
+
for idx, line in enumerate(head_lines):
|
|
74
|
+
append_line(out, line)
|
|
75
|
+
if idx < len(head_lines) - 1 or truncated_lines > 0 or tail_lines:
|
|
76
|
+
out.append("\n")
|
|
77
|
+
|
|
78
|
+
if truncated_lines > 0:
|
|
79
|
+
out.append_text(Text(f"⋮ (more {truncated_lines} lines)\n", style=ThemeKey.TOOL_RESULT_TRUNCATED))
|
|
80
|
+
|
|
81
|
+
for idx, line in enumerate(tail_lines):
|
|
82
|
+
append_line(out, line)
|
|
83
|
+
if idx < len(tail_lines) - 1:
|
|
84
|
+
out.append("\n")
|
|
85
|
+
|
|
86
|
+
return out
|
|
@@ -4,11 +4,12 @@ from rich.table import Table
|
|
|
4
4
|
from rich.text import Text
|
|
5
5
|
|
|
6
6
|
from klaude_code.protocol import commands, events, model
|
|
7
|
-
from klaude_code.ui.renderers import
|
|
8
|
-
from klaude_code.ui.renderers.common import create_grid
|
|
7
|
+
from klaude_code.ui.renderers.common import create_grid, truncate_display
|
|
9
8
|
from klaude_code.ui.renderers.tools import render_path
|
|
9
|
+
from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
10
10
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
REMINDER_BULLET = " ⧉"
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
|
|
@@ -18,6 +19,7 @@ def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
|
|
|
18
19
|
or e.item.todo_use
|
|
19
20
|
or e.item.at_files
|
|
20
21
|
or e.item.user_image_count
|
|
22
|
+
or e.item.skill_name
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
|
|
@@ -32,7 +34,7 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
32
34
|
if mp := e.item.memory_paths:
|
|
33
35
|
grid = create_grid()
|
|
34
36
|
grid.add_row(
|
|
35
|
-
Text(
|
|
37
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
36
38
|
Text.assemble(
|
|
37
39
|
("Load memory ", ThemeKey.REMINDER),
|
|
38
40
|
Text(", ", ThemeKey.REMINDER).join(
|
|
@@ -46,7 +48,7 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
46
48
|
grid = create_grid()
|
|
47
49
|
for file_path in fc:
|
|
48
50
|
grid.add_row(
|
|
49
|
-
Text(
|
|
51
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
50
52
|
Text.assemble(
|
|
51
53
|
("Read ", ThemeKey.REMINDER),
|
|
52
54
|
render_path(file_path, ThemeKey.REMINDER_BOLD),
|
|
@@ -58,31 +60,62 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
58
60
|
if e.item.todo_use:
|
|
59
61
|
grid = create_grid()
|
|
60
62
|
grid.add_row(
|
|
61
|
-
Text(
|
|
63
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
62
64
|
Text("Todo hasn't been updated recently", ThemeKey.REMINDER),
|
|
63
65
|
)
|
|
64
66
|
parts.append(grid)
|
|
65
67
|
|
|
66
68
|
if e.item.at_files:
|
|
67
69
|
grid = create_grid()
|
|
70
|
+
# Group at_files by (operation, mentioned_in)
|
|
71
|
+
grouped: dict[tuple[str, str | None], list[str]] = {}
|
|
68
72
|
for at_file in e.item.at_files:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
73
|
+
key = (at_file.operation, at_file.mentioned_in)
|
|
74
|
+
if key not in grouped:
|
|
75
|
+
grouped[key] = []
|
|
76
|
+
grouped[key].append(at_file.path)
|
|
77
|
+
|
|
78
|
+
for (operation, mentioned_in), paths in grouped.items():
|
|
79
|
+
path_texts = Text(", ", ThemeKey.REMINDER).join(render_path(p, ThemeKey.REMINDER_BOLD) for p in paths)
|
|
80
|
+
if mentioned_in:
|
|
81
|
+
grid.add_row(
|
|
82
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
83
|
+
Text.assemble(
|
|
84
|
+
(f"{operation} ", ThemeKey.REMINDER),
|
|
85
|
+
path_texts,
|
|
86
|
+
(" mentioned in ", ThemeKey.REMINDER),
|
|
87
|
+
render_path(mentioned_in, ThemeKey.REMINDER_BOLD),
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
grid.add_row(
|
|
92
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
93
|
+
Text.assemble(
|
|
94
|
+
(f"{operation} ", ThemeKey.REMINDER),
|
|
95
|
+
path_texts,
|
|
96
|
+
),
|
|
97
|
+
)
|
|
76
98
|
parts.append(grid)
|
|
77
99
|
|
|
78
100
|
if uic := e.item.user_image_count:
|
|
79
101
|
grid = create_grid()
|
|
80
102
|
grid.add_row(
|
|
81
|
-
Text(
|
|
103
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
82
104
|
Text(f"Attached {uic} image{'s' if uic > 1 else ''}", style=ThemeKey.REMINDER),
|
|
83
105
|
)
|
|
84
106
|
parts.append(grid)
|
|
85
107
|
|
|
108
|
+
if sn := e.item.skill_name:
|
|
109
|
+
grid = create_grid()
|
|
110
|
+
grid.add_row(
|
|
111
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
112
|
+
Text.assemble(
|
|
113
|
+
("Activated skill ", ThemeKey.REMINDER),
|
|
114
|
+
(sn, ThemeKey.REMINDER_BOLD),
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
parts.append(grid)
|
|
118
|
+
|
|
86
119
|
return Group(*parts) if parts else Text("")
|
|
87
120
|
|
|
88
121
|
|
|
@@ -92,18 +125,18 @@ def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
92
125
|
return Text("")
|
|
93
126
|
|
|
94
127
|
match e.item.command_output.command_name:
|
|
95
|
-
case commands.CommandName.DIFF:
|
|
96
|
-
if e.item.content is None or len(e.item.content) == 0:
|
|
97
|
-
return Padding.indent(Text("(no changes)", style=ThemeKey.TOOL_RESULT), level=2)
|
|
98
|
-
return r_diffs.render_diff_panel(e.item.content, show_file_name=True)
|
|
99
128
|
case commands.CommandName.HELP:
|
|
100
129
|
return Padding.indent(Text.from_markup(e.item.content or ""), level=2)
|
|
101
130
|
case commands.CommandName.STATUS:
|
|
102
131
|
return _render_status_output(e.item.command_output)
|
|
132
|
+
case commands.CommandName.RELEASE_NOTES:
|
|
133
|
+
return Padding.indent(NoInsetMarkdown(e.item.content or ""), level=2)
|
|
134
|
+
case commands.CommandName.FORK_SESSION:
|
|
135
|
+
return _render_fork_session_output(e.item.command_output)
|
|
103
136
|
case _:
|
|
104
137
|
content = e.item.content or "(no content)"
|
|
105
138
|
style = ThemeKey.TOOL_RESULT if not e.item.command_output.is_error else ThemeKey.ERROR
|
|
106
|
-
return Padding.indent(
|
|
139
|
+
return Padding.indent(truncate_display(content, base_style=style), level=2)
|
|
107
140
|
|
|
108
141
|
|
|
109
142
|
def _format_tokens(tokens: int) -> str:
|
|
@@ -115,44 +148,67 @@ def _format_tokens(tokens: int) -> str:
|
|
|
115
148
|
return str(tokens)
|
|
116
149
|
|
|
117
150
|
|
|
118
|
-
def _format_cost(cost: float | None) -> str:
|
|
119
|
-
"""Format cost
|
|
151
|
+
def _format_cost(cost: float | None, currency: str = "USD") -> str:
|
|
152
|
+
"""Format cost with currency symbol."""
|
|
120
153
|
if cost is None:
|
|
121
154
|
return "-"
|
|
155
|
+
symbol = "¥" if currency == "CNY" else "$"
|
|
122
156
|
if cost < 0.01:
|
|
123
|
-
return f"
|
|
124
|
-
return f"
|
|
157
|
+
return f"{symbol}{cost:.4f}"
|
|
158
|
+
return f"{symbol}{cost:.2f}"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _render_fork_session_output(command_output: model.CommandOutput) -> RenderableType:
|
|
162
|
+
"""Render fork session output with usage instructions."""
|
|
163
|
+
if not isinstance(command_output.ui_extra, model.SessionIdUIExtra):
|
|
164
|
+
return Padding.indent(Text("(no session id)", style=ThemeKey.METADATA), level=2)
|
|
165
|
+
|
|
166
|
+
grid = Table.grid(padding=(0, 1))
|
|
167
|
+
session_id = command_output.ui_extra.session_id
|
|
168
|
+
grid.add_column(style=ThemeKey.METADATA, overflow="fold")
|
|
169
|
+
|
|
170
|
+
grid.add_row(Text("Session forked. Resume command copied to clipboard:", style=ThemeKey.METADATA))
|
|
171
|
+
grid.add_row(Text(f" klaude --resume-by-id {session_id}", style=ThemeKey.METADATA_BOLD))
|
|
172
|
+
|
|
173
|
+
return Padding.indent(grid, level=2)
|
|
125
174
|
|
|
126
175
|
|
|
127
176
|
def _render_status_output(command_output: model.CommandOutput) -> RenderableType:
|
|
128
|
-
"""Render session status
|
|
129
|
-
if not command_output.ui_extra
|
|
130
|
-
return Text("(no status data)", style=ThemeKey.
|
|
177
|
+
"""Render session status with total cost and per-model breakdown."""
|
|
178
|
+
if not isinstance(command_output.ui_extra, model.SessionStatusUIExtra):
|
|
179
|
+
return Text("(no status data)", style=ThemeKey.METADATA)
|
|
131
180
|
|
|
132
|
-
status = command_output.ui_extra
|
|
181
|
+
status = command_output.ui_extra
|
|
133
182
|
usage = status.usage
|
|
134
183
|
|
|
135
184
|
table = Table.grid(padding=(0, 2))
|
|
136
|
-
table.add_column(style=ThemeKey.
|
|
137
|
-
table.add_column(style=ThemeKey.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
table.add_row(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
185
|
+
table.add_column(style=ThemeKey.METADATA, overflow="fold")
|
|
186
|
+
table.add_column(style=ThemeKey.METADATA, overflow="fold")
|
|
187
|
+
|
|
188
|
+
# Total cost line
|
|
189
|
+
table.add_row(
|
|
190
|
+
Text("Total cost:", style=ThemeKey.METADATA_BOLD),
|
|
191
|
+
Text(_format_cost(usage.total_cost, usage.currency), style=ThemeKey.METADATA_BOLD),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Per-model breakdown
|
|
195
|
+
if status.by_model:
|
|
196
|
+
table.add_row(Text("Usage by model:", style=ThemeKey.METADATA_BOLD), "")
|
|
197
|
+
for meta in status.by_model:
|
|
198
|
+
model_label = meta.model_name
|
|
199
|
+
if meta.provider:
|
|
200
|
+
model_label = f"{meta.model_name} ({meta.provider.lower().replace(' ', '-')})"
|
|
201
|
+
|
|
202
|
+
if meta.usage:
|
|
203
|
+
usage_detail = (
|
|
204
|
+
f"{_format_tokens(meta.usage.input_tokens)} input, "
|
|
205
|
+
f"{_format_tokens(meta.usage.output_tokens)} output, "
|
|
206
|
+
f"{_format_tokens(meta.usage.cached_tokens)} cache read, "
|
|
207
|
+
f"{_format_tokens(meta.usage.reasoning_tokens)} thinking, "
|
|
208
|
+
f"({_format_cost(meta.usage.total_cost, meta.usage.currency)})"
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
usage_detail = "(no usage data)"
|
|
212
|
+
table.add_row(f"{model_label}:", usage_detail)
|
|
157
213
|
|
|
158
214
|
return Padding.indent(table, level=2)
|
|
@@ -5,6 +5,7 @@ from rich.panel import Panel
|
|
|
5
5
|
from rich.text import Text
|
|
6
6
|
|
|
7
7
|
from klaude_code import const
|
|
8
|
+
from klaude_code.protocol import model
|
|
8
9
|
from klaude_code.ui.renderers.common import create_grid
|
|
9
10
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
10
11
|
|
|
@@ -73,10 +74,7 @@ def render_diff(diff_text: str, show_file_name: bool = False) -> RenderableType:
|
|
|
73
74
|
if line.startswith("--- "):
|
|
74
75
|
raw = line[4:].strip()
|
|
75
76
|
if raw != "/dev/null":
|
|
76
|
-
if raw.startswith(("a/", "b/"))
|
|
77
|
-
from_file_name = raw[2:]
|
|
78
|
-
else:
|
|
79
|
-
from_file_name = raw
|
|
77
|
+
from_file_name = raw[2:] if raw.startswith(("a/", "b/")) else raw
|
|
80
78
|
continue
|
|
81
79
|
|
|
82
80
|
# Parse file name from diff headers
|
|
@@ -150,7 +148,7 @@ def render_diff(diff_text: str, show_file_name: bool = False) -> RenderableType:
|
|
|
150
148
|
plus = parts[2] # like '+12,4'
|
|
151
149
|
new_start = int(plus[1:].split(",")[0])
|
|
152
150
|
new_ln = new_start
|
|
153
|
-
except
|
|
151
|
+
except (IndexError, ValueError):
|
|
154
152
|
new_ln = None
|
|
155
153
|
if has_rendered_diff_content:
|
|
156
154
|
grid.add_row(Text(f"{'⋮':>{const.DIFF_PREFIX_WIDTH}}", style=ThemeKey.TOOL_RESULT), "")
|
|
@@ -182,11 +180,35 @@ def render_diff(diff_text: str, show_file_name: bool = False) -> RenderableType:
|
|
|
182
180
|
return grid
|
|
183
181
|
|
|
184
182
|
|
|
183
|
+
def render_structured_diff(ui_extra: model.DiffUIExtra, show_file_name: bool = False) -> RenderableType:
|
|
184
|
+
files = ui_extra.files
|
|
185
|
+
if not files:
|
|
186
|
+
return Text("")
|
|
187
|
+
|
|
188
|
+
grid = create_grid()
|
|
189
|
+
grid.padding = (0, 0)
|
|
190
|
+
show_headers = show_file_name or len(files) > 1
|
|
191
|
+
|
|
192
|
+
for idx, file_diff in enumerate(files):
|
|
193
|
+
if idx > 0:
|
|
194
|
+
grid.add_row("", "")
|
|
195
|
+
|
|
196
|
+
if show_headers:
|
|
197
|
+
grid.add_row(*_render_file_header(file_diff))
|
|
198
|
+
|
|
199
|
+
for line in file_diff.lines:
|
|
200
|
+
prefix = _make_structured_prefix(line, const.DIFF_PREFIX_WIDTH)
|
|
201
|
+
text = _render_structured_line(line)
|
|
202
|
+
grid.add_row(Text(prefix, ThemeKey.TOOL_RESULT), text)
|
|
203
|
+
|
|
204
|
+
return grid
|
|
205
|
+
|
|
206
|
+
|
|
185
207
|
def render_diff_panel(
|
|
186
208
|
diff_text: str,
|
|
187
209
|
*,
|
|
188
210
|
show_file_name: bool = True,
|
|
189
|
-
heading: str = "
|
|
211
|
+
heading: str = "DIFF",
|
|
190
212
|
indent: int = 2,
|
|
191
213
|
) -> RenderableType:
|
|
192
214
|
lines = diff_text.splitlines()
|
|
@@ -213,3 +235,62 @@ def render_diff_panel(
|
|
|
213
235
|
if indent <= 0:
|
|
214
236
|
return panel
|
|
215
237
|
return Padding.indent(panel, level=indent)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _render_file_header(file_diff: model.DiffFileDiff) -> tuple[Text, Text]:
|
|
241
|
+
file_text = Text(file_diff.file_path, style=ThemeKey.DIFF_FILE_NAME)
|
|
242
|
+
stats_text = Text()
|
|
243
|
+
if file_diff.stats_add > 0:
|
|
244
|
+
stats_text.append(f"+{file_diff.stats_add}", style=ThemeKey.DIFF_STATS_ADD)
|
|
245
|
+
if file_diff.stats_remove > 0:
|
|
246
|
+
if stats_text.plain:
|
|
247
|
+
stats_text.append(" ")
|
|
248
|
+
stats_text.append(f"-{file_diff.stats_remove}", style=ThemeKey.DIFF_STATS_REMOVE)
|
|
249
|
+
|
|
250
|
+
file_line = Text(style=ThemeKey.DIFF_FILE_NAME)
|
|
251
|
+
file_line.append_text(file_text)
|
|
252
|
+
if stats_text.plain:
|
|
253
|
+
file_line.append(" (")
|
|
254
|
+
file_line.append_text(stats_text)
|
|
255
|
+
file_line.append(")")
|
|
256
|
+
|
|
257
|
+
if file_diff.stats_add > 0 and file_diff.stats_remove == 0:
|
|
258
|
+
file_mark = "+"
|
|
259
|
+
elif file_diff.stats_remove > 0 and file_diff.stats_add == 0:
|
|
260
|
+
file_mark = "-"
|
|
261
|
+
else:
|
|
262
|
+
file_mark = "±"
|
|
263
|
+
|
|
264
|
+
prefix = Text(f"{file_mark:>{const.DIFF_PREFIX_WIDTH}} ", style=ThemeKey.DIFF_FILE_NAME)
|
|
265
|
+
return prefix, file_line
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _make_structured_prefix(line: model.DiffLine, width: int) -> str:
|
|
269
|
+
if line.kind == "gap":
|
|
270
|
+
return f"{'⋮':>{width}} "
|
|
271
|
+
number = " " * width
|
|
272
|
+
if line.kind in {"add", "ctx"} and line.new_line_no is not None:
|
|
273
|
+
number = f"{line.new_line_no:>{width}}"
|
|
274
|
+
marker = "+" if line.kind == "add" else "-" if line.kind == "remove" else " "
|
|
275
|
+
return f"{number} {marker}"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _render_structured_line(line: model.DiffLine) -> Text:
|
|
279
|
+
if line.kind == "gap":
|
|
280
|
+
return Text("")
|
|
281
|
+
text = Text()
|
|
282
|
+
for span in line.spans:
|
|
283
|
+
text.append(span.text, style=_span_style(line.kind, span.op))
|
|
284
|
+
return text
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _span_style(line_kind: str, span_op: str) -> ThemeKey:
|
|
288
|
+
if line_kind == "add":
|
|
289
|
+
if span_op == "insert":
|
|
290
|
+
return ThemeKey.DIFF_ADD_CHAR
|
|
291
|
+
return ThemeKey.DIFF_ADD
|
|
292
|
+
if line_kind == "remove":
|
|
293
|
+
if span_op == "delete":
|
|
294
|
+
return ThemeKey.DIFF_REMOVE_CHAR
|
|
295
|
+
return ThemeKey.DIFF_REMOVE
|
|
296
|
+
return ThemeKey.TOOL_RESULT
|
|
@@ -5,12 +5,17 @@ from klaude_code.ui.renderers.common import create_grid
|
|
|
5
5
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def render_error(error_msg: Text
|
|
9
|
-
"""
|
|
8
|
+
def render_error(error_msg: Text) -> RenderableType:
|
|
9
|
+
"""Render error with X mark for error events."""
|
|
10
|
+
grid = create_grid()
|
|
11
|
+
error_msg.style = ThemeKey.ERROR
|
|
12
|
+
grid.add_row(Text("✘", style=ThemeKey.ERROR_BOLD), error_msg)
|
|
13
|
+
return grid
|
|
14
|
+
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
"""
|
|
16
|
+
def render_tool_error(error_msg: Text) -> RenderableType:
|
|
17
|
+
"""Render error with indent for tool results."""
|
|
13
18
|
grid = create_grid()
|
|
14
|
-
error_msg.
|
|
15
|
-
grid.add_row(Text(" "
|
|
19
|
+
error_msg.style = ThemeKey.ERROR
|
|
20
|
+
grid.add_row(Text(" "), error_msg)
|
|
16
21
|
return grid
|