klaude-code 2.0.2__py3-none-any.whl → 2.1.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/app/__init__.py +12 -0
- klaude_code/app/runtime.py +215 -0
- klaude_code/cli/auth_cmd.py +2 -2
- klaude_code/cli/config_cmd.py +2 -2
- klaude_code/cli/cost_cmd.py +1 -1
- klaude_code/cli/debug.py +12 -36
- klaude_code/cli/list_model.py +3 -3
- klaude_code/cli/main.py +17 -60
- klaude_code/cli/self_update.py +2 -187
- klaude_code/cli/session_cmd.py +2 -2
- klaude_code/config/config.py +1 -1
- klaude_code/config/select_model.py +1 -1
- klaude_code/const.py +9 -1
- klaude_code/core/agent.py +9 -62
- klaude_code/core/agent_profile.py +284 -0
- klaude_code/core/executor.py +335 -230
- klaude_code/core/manager/llm_clients_builder.py +1 -1
- klaude_code/core/manager/sub_agent_manager.py +16 -29
- klaude_code/core/reminders.py +64 -99
- klaude_code/core/task.py +12 -20
- klaude_code/core/tool/__init__.py +5 -17
- klaude_code/core/tool/context.py +84 -0
- klaude_code/core/tool/file/apply_patch_tool.py +18 -21
- klaude_code/core/tool/file/edit_tool.py +39 -42
- klaude_code/core/tool/file/read_tool.py +14 -9
- klaude_code/core/tool/file/write_tool.py +12 -13
- klaude_code/core/tool/report_back_tool.py +4 -1
- klaude_code/core/tool/shell/bash_tool.py +6 -11
- klaude_code/core/tool/skill/skill_tool.py +3 -1
- klaude_code/core/tool/sub_agent_tool.py +8 -7
- klaude_code/core/tool/todo/todo_write_tool.py +3 -9
- klaude_code/core/tool/todo/update_plan_tool.py +3 -5
- klaude_code/core/tool/tool_abc.py +2 -1
- klaude_code/core/tool/tool_registry.py +2 -33
- klaude_code/core/tool/tool_runner.py +13 -10
- klaude_code/core/tool/web/mermaid_tool.py +3 -1
- klaude_code/core/tool/web/web_fetch_tool.py +5 -3
- klaude_code/core/tool/web/web_search_tool.py +5 -3
- klaude_code/core/turn.py +86 -26
- klaude_code/llm/anthropic/client.py +1 -1
- klaude_code/llm/bedrock/client.py +1 -1
- klaude_code/llm/claude/client.py +1 -1
- klaude_code/llm/codex/client.py +1 -1
- klaude_code/llm/google/client.py +1 -1
- klaude_code/llm/openai_compatible/client.py +1 -1
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +1 -1
- klaude_code/llm/openrouter/client.py +1 -1
- klaude_code/llm/openrouter/reasoning.py +1 -1
- klaude_code/llm/responses/client.py +1 -1
- klaude_code/protocol/events/__init__.py +57 -0
- klaude_code/protocol/events/base.py +18 -0
- klaude_code/protocol/events/chat.py +20 -0
- klaude_code/protocol/events/lifecycle.py +22 -0
- klaude_code/protocol/events/metadata.py +15 -0
- klaude_code/protocol/events/streaming.py +43 -0
- klaude_code/protocol/events/system.py +53 -0
- klaude_code/protocol/events/tools.py +23 -0
- klaude_code/protocol/op.py +5 -0
- klaude_code/session/session.py +6 -5
- klaude_code/skill/assets/create-plan/SKILL.md +76 -0
- klaude_code/skill/loader.py +1 -1
- klaude_code/skill/system_skills.py +1 -1
- klaude_code/tui/__init__.py +8 -0
- klaude_code/{command → tui/command}/clear_cmd.py +2 -1
- klaude_code/{command → tui/command}/debug_cmd.py +3 -2
- klaude_code/{command → tui/command}/export_cmd.py +2 -1
- klaude_code/{command → tui/command}/export_online_cmd.py +2 -1
- klaude_code/{command → tui/command}/fork_session_cmd.py +4 -3
- klaude_code/{command → tui/command}/help_cmd.py +2 -1
- klaude_code/{command → tui/command}/model_cmd.py +4 -3
- klaude_code/{command → tui/command}/model_select.py +2 -2
- klaude_code/{command → tui/command}/prompt_command.py +4 -3
- klaude_code/{command → tui/command}/refresh_cmd.py +3 -1
- klaude_code/{command → tui/command}/registry.py +6 -5
- klaude_code/{command → tui/command}/release_notes_cmd.py +2 -1
- klaude_code/{command → tui/command}/resume_cmd.py +4 -3
- klaude_code/{command → tui/command}/status_cmd.py +2 -1
- klaude_code/{command → tui/command}/terminal_setup_cmd.py +2 -1
- klaude_code/{command → tui/command}/thinking_cmd.py +3 -2
- klaude_code/tui/commands.py +164 -0
- klaude_code/{ui/renderers → tui/components}/assistant.py +3 -3
- klaude_code/{ui/renderers → tui/components}/bash_syntax.py +2 -2
- klaude_code/{ui/renderers → tui/components}/common.py +1 -1
- klaude_code/{ui/renderers → tui/components}/developer.py +4 -4
- klaude_code/{ui/renderers → tui/components}/diffs.py +2 -2
- klaude_code/{ui/renderers → tui/components}/errors.py +2 -2
- klaude_code/{ui/renderers → tui/components}/metadata.py +7 -7
- klaude_code/{ui → tui/components}/rich/markdown.py +9 -23
- klaude_code/{ui → tui/components}/rich/status.py +2 -2
- klaude_code/{ui → tui/components}/rich/theme.py +3 -1
- klaude_code/{ui/renderers → tui/components}/sub_agent.py +23 -43
- klaude_code/{ui/renderers → tui/components}/thinking.py +3 -3
- klaude_code/{ui/renderers → tui/components}/tools.py +9 -9
- klaude_code/{ui/renderers → tui/components}/user_input.py +3 -20
- klaude_code/tui/display.py +85 -0
- klaude_code/{ui/modes/repl → tui/input}/__init__.py +1 -1
- klaude_code/{ui/modes/repl → tui/input}/completers.py +1 -1
- klaude_code/{ui/modes/repl/input_prompt_toolkit.py → tui/input/prompt_toolkit.py} +6 -6
- klaude_code/tui/machine.py +606 -0
- klaude_code/tui/renderer.py +707 -0
- klaude_code/tui/runner.py +321 -0
- klaude_code/tui/terminal/__init__.py +56 -0
- klaude_code/{ui → tui}/terminal/color.py +1 -1
- klaude_code/{ui → tui}/terminal/control.py +1 -1
- klaude_code/{ui → tui}/terminal/notifier.py +1 -1
- klaude_code/ui/__init__.py +6 -50
- klaude_code/ui/core/display.py +3 -3
- klaude_code/ui/core/input.py +2 -1
- klaude_code/ui/{modes/debug/display.py → debug_mode.py} +1 -1
- klaude_code/ui/{modes/exec/display.py → exec_mode.py} +0 -2
- klaude_code/ui/terminal/__init__.py +6 -54
- klaude_code/ui/terminal/title.py +31 -0
- klaude_code/update.py +163 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.0.dist-info}/METADATA +1 -1
- klaude_code-2.1.0.dist-info/RECORD +235 -0
- klaude_code/cli/runtime.py +0 -518
- klaude_code/core/prompt.py +0 -108
- klaude_code/core/tool/tool_context.py +0 -148
- klaude_code/protocol/events.py +0 -195
- klaude_code/skill/assets/dev-docs/SKILL.md +0 -108
- klaude_code/trace/__init__.py +0 -21
- klaude_code/ui/core/stage_manager.py +0 -48
- klaude_code/ui/modes/__init__.py +0 -1
- klaude_code/ui/modes/debug/__init__.py +0 -1
- klaude_code/ui/modes/exec/__init__.py +0 -1
- klaude_code/ui/modes/repl/display.py +0 -61
- klaude_code/ui/modes/repl/event_handler.py +0 -629
- klaude_code/ui/modes/repl/renderer.py +0 -464
- klaude_code/ui/utils/__init__.py +0 -1
- klaude_code-2.0.2.dist-info/RECORD +0 -227
- /klaude_code/{trace/log.py → log.py} +0 -0
- /klaude_code/{command → tui/command}/__init__.py +0 -0
- /klaude_code/{command → tui/command}/command_abc.py +0 -0
- /klaude_code/{command → tui/command}/prompt-commit.md +0 -0
- /klaude_code/{command → tui/command}/prompt-init.md +0 -0
- /klaude_code/{ui/renderers → tui/components}/__init__.py +0 -0
- /klaude_code/{ui/renderers → tui/components}/mermaid_viewer.py +0 -0
- /klaude_code/{ui → tui/components}/rich/__init__.py +0 -0
- /klaude_code/{ui → tui/components}/rich/cjk_wrap.py +0 -0
- /klaude_code/{ui → tui/components}/rich/code_panel.py +0 -0
- /klaude_code/{ui → tui/components}/rich/live.py +0 -0
- /klaude_code/{ui → tui/components}/rich/quote.py +0 -0
- /klaude_code/{ui → tui/components}/rich/searchable_text.py +0 -0
- /klaude_code/{ui/modes/repl → tui/input}/clipboard.py +0 -0
- /klaude_code/{ui/modes/repl → tui/input}/key_bindings.py +0 -0
- /klaude_code/{ui → tui}/terminal/image.py +0 -0
- /klaude_code/{ui → tui}/terminal/progress_bar.py +0 -0
- /klaude_code/{ui → tui}/terminal/selector.py +0 -0
- /klaude_code/ui/{utils/common.py → common.py} +0 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.0.dist-info}/entry_points.txt +0 -0
|
@@ -5,12 +5,12 @@ from rich.padding import Padding
|
|
|
5
5
|
from rich.text import Text
|
|
6
6
|
|
|
7
7
|
from klaude_code.const import DEFAULT_MAX_TOKENS
|
|
8
|
+
from klaude_code.log import is_debug_enabled
|
|
8
9
|
from klaude_code.protocol import events, model
|
|
9
|
-
from klaude_code.
|
|
10
|
-
from klaude_code.
|
|
11
|
-
from klaude_code.
|
|
12
|
-
from klaude_code.ui.
|
|
13
|
-
from klaude_code.ui.utils.common import format_model_params, format_number
|
|
10
|
+
from klaude_code.tui.components.common import create_grid
|
|
11
|
+
from klaude_code.tui.components.rich.quote import Quote
|
|
12
|
+
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
13
|
+
from klaude_code.ui.common import format_model_params, format_number
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _get_version() -> str:
|
|
@@ -99,7 +99,7 @@ def _render_task_metadata_block(
|
|
|
99
99
|
)
|
|
100
100
|
)
|
|
101
101
|
if metadata.usage is not None:
|
|
102
|
-
# Context
|
|
102
|
+
# Context usage
|
|
103
103
|
if show_context_and_time and metadata.usage.context_usage_percent is not None:
|
|
104
104
|
context_size = format_number(metadata.usage.context_size or 0)
|
|
105
105
|
# Calculate effective limit (same as Usage.context_usage_percent)
|
|
@@ -169,7 +169,7 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
|
169
169
|
|
|
170
170
|
# Render each sub-agent metadata block
|
|
171
171
|
for meta in e.metadata.sub_agent_task_metadata:
|
|
172
|
-
renderables.append(_render_task_metadata_block(meta, is_sub_agent=True, show_context_and_time=
|
|
172
|
+
renderables.append(_render_task_metadata_block(meta, is_sub_agent=True, show_context_and_time=True))
|
|
173
173
|
|
|
174
174
|
# Add total cost line when there are sub-agents
|
|
175
175
|
if e.metadata.sub_agent_task_metadata:
|
|
@@ -24,7 +24,7 @@ from klaude_code.const import (
|
|
|
24
24
|
MARKDOWN_STREAM_SYNCHRONIZED_OUTPUT_ENABLED,
|
|
25
25
|
UI_REFRESH_RATE_FPS,
|
|
26
26
|
)
|
|
27
|
-
from klaude_code.
|
|
27
|
+
from klaude_code.tui.components.rich.code_panel import CodePanel
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class NoInsetCodeBlock(CodeBlock):
|
|
@@ -61,7 +61,13 @@ class Divider(MarkdownElement):
|
|
|
61
61
|
|
|
62
62
|
class MarkdownTable(TableElement):
|
|
63
63
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
64
|
-
|
|
64
|
+
# rich.box.MARKDOWN intentionally includes a blank top/bottom edge row. Rather than
|
|
65
|
+
# post-processing rendered segments, disable outer edges to avoid emitting those rows.
|
|
66
|
+
table = Table(
|
|
67
|
+
box=box.MARKDOWN,
|
|
68
|
+
show_edge=False,
|
|
69
|
+
border_style=console.get_style("markdown.table.border"),
|
|
70
|
+
)
|
|
65
71
|
|
|
66
72
|
if self.header is not None and self.header.row is not None:
|
|
67
73
|
for column in self.header.row.cells:
|
|
@@ -72,27 +78,7 @@ class MarkdownTable(TableElement):
|
|
|
72
78
|
row_content = [element.content for element in row.cells]
|
|
73
79
|
table.add_row(*row_content)
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
segments = list(console.render(table, options))
|
|
77
|
-
|
|
78
|
-
# Skip leading blank line (before first newline)
|
|
79
|
-
first_newline_idx = next((i for i, s in enumerate(segments) if s.text == "\n"), None)
|
|
80
|
-
if first_newline_idx is not None:
|
|
81
|
-
first_line = "".join(s.text for s in segments[:first_newline_idx])
|
|
82
|
-
if not first_line.strip():
|
|
83
|
-
segments = segments[first_newline_idx + 1 :]
|
|
84
|
-
|
|
85
|
-
# Skip trailing blank line (after last newline)
|
|
86
|
-
while len(segments) >= 2 and segments[-1].text == "\n":
|
|
87
|
-
prev_newline = next((i for i in range(len(segments) - 2, -1, -1) if segments[i].text == "\n"), None)
|
|
88
|
-
if prev_newline is not None:
|
|
89
|
-
between = "".join(s.text for s in segments[prev_newline + 1 : -1])
|
|
90
|
-
if not between.strip():
|
|
91
|
-
segments = segments[: prev_newline + 1]
|
|
92
|
-
continue
|
|
93
|
-
break
|
|
94
|
-
|
|
95
|
-
yield from segments
|
|
81
|
+
yield table
|
|
96
82
|
|
|
97
83
|
|
|
98
84
|
class LeftHeading(Heading):
|
|
@@ -23,8 +23,8 @@ from klaude_code.const import (
|
|
|
23
23
|
STATUS_SHIMMER_BAND_HALF_WIDTH,
|
|
24
24
|
STATUS_SHIMMER_PADDING,
|
|
25
25
|
)
|
|
26
|
-
from klaude_code.
|
|
27
|
-
from klaude_code.
|
|
26
|
+
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
27
|
+
from klaude_code.tui.terminal.color import get_last_terminal_background_rgb
|
|
28
28
|
|
|
29
29
|
# Use an existing Rich spinner name; BreathingSpinner overrides its rendering
|
|
30
30
|
BREATHING_SPINNER_NAME = "dots"
|
|
@@ -138,6 +138,7 @@ class ThemeKey(str, Enum):
|
|
|
138
138
|
STATUS_TEXT = "spinner.status.text"
|
|
139
139
|
STATUS_TEXT_BOLD = "spinner.status.text.bold"
|
|
140
140
|
STATUS_TEXT_BOLD_ITALIC = "spinner.status.text.bold_italic"
|
|
141
|
+
STATUS_TOAST = "spinner.status.toast"
|
|
141
142
|
# STATUS
|
|
142
143
|
STATUS_HINT = "status.hint"
|
|
143
144
|
# USER_INPUT
|
|
@@ -262,6 +263,7 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
262
263
|
ThemeKey.STATUS_TEXT.value: palette.blue,
|
|
263
264
|
ThemeKey.STATUS_TEXT_BOLD.value: "bold " + palette.blue,
|
|
264
265
|
ThemeKey.STATUS_TEXT_BOLD_ITALIC.value: "bold italic " + palette.blue,
|
|
266
|
+
ThemeKey.STATUS_TOAST.value: "bold " + palette.yellow,
|
|
265
267
|
ThemeKey.STATUS_HINT.value: palette.grey2,
|
|
266
268
|
# REMINDER
|
|
267
269
|
ThemeKey.REMINDER.value: palette.grey1,
|
|
@@ -275,7 +277,7 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
275
277
|
ThemeKey.TOOL_RESULT.value: palette.grey_green,
|
|
276
278
|
ThemeKey.TOOL_RESULT_TREE_PREFIX.value: palette.grey3 + " dim",
|
|
277
279
|
ThemeKey.TOOL_RESULT_BOLD.value: "bold " + palette.grey_green,
|
|
278
|
-
ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.grey1,
|
|
280
|
+
ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.grey1 + " dim",
|
|
279
281
|
ThemeKey.TOOL_MARK.value: "bold",
|
|
280
282
|
ThemeKey.TOOL_APPROVED.value: palette.green + " bold reverse",
|
|
281
283
|
ThemeKey.TOOL_REJECTED.value: palette.red + " bold reverse",
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Any, cast
|
|
3
3
|
|
|
4
|
-
from rich import box
|
|
5
4
|
from rich.console import Group, RenderableType
|
|
6
5
|
from rich.json import JSON
|
|
7
|
-
from rich.
|
|
8
|
-
from rich.style import Style
|
|
6
|
+
from rich.style import Style, StyleType
|
|
9
7
|
from rich.text import Text
|
|
10
8
|
|
|
11
9
|
from klaude_code.const import SUB_AGENT_RESULT_MAX_LINES
|
|
12
10
|
from klaude_code.protocol import events, model
|
|
13
11
|
from klaude_code.protocol.sub_agent import get_sub_agent_profile_by_tool
|
|
14
|
-
from klaude_code.
|
|
15
|
-
from klaude_code.
|
|
16
|
-
from klaude_code.ui.rich.theme import ThemeKey
|
|
12
|
+
from klaude_code.tui.components.common import truncate_head
|
|
13
|
+
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
17
14
|
|
|
18
15
|
|
|
19
16
|
def _compact_schema_value(value: dict[str, Any]) -> str | list[Any] | dict[str, Any]:
|
|
@@ -83,13 +80,11 @@ def render_sub_agent_result(
|
|
|
83
80
|
result: str,
|
|
84
81
|
*,
|
|
85
82
|
code_theme: str,
|
|
86
|
-
style:
|
|
83
|
+
style: StyleType | None = None,
|
|
87
84
|
has_structured_output: bool = False,
|
|
88
85
|
description: str | None = None,
|
|
89
|
-
panel_style: Style | None = None,
|
|
90
86
|
) -> RenderableType:
|
|
91
87
|
stripped_result = result.strip()
|
|
92
|
-
result_panel_style = panel_style or ThemeKey.SUB_AGENT_RESULT_PANEL
|
|
93
88
|
|
|
94
89
|
# Extract agentId footer for separate styling
|
|
95
90
|
main_content, agent_id_footer = _extract_agent_id_footer(stripped_result)
|
|
@@ -106,15 +101,10 @@ def render_sub_agent_result(
|
|
|
106
101
|
JSON(stripped_result),
|
|
107
102
|
]
|
|
108
103
|
if description:
|
|
109
|
-
group_elements.insert(0,
|
|
104
|
+
group_elements.insert(0, Text(f"\n{description}", style=style or ""))
|
|
110
105
|
if agent_id_footer:
|
|
111
106
|
group_elements.append(Text(agent_id_footer, style=ThemeKey.SUB_AGENT_FOOTER))
|
|
112
|
-
return
|
|
113
|
-
Group(*group_elements),
|
|
114
|
-
box=box.SIMPLE,
|
|
115
|
-
border_style=ThemeKey.LINES,
|
|
116
|
-
style=result_panel_style,
|
|
117
|
-
)
|
|
107
|
+
return Group(*group_elements)
|
|
118
108
|
except json.JSONDecodeError:
|
|
119
109
|
# Fall back to markdown if not valid JSON
|
|
120
110
|
pass
|
|
@@ -122,42 +112,32 @@ def render_sub_agent_result(
|
|
|
122
112
|
if not stripped_result:
|
|
123
113
|
return Text()
|
|
124
114
|
|
|
125
|
-
# Add markdown heading if description is provided for non-structured output
|
|
126
|
-
if description:
|
|
127
|
-
stripped_result = f"# {description}\n\n{stripped_result}"
|
|
128
|
-
|
|
129
115
|
lines = stripped_result.splitlines()
|
|
130
116
|
if len(lines) > SUB_AGENT_RESULT_MAX_LINES:
|
|
131
117
|
hidden_count = len(lines) - SUB_AGENT_RESULT_MAX_LINES
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
118
|
+
tail_text = "\n".join(lines[-SUB_AGENT_RESULT_MAX_LINES:])
|
|
119
|
+
truncated_elements: list[RenderableType] = []
|
|
120
|
+
# Add description heading separately so it won't be truncated
|
|
121
|
+
if description:
|
|
122
|
+
truncated_elements.append(Text(f"---\n{description}", style=style or ""))
|
|
123
|
+
truncated_elements.append(
|
|
138
124
|
Text(
|
|
139
|
-
f"( … more {hidden_count} lines
|
|
125
|
+
f"( … more {hidden_count} lines)",
|
|
140
126
|
style=ThemeKey.TOOL_RESULT_TRUNCATED,
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
truncated_elements.append(Text(tail_text, style=style or ""))
|
|
144
130
|
if agent_id_footer:
|
|
145
131
|
truncated_elements.append(Text(agent_id_footer, style=ThemeKey.SUB_AGENT_FOOTER))
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
normal_elements: list[RenderableType] = [NoInsetMarkdown(stripped_result, code_theme=code_theme)]
|
|
132
|
+
return Group(*truncated_elements)
|
|
133
|
+
|
|
134
|
+
# No truncation needed - add description heading if provided
|
|
135
|
+
if description:
|
|
136
|
+
stripped_result = f"\n# {description}\n\n{stripped_result}"
|
|
137
|
+
normal_elements: list[RenderableType] = [Text(stripped_result)]
|
|
153
138
|
if agent_id_footer:
|
|
154
139
|
normal_elements.append(Text(agent_id_footer, style=ThemeKey.SUB_AGENT_FOOTER))
|
|
155
|
-
return
|
|
156
|
-
Group(*normal_elements),
|
|
157
|
-
box=box.SIMPLE,
|
|
158
|
-
border_style=ThemeKey.LINES,
|
|
159
|
-
style=result_panel_style,
|
|
160
|
-
)
|
|
140
|
+
return Group(*normal_elements)
|
|
161
141
|
|
|
162
142
|
|
|
163
143
|
def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAgentState | None:
|
|
@@ -5,9 +5,9 @@ from rich.padding import Padding
|
|
|
5
5
|
from rich.text import Text
|
|
6
6
|
|
|
7
7
|
from klaude_code.const import MARKDOWN_RIGHT_MARGIN
|
|
8
|
-
from klaude_code.
|
|
9
|
-
from klaude_code.
|
|
10
|
-
from klaude_code.
|
|
8
|
+
from klaude_code.tui.components.common import create_grid
|
|
9
|
+
from klaude_code.tui.components.rich.markdown import ThinkingMarkdown
|
|
10
|
+
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
11
11
|
|
|
12
12
|
# UI markers
|
|
13
13
|
THINKING_MESSAGE_MARK = "∴"
|
|
@@ -17,14 +17,14 @@ from klaude_code.const import (
|
|
|
17
17
|
)
|
|
18
18
|
from klaude_code.protocol import events, model, tools
|
|
19
19
|
from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
|
|
20
|
-
from klaude_code.
|
|
21
|
-
from klaude_code.
|
|
22
|
-
from klaude_code.
|
|
23
|
-
from klaude_code.
|
|
24
|
-
from klaude_code.
|
|
25
|
-
from klaude_code.
|
|
26
|
-
from klaude_code.
|
|
27
|
-
from klaude_code.
|
|
20
|
+
from klaude_code.tui.components import diffs as r_diffs
|
|
21
|
+
from klaude_code.tui.components import mermaid_viewer as r_mermaid_viewer
|
|
22
|
+
from klaude_code.tui.components.bash_syntax import highlight_bash_command
|
|
23
|
+
from klaude_code.tui.components.common import create_grid, truncate_middle
|
|
24
|
+
from klaude_code.tui.components.rich.code_panel import CodePanel
|
|
25
|
+
from klaude_code.tui.components.rich.markdown import NoInsetMarkdown
|
|
26
|
+
from klaude_code.tui.components.rich.quote import TreeQuote
|
|
27
|
+
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
28
28
|
|
|
29
29
|
# Tool markers (Unicode symbols for UI display)
|
|
30
30
|
MARK_GENERIC = "⚒"
|
|
@@ -487,7 +487,7 @@ def render_mermaid_tool_result(
|
|
|
487
487
|
*,
|
|
488
488
|
session_id: str | None = None,
|
|
489
489
|
) -> RenderableType:
|
|
490
|
-
from klaude_code.
|
|
490
|
+
from klaude_code.tui.terminal import supports_osc8_hyperlinks
|
|
491
491
|
|
|
492
492
|
link_info = _extract_mermaid_link(tr.ui_extra)
|
|
493
493
|
if link_info is None:
|
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from collections.abc import Callable
|
|
3
2
|
|
|
4
3
|
from rich.console import Group, RenderableType
|
|
5
4
|
from rich.text import Text
|
|
6
5
|
|
|
7
6
|
from klaude_code.skill import get_available_skills
|
|
8
|
-
from klaude_code.
|
|
9
|
-
from klaude_code.
|
|
10
|
-
|
|
11
|
-
# Module-level command name checker. Set by cli/runtime.py on startup.
|
|
12
|
-
_command_name_checker: Callable[[str], bool] | None = None
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def set_command_name_checker(checker: Callable[[str], bool]) -> None:
|
|
16
|
-
"""Set the command name validation function (called from runtime layer)."""
|
|
17
|
-
global _command_name_checker
|
|
18
|
-
_command_name_checker = checker
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def is_slash_command_name(name: str) -> bool:
|
|
22
|
-
"""Check if name is a valid slash command using the injected checker."""
|
|
23
|
-
if _command_name_checker is None:
|
|
24
|
-
return False
|
|
25
|
-
return _command_name_checker(name)
|
|
26
|
-
|
|
7
|
+
from klaude_code.tui.command import is_slash_command_name
|
|
8
|
+
from klaude_code.tui.components.common import create_grid
|
|
9
|
+
from klaude_code.tui.components.rich.theme import ThemeKey
|
|
27
10
|
|
|
28
11
|
# Match @-file patterns only when they appear at the beginning of the line
|
|
29
12
|
# or immediately after whitespace, to avoid treating mid-word email-like
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
from collections.abc import Coroutine
|
|
6
|
+
from typing import Any, override
|
|
7
|
+
|
|
8
|
+
from klaude_code.protocol import events
|
|
9
|
+
from klaude_code.tui.machine import DisplayStateMachine
|
|
10
|
+
from klaude_code.tui.renderer import TUICommandRenderer
|
|
11
|
+
from klaude_code.tui.terminal.notifier import TerminalNotifier
|
|
12
|
+
from klaude_code.ui.core.display import DisplayABC
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TUIDisplay(DisplayABC):
|
|
16
|
+
"""Interactive terminal display using Rich for rendering."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, theme: str | None = None, notifier: TerminalNotifier | None = None):
|
|
19
|
+
self._notifier = notifier or TerminalNotifier()
|
|
20
|
+
self._machine = DisplayStateMachine()
|
|
21
|
+
self._renderer = TUICommandRenderer(theme=theme, notifier=self._notifier)
|
|
22
|
+
|
|
23
|
+
self._sigint_toast_clear_handle: asyncio.Handle | None = None
|
|
24
|
+
self._bg_tasks: set[asyncio.Task[None]] = set()
|
|
25
|
+
|
|
26
|
+
def _create_bg_task(self, coro: Coroutine[Any, Any, None]) -> None:
|
|
27
|
+
task = asyncio.create_task(coro)
|
|
28
|
+
self._bg_tasks.add(task)
|
|
29
|
+
task.add_done_callback(self._bg_tasks.discard)
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
async def consume_event(self, event: events.Event) -> None:
|
|
33
|
+
commands = self._machine.transition(event)
|
|
34
|
+
await self._renderer.execute(commands)
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
async def start(self) -> None:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
async def stop(self) -> None:
|
|
42
|
+
if self._sigint_toast_clear_handle is not None:
|
|
43
|
+
with contextlib.suppress(Exception):
|
|
44
|
+
self._sigint_toast_clear_handle.cancel()
|
|
45
|
+
self._sigint_toast_clear_handle = None
|
|
46
|
+
|
|
47
|
+
for task in list(self._bg_tasks):
|
|
48
|
+
with contextlib.suppress(Exception):
|
|
49
|
+
task.cancel()
|
|
50
|
+
self._bg_tasks.clear()
|
|
51
|
+
|
|
52
|
+
await self._renderer.stop()
|
|
53
|
+
|
|
54
|
+
with contextlib.suppress(Exception):
|
|
55
|
+
self._renderer.stop_bottom_live()
|
|
56
|
+
|
|
57
|
+
def show_sigint_exit_toast(self, *, window_seconds: float = 2.0) -> None:
|
|
58
|
+
"""Show a transient Ctrl+C hint in the TUI status line."""
|
|
59
|
+
|
|
60
|
+
async def _apply_show() -> None:
|
|
61
|
+
await self._renderer.execute(self._machine.show_sigint_exit_toast())
|
|
62
|
+
|
|
63
|
+
async def _apply_clear() -> None:
|
|
64
|
+
await self._renderer.execute(self._machine.clear_sigint_exit_toast())
|
|
65
|
+
|
|
66
|
+
loop = asyncio.get_running_loop()
|
|
67
|
+
self._create_bg_task(_apply_show())
|
|
68
|
+
|
|
69
|
+
if self._sigint_toast_clear_handle is not None:
|
|
70
|
+
with contextlib.suppress(Exception):
|
|
71
|
+
self._sigint_toast_clear_handle.cancel()
|
|
72
|
+
self._sigint_toast_clear_handle = None
|
|
73
|
+
|
|
74
|
+
def _schedule_clear() -> None:
|
|
75
|
+
self._create_bg_task(_apply_clear())
|
|
76
|
+
|
|
77
|
+
self._sigint_toast_clear_handle = loop.call_later(window_seconds, _schedule_clear)
|
|
78
|
+
|
|
79
|
+
def hide_progress_ui(self) -> None:
|
|
80
|
+
"""Stop transient Rich UI elements before prompt-toolkit takes control."""
|
|
81
|
+
|
|
82
|
+
with contextlib.suppress(Exception):
|
|
83
|
+
self._renderer.spinner_stop()
|
|
84
|
+
with contextlib.suppress(Exception):
|
|
85
|
+
self._renderer.stop_bottom_live()
|
|
@@ -28,8 +28,8 @@ from prompt_toolkit.document import Document
|
|
|
28
28
|
from prompt_toolkit.formatted_text import FormattedText
|
|
29
29
|
|
|
30
30
|
from klaude_code.const import COMPLETER_CACHE_TTL_SEC, COMPLETER_CMD_TIMEOUT_SEC, COMPLETER_DEBOUNCE_SEC
|
|
31
|
+
from klaude_code.log import DebugType, log_debug
|
|
31
32
|
from klaude_code.protocol.commands import CommandInfo
|
|
32
|
-
from klaude_code.trace.log import DebugType, log_debug
|
|
33
33
|
|
|
34
34
|
# Pattern to match @token for completion refresh (used by key bindings).
|
|
35
35
|
# Supports both plain tokens like `@src/file.py` and quoted tokens like
|
|
@@ -35,13 +35,13 @@ from klaude_code.config.thinking import (
|
|
|
35
35
|
from klaude_code.protocol import llm_param
|
|
36
36
|
from klaude_code.protocol.commands import CommandInfo
|
|
37
37
|
from klaude_code.protocol.message import UserInputPayload
|
|
38
|
+
from klaude_code.tui.components.user_input import USER_MESSAGE_MARK
|
|
39
|
+
from klaude_code.tui.input.clipboard import capture_clipboard_tag, copy_to_clipboard, extract_images_from_text
|
|
40
|
+
from klaude_code.tui.input.completers import AT_TOKEN_PATTERN, create_repl_completer
|
|
41
|
+
from klaude_code.tui.input.key_bindings import create_key_bindings
|
|
42
|
+
from klaude_code.tui.terminal.color import is_light_terminal_background
|
|
43
|
+
from klaude_code.tui.terminal.selector import SelectItem, SelectOverlay, build_model_select_items
|
|
38
44
|
from klaude_code.ui.core.input import InputProviderABC
|
|
39
|
-
from klaude_code.ui.modes.repl.clipboard import capture_clipboard_tag, copy_to_clipboard, extract_images_from_text
|
|
40
|
-
from klaude_code.ui.modes.repl.completers import AT_TOKEN_PATTERN, create_repl_completer
|
|
41
|
-
from klaude_code.ui.modes.repl.key_bindings import create_key_bindings
|
|
42
|
-
from klaude_code.ui.renderers.user_input import USER_MESSAGE_MARK
|
|
43
|
-
from klaude_code.ui.terminal.color import is_light_terminal_background
|
|
44
|
-
from klaude_code.ui.terminal.selector import SelectItem, SelectOverlay, build_model_select_items
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class REPLStatusSnapshot(NamedTuple):
|