klaude-code 1.9.0__py3-none-any.whl → 2.0.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/base.py +2 -6
- klaude_code/cli/auth_cmd.py +4 -4
- klaude_code/cli/list_model.py +1 -1
- klaude_code/cli/main.py +1 -1
- klaude_code/cli/runtime.py +7 -5
- klaude_code/cli/self_update.py +1 -1
- klaude_code/cli/session_cmd.py +1 -1
- klaude_code/command/clear_cmd.py +6 -2
- klaude_code/command/command_abc.py +2 -2
- klaude_code/command/debug_cmd.py +4 -4
- klaude_code/command/export_cmd.py +2 -2
- klaude_code/command/export_online_cmd.py +12 -12
- klaude_code/command/fork_session_cmd.py +29 -23
- klaude_code/command/help_cmd.py +4 -4
- klaude_code/command/model_cmd.py +4 -4
- klaude_code/command/model_select.py +1 -1
- klaude_code/command/prompt-commit.md +11 -2
- klaude_code/command/prompt_command.py +3 -3
- klaude_code/command/refresh_cmd.py +2 -2
- klaude_code/command/registry.py +7 -5
- klaude_code/command/release_notes_cmd.py +4 -4
- klaude_code/command/resume_cmd.py +15 -11
- klaude_code/command/status_cmd.py +4 -4
- klaude_code/command/terminal_setup_cmd.py +8 -8
- klaude_code/command/thinking_cmd.py +4 -4
- klaude_code/config/assets/builtin_config.yaml +16 -0
- klaude_code/config/builtin_config.py +16 -5
- klaude_code/config/config.py +7 -2
- klaude_code/const.py +146 -91
- klaude_code/core/agent.py +3 -12
- klaude_code/core/executor.py +21 -13
- klaude_code/core/manager/sub_agent_manager.py +71 -7
- klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
- klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
- klaude_code/core/reminders.py +88 -69
- klaude_code/core/task.py +44 -45
- klaude_code/core/tool/file/apply_patch_tool.py +9 -9
- klaude_code/core/tool/file/diff_builder.py +3 -5
- klaude_code/core/tool/file/edit_tool.py +23 -23
- klaude_code/core/tool/file/move_tool.py +43 -43
- klaude_code/core/tool/file/read_tool.py +44 -39
- klaude_code/core/tool/file/write_tool.py +14 -14
- klaude_code/core/tool/report_back_tool.py +4 -4
- klaude_code/core/tool/shell/bash_tool.py +23 -23
- klaude_code/core/tool/skill/skill_tool.py +7 -7
- klaude_code/core/tool/sub_agent_tool.py +38 -9
- klaude_code/core/tool/todo/todo_write_tool.py +8 -8
- klaude_code/core/tool/todo/update_plan_tool.py +6 -6
- klaude_code/core/tool/tool_abc.py +2 -2
- klaude_code/core/tool/tool_context.py +27 -0
- klaude_code/core/tool/tool_runner.py +88 -42
- klaude_code/core/tool/truncation.py +38 -20
- klaude_code/core/tool/web/mermaid_tool.py +6 -7
- klaude_code/core/tool/web/web_fetch_tool.py +68 -30
- klaude_code/core/tool/web/web_search_tool.py +15 -17
- klaude_code/core/turn.py +120 -73
- klaude_code/llm/anthropic/client.py +79 -44
- klaude_code/llm/anthropic/input.py +116 -108
- klaude_code/llm/bedrock/client.py +8 -5
- klaude_code/llm/claude/client.py +18 -8
- klaude_code/llm/client.py +4 -3
- klaude_code/llm/codex/client.py +15 -9
- klaude_code/llm/google/client.py +122 -60
- klaude_code/llm/google/input.py +94 -108
- klaude_code/llm/image.py +123 -0
- klaude_code/llm/input_common.py +136 -189
- klaude_code/llm/openai_compatible/client.py +17 -7
- klaude_code/llm/openai_compatible/input.py +36 -66
- klaude_code/llm/openai_compatible/stream.py +119 -67
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
- klaude_code/llm/openrouter/client.py +34 -9
- klaude_code/llm/openrouter/input.py +63 -64
- klaude_code/llm/openrouter/reasoning.py +22 -24
- klaude_code/llm/registry.py +20 -17
- klaude_code/llm/responses/client.py +107 -45
- klaude_code/llm/responses/input.py +115 -98
- klaude_code/llm/usage.py +52 -25
- klaude_code/protocol/__init__.py +1 -0
- klaude_code/protocol/events.py +16 -12
- klaude_code/protocol/llm_param.py +20 -2
- klaude_code/protocol/message.py +250 -0
- klaude_code/protocol/model.py +94 -281
- klaude_code/protocol/op.py +2 -2
- klaude_code/protocol/sub_agent/__init__.py +1 -0
- klaude_code/protocol/sub_agent/explore.py +10 -0
- klaude_code/protocol/sub_agent/image_gen.py +119 -0
- klaude_code/protocol/sub_agent/task.py +10 -0
- klaude_code/protocol/sub_agent/web.py +10 -0
- klaude_code/session/codec.py +6 -6
- klaude_code/session/export.py +261 -62
- klaude_code/session/selector.py +7 -24
- klaude_code/session/session.py +126 -54
- klaude_code/session/store.py +5 -32
- klaude_code/session/templates/export_session.html +1 -1
- klaude_code/session/templates/mermaid_viewer.html +1 -1
- klaude_code/trace/log.py +11 -6
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +1 -8
- klaude_code/ui/modes/debug/display.py +2 -2
- klaude_code/ui/modes/repl/clipboard.py +2 -2
- klaude_code/ui/modes/repl/completers.py +18 -10
- klaude_code/ui/modes/repl/event_handler.py +136 -127
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
- klaude_code/ui/modes/repl/key_bindings.py +1 -1
- klaude_code/ui/modes/repl/renderer.py +107 -15
- klaude_code/ui/renderers/assistant.py +2 -2
- klaude_code/ui/renderers/common.py +65 -7
- klaude_code/ui/renderers/developer.py +7 -6
- klaude_code/ui/renderers/diffs.py +11 -11
- klaude_code/ui/renderers/mermaid_viewer.py +49 -2
- klaude_code/ui/renderers/metadata.py +33 -5
- klaude_code/ui/renderers/sub_agent.py +57 -16
- klaude_code/ui/renderers/thinking.py +37 -2
- klaude_code/ui/renderers/tools.py +180 -165
- klaude_code/ui/rich/live.py +3 -1
- klaude_code/ui/rich/markdown.py +39 -7
- klaude_code/ui/rich/quote.py +76 -1
- klaude_code/ui/rich/status.py +14 -8
- klaude_code/ui/rich/theme.py +8 -2
- klaude_code/ui/terminal/image.py +34 -0
- klaude_code/ui/terminal/notifier.py +2 -1
- klaude_code/ui/terminal/progress_bar.py +4 -4
- klaude_code/ui/terminal/selector.py +22 -4
- klaude_code/ui/utils/common.py +11 -2
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/METADATA +4 -2
- klaude_code-2.0.0.dist-info/RECORD +229 -0
- klaude_code-1.9.0.dist-info/RECORD +0 -224
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/entry_points.txt +0 -0
klaude_code/ui/rich/live.py
CHANGED
|
@@ -7,6 +7,8 @@ from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
|
|
7
7
|
from rich.live import Live
|
|
8
8
|
from rich.segment import Segment
|
|
9
9
|
|
|
10
|
+
from klaude_code.const import CROP_ABOVE_LIVE_REFRESH_PER_SECOND
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class CropAbove:
|
|
12
14
|
def __init__(self, renderable: RenderableType, style: str = "") -> None:
|
|
@@ -33,7 +35,7 @@ class CropAboveLive(Live):
|
|
|
33
35
|
renderable: RenderableType | None = None,
|
|
34
36
|
*,
|
|
35
37
|
console: Console | None = None,
|
|
36
|
-
refresh_per_second: float =
|
|
38
|
+
refresh_per_second: float = CROP_ABOVE_LIVE_REFRESH_PER_SECOND,
|
|
37
39
|
transient: bool = False,
|
|
38
40
|
get_renderable: Any | None = None,
|
|
39
41
|
style: str = "",
|
klaude_code/ui/rich/markdown.py
CHANGED
|
@@ -18,7 +18,7 @@ from rich.table import Table
|
|
|
18
18
|
from rich.text import Text
|
|
19
19
|
from rich.theme import Theme
|
|
20
20
|
|
|
21
|
-
from klaude_code import
|
|
21
|
+
from klaude_code.const import MARKDOWN_RIGHT_MARGIN, MARKDOWN_STREAM_LIVE_REPAINT_ENABLED, UI_REFRESH_RATE_FPS
|
|
22
22
|
from klaude_code.ui.rich.code_panel import CodePanel
|
|
23
23
|
|
|
24
24
|
|
|
@@ -55,8 +55,6 @@ class Divider(MarkdownElement):
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
class MarkdownTable(TableElement):
|
|
58
|
-
"""A table element with MINIMAL_HEAVY_HEAD box style."""
|
|
59
|
-
|
|
60
58
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
61
59
|
table = Table(box=box.MARKDOWN, border_style=console.get_style("markdown.table.border"))
|
|
62
60
|
|
|
@@ -69,7 +67,27 @@ class MarkdownTable(TableElement):
|
|
|
69
67
|
row_content = [element.content for element in row.cells]
|
|
70
68
|
table.add_row(*row_content)
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
# Render table and strip top/bottom blank lines that MARKDOWN box adds
|
|
71
|
+
segments = list(console.render(table, options))
|
|
72
|
+
|
|
73
|
+
# Skip leading blank line (before first newline)
|
|
74
|
+
first_newline_idx = next((i for i, s in enumerate(segments) if s.text == "\n"), None)
|
|
75
|
+
if first_newline_idx is not None:
|
|
76
|
+
first_line = "".join(s.text for s in segments[:first_newline_idx])
|
|
77
|
+
if not first_line.strip():
|
|
78
|
+
segments = segments[first_newline_idx + 1 :]
|
|
79
|
+
|
|
80
|
+
# Skip trailing blank line (after last newline)
|
|
81
|
+
while len(segments) >= 2 and segments[-1].text == "\n":
|
|
82
|
+
prev_newline = next((i for i in range(len(segments) - 2, -1, -1) if segments[i].text == "\n"), None)
|
|
83
|
+
if prev_newline is not None:
|
|
84
|
+
between = "".join(s.text for s in segments[prev_newline + 1 : -1])
|
|
85
|
+
if not between.strip():
|
|
86
|
+
segments = segments[: prev_newline + 1]
|
|
87
|
+
continue
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
yield from segments
|
|
73
91
|
|
|
74
92
|
|
|
75
93
|
class LeftHeading(Heading):
|
|
@@ -135,7 +153,7 @@ class MarkdownStream:
|
|
|
135
153
|
mark: str | None = None,
|
|
136
154
|
mark_style: StyleType | None = None,
|
|
137
155
|
left_margin: int = 0,
|
|
138
|
-
right_margin: int =
|
|
156
|
+
right_margin: int = MARKDOWN_RIGHT_MARGIN,
|
|
139
157
|
markdown_class: Callable[..., Markdown] | None = None,
|
|
140
158
|
) -> None:
|
|
141
159
|
"""Initialize the markdown stream.
|
|
@@ -162,7 +180,7 @@ class MarkdownStream:
|
|
|
162
180
|
|
|
163
181
|
# Streaming control
|
|
164
182
|
self.when: float = 0.0 # Timestamp of last update
|
|
165
|
-
self.min_delay: float = 1.0 /
|
|
183
|
+
self.min_delay: float = 1.0 / UI_REFRESH_RATE_FPS
|
|
166
184
|
self._parser: MarkdownIt = MarkdownIt("commonmark")
|
|
167
185
|
|
|
168
186
|
self.theme = theme
|
|
@@ -201,6 +219,20 @@ class MarkdownStream:
|
|
|
201
219
|
|
|
202
220
|
last = top_level[-1]
|
|
203
221
|
assert last.map is not None
|
|
222
|
+
|
|
223
|
+
# When the buffer ends mid-line, markdown-it-py can temporarily classify
|
|
224
|
+
# some lines as a thematic break (hr). For example, a trailing "- --"
|
|
225
|
+
# parses as an hr, but appending a non-hr character ("- --0") turns it
|
|
226
|
+
# into a list item, which should belong to the previous list block.
|
|
227
|
+
#
|
|
228
|
+
# Because stable_line is clamped to be monotonic, advancing to the hr's
|
|
229
|
+
# start line would be irreversible and can split a list across
|
|
230
|
+
# stable/live, producing a render mismatch.
|
|
231
|
+
if last.type == "hr" and not text.endswith("\n"):
|
|
232
|
+
prev = top_level[-2]
|
|
233
|
+
assert prev.map is not None
|
|
234
|
+
return max(prev.map[0], 0)
|
|
235
|
+
|
|
204
236
|
start_line = last.map[0]
|
|
205
237
|
return max(start_line, 0)
|
|
206
238
|
|
|
@@ -414,7 +446,7 @@ class MarkdownStream:
|
|
|
414
446
|
self._live_sink(None)
|
|
415
447
|
return
|
|
416
448
|
|
|
417
|
-
if
|
|
449
|
+
if MARKDOWN_STREAM_LIVE_REPAINT_ENABLED and self._live_sink is not None:
|
|
418
450
|
apply_mark_live = self._stable_source_line_count == 0
|
|
419
451
|
live_lines = self._render_markdown_to_lines(live_source, apply_mark=apply_mark_live)
|
|
420
452
|
|
klaude_code/ui/rich/quote.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
2
2
|
|
|
3
3
|
from rich.console import Console, ConsoleOptions, RenderResult
|
|
4
4
|
from rich.segment import Segment
|
|
5
5
|
from rich.style import Style
|
|
6
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from rich.console import RenderableType
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
class Quote:
|
|
9
12
|
"""Wrapper to add quote prefix to any content"""
|
|
@@ -32,3 +35,75 @@ class Quote:
|
|
|
32
35
|
yield prefix_segment
|
|
33
36
|
yield from line
|
|
34
37
|
yield new_line
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TreeQuote:
|
|
41
|
+
"""Wrapper to add a tree-style prefix to any content."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
content: Any,
|
|
46
|
+
*,
|
|
47
|
+
prefix_first: str | None = None,
|
|
48
|
+
prefix_middle: str = "│ ",
|
|
49
|
+
prefix_last: str = "└ ",
|
|
50
|
+
style: str | Style = "magenta",
|
|
51
|
+
style_first: str | Style | None = None,
|
|
52
|
+
):
|
|
53
|
+
self.content = content
|
|
54
|
+
self.prefix_first = prefix_first
|
|
55
|
+
self.prefix_middle = prefix_middle
|
|
56
|
+
self.prefix_last = prefix_last
|
|
57
|
+
self.style = style
|
|
58
|
+
self.style_first = style_first
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def for_tool_call(cls, content: "RenderableType", *, mark: str, style: str, style_first: str) -> Self:
|
|
62
|
+
"""Create a tree quote for tool call display.
|
|
63
|
+
|
|
64
|
+
The mark appears on the first line, with continuation lines using "│ ".
|
|
65
|
+
"""
|
|
66
|
+
return cls(
|
|
67
|
+
content,
|
|
68
|
+
prefix_first=f"{mark} ",
|
|
69
|
+
prefix_middle="│ ",
|
|
70
|
+
prefix_last="│ ",
|
|
71
|
+
style=style,
|
|
72
|
+
style_first=style_first,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def for_tool_result(
|
|
77
|
+
cls, content: "RenderableType", *, is_last: bool, style: str = "tool.result.tree_prefix"
|
|
78
|
+
) -> Self:
|
|
79
|
+
"""Create a tree quote for tool result display.
|
|
80
|
+
|
|
81
|
+
Uses "└ " for the last result in a turn, "│ " otherwise.
|
|
82
|
+
"""
|
|
83
|
+
return cls(content, prefix_last="└ " if is_last else "│ ", style=style)
|
|
84
|
+
|
|
85
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
86
|
+
# Reduce width to leave space for prefix
|
|
87
|
+
prefix_width = max(
|
|
88
|
+
len(self.prefix_middle),
|
|
89
|
+
len(self.prefix_last),
|
|
90
|
+
len(self.prefix_first) if self.prefix_first is not None else 0,
|
|
91
|
+
)
|
|
92
|
+
render_options = options.update(width=options.max_width - prefix_width)
|
|
93
|
+
|
|
94
|
+
quote_style = console.get_style(self.style) if isinstance(self.style, str) else self.style
|
|
95
|
+
first_style = console.get_style(self.style_first) if isinstance(self.style_first, str) else self.style_first
|
|
96
|
+
|
|
97
|
+
new_line = Segment("\n")
|
|
98
|
+
lines = console.render_lines(self.content, render_options)
|
|
99
|
+
line_count = len(lines)
|
|
100
|
+
|
|
101
|
+
for idx, line in enumerate(lines):
|
|
102
|
+
if idx == 0 and self.prefix_first is not None:
|
|
103
|
+
yield Segment(self.prefix_first, first_style or quote_style)
|
|
104
|
+
else:
|
|
105
|
+
is_last = idx == line_count - 1
|
|
106
|
+
prefix = self.prefix_last if is_last else self.prefix_middle
|
|
107
|
+
yield Segment(prefix, quote_style)
|
|
108
|
+
yield from line
|
|
109
|
+
yield new_line
|
klaude_code/ui/rich/status.py
CHANGED
|
@@ -16,7 +16,13 @@ from rich.style import Style
|
|
|
16
16
|
from rich.table import Table
|
|
17
17
|
from rich.text import Text
|
|
18
18
|
|
|
19
|
-
from klaude_code import
|
|
19
|
+
from klaude_code.const import (
|
|
20
|
+
SPINNER_BREATH_PERIOD_SECONDS,
|
|
21
|
+
STATUS_HINT_TEXT,
|
|
22
|
+
STATUS_SHIMMER_ALPHA_SCALE,
|
|
23
|
+
STATUS_SHIMMER_BAND_HALF_WIDTH,
|
|
24
|
+
STATUS_SHIMMER_PADDING,
|
|
25
|
+
)
|
|
20
26
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
21
27
|
from klaude_code.ui.terminal.color import get_last_terminal_background_rgb
|
|
22
28
|
|
|
@@ -91,7 +97,7 @@ def current_hint_text(*, min_time_width: int = 0) -> str:
|
|
|
91
97
|
|
|
92
98
|
# Keep the signature stable; min_time_width is intentionally ignored.
|
|
93
99
|
_ = min_time_width
|
|
94
|
-
return
|
|
100
|
+
return STATUS_HINT_TEXT
|
|
95
101
|
|
|
96
102
|
|
|
97
103
|
def current_elapsed_text(*, min_time_width: int = 0) -> str | None:
|
|
@@ -152,18 +158,18 @@ def _shimmer_profile(main_text: str) -> list[tuple[str, float]]:
|
|
|
152
158
|
if not chars:
|
|
153
159
|
return []
|
|
154
160
|
|
|
155
|
-
padding =
|
|
161
|
+
padding = STATUS_SHIMMER_PADDING
|
|
156
162
|
char_count = len(chars)
|
|
157
163
|
period = char_count + padding * 2
|
|
158
164
|
|
|
159
165
|
# Use same period as breathing spinner for visual consistency
|
|
160
|
-
sweep_seconds = max(
|
|
166
|
+
sweep_seconds = max(SPINNER_BREATH_PERIOD_SECONDS, 0.1)
|
|
161
167
|
|
|
162
168
|
elapsed = _elapsed_since_start()
|
|
163
169
|
# Complete one full sweep in sweep_seconds, regardless of text length
|
|
164
170
|
pos_f = (elapsed / sweep_seconds % 1.0) * period
|
|
165
171
|
pos = int(pos_f)
|
|
166
|
-
band_half_width =
|
|
172
|
+
band_half_width = STATUS_SHIMMER_BAND_HALF_WIDTH
|
|
167
173
|
|
|
168
174
|
profile: list[tuple[str, float]] = []
|
|
169
175
|
for index, ch in enumerate(chars):
|
|
@@ -189,7 +195,7 @@ def _shimmer_style(console: Console, base_style: Style, intensity: float) -> Sty
|
|
|
189
195
|
if intensity <= 0.0:
|
|
190
196
|
return base_style
|
|
191
197
|
|
|
192
|
-
alpha = max(0.0, min(1.0, intensity *
|
|
198
|
+
alpha = max(0.0, min(1.0, intensity * STATUS_SHIMMER_ALPHA_SCALE))
|
|
193
199
|
|
|
194
200
|
base_color = base_style.color or Color.default()
|
|
195
201
|
base_triplet = base_color.get_truecolor()
|
|
@@ -213,7 +219,7 @@ def _breathing_intensity() -> float:
|
|
|
213
219
|
then returning to 0, giving a subtle "breathing" effect.
|
|
214
220
|
"""
|
|
215
221
|
|
|
216
|
-
period = max(
|
|
222
|
+
period = max(SPINNER_BREATH_PERIOD_SECONDS, 0.1)
|
|
217
223
|
elapsed = _elapsed_since_start()
|
|
218
224
|
phase = (elapsed % period) / period
|
|
219
225
|
return 0.5 * (1.0 - math.cos(2.0 * math.pi * phase))
|
|
@@ -224,7 +230,7 @@ def _breathing_glyph() -> str:
|
|
|
224
230
|
|
|
225
231
|
Alternates between glyphs at each breath cycle (when intensity reaches 0).
|
|
226
232
|
"""
|
|
227
|
-
period = max(
|
|
233
|
+
period = max(SPINNER_BREATH_PERIOD_SECONDS, 0.1)
|
|
228
234
|
elapsed = _elapsed_since_start()
|
|
229
235
|
cycle = int(elapsed / period)
|
|
230
236
|
return BREATHING_SPINNER_GLYPHS[cycle % len(BREATHING_SPINNER_GLYPHS)]
|
klaude_code/ui/rich/theme.py
CHANGED
|
@@ -124,6 +124,7 @@ class ThemeKey(str, Enum):
|
|
|
124
124
|
# ERROR
|
|
125
125
|
ERROR = "error"
|
|
126
126
|
ERROR_BOLD = "error.bold"
|
|
127
|
+
ERROR_DIM = "error.dim"
|
|
127
128
|
INTERRUPT = "interrupt"
|
|
128
129
|
# METADATA
|
|
129
130
|
METADATA = "metadata"
|
|
@@ -154,6 +155,7 @@ class ThemeKey(str, Enum):
|
|
|
154
155
|
TOOL_PARAM = "tool.param"
|
|
155
156
|
TOOL_PARAM_BOLD = "tool.param.bold"
|
|
156
157
|
TOOL_RESULT = "tool.result"
|
|
158
|
+
TOOL_RESULT_TREE_PREFIX = "tool.result.tree_prefix"
|
|
157
159
|
TOOL_RESULT_TRUNCATED = "tool.result.truncated"
|
|
158
160
|
TOOL_RESULT_BOLD = "tool.result.bold"
|
|
159
161
|
TOOL_MARK = "tool.mark"
|
|
@@ -161,6 +163,7 @@ class ThemeKey(str, Enum):
|
|
|
161
163
|
TOOL_REJECTED = "tool.rejected"
|
|
162
164
|
TOOL_TIMEOUT = "tool.timeout"
|
|
163
165
|
TOOL_RESULT_MERMAID = "tool.result.mermaid"
|
|
166
|
+
SUB_AGENT_FOOTER = "sub_agent.footer"
|
|
164
167
|
# BASH SYNTAX
|
|
165
168
|
BASH_COMMAND = "bash.command"
|
|
166
169
|
BASH_ARGUMENT = "bash.argument"
|
|
@@ -200,7 +203,6 @@ class ThemeKey(str, Enum):
|
|
|
200
203
|
CONFIG_MODEL_ID = "config.model.id"
|
|
201
204
|
CONFIG_PARAM_LABEL = "config.param.label"
|
|
202
205
|
|
|
203
|
-
|
|
204
206
|
def __str__(self) -> str:
|
|
205
207
|
return self.value
|
|
206
208
|
|
|
@@ -217,6 +219,7 @@ class Themes:
|
|
|
217
219
|
|
|
218
220
|
def get_theme(theme: str | None = None) -> Themes:
|
|
219
221
|
palette = LIGHT_PALETTE if theme == "light" else DARK_PALETTE
|
|
222
|
+
|
|
220
223
|
return Themes(
|
|
221
224
|
app_theme=Theme(
|
|
222
225
|
styles={
|
|
@@ -235,6 +238,7 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
235
238
|
# ERROR
|
|
236
239
|
ThemeKey.ERROR.value: palette.red,
|
|
237
240
|
ThemeKey.ERROR_BOLD.value: "bold " + palette.red,
|
|
241
|
+
ThemeKey.ERROR_DIM.value: "dim " + palette.red,
|
|
238
242
|
ThemeKey.INTERRUPT.value: "reverse bold " + palette.red,
|
|
239
243
|
# USER_INPUT
|
|
240
244
|
ThemeKey.USER_INPUT.value: palette.magenta,
|
|
@@ -264,13 +268,15 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
264
268
|
ThemeKey.TOOL_PARAM.value: palette.green,
|
|
265
269
|
ThemeKey.TOOL_PARAM_BOLD.value: "bold " + palette.green,
|
|
266
270
|
ThemeKey.TOOL_RESULT.value: palette.grey_green,
|
|
271
|
+
ThemeKey.TOOL_RESULT_TREE_PREFIX.value: palette.grey_green + " dim",
|
|
267
272
|
ThemeKey.TOOL_RESULT_BOLD.value: "bold " + palette.grey_green,
|
|
268
|
-
ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.yellow,
|
|
273
|
+
ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.yellow + " dim",
|
|
269
274
|
ThemeKey.TOOL_MARK.value: "bold",
|
|
270
275
|
ThemeKey.TOOL_APPROVED.value: palette.green + " bold reverse",
|
|
271
276
|
ThemeKey.TOOL_REJECTED.value: palette.red + " bold reverse",
|
|
272
277
|
ThemeKey.TOOL_TIMEOUT.value: palette.yellow,
|
|
273
278
|
ThemeKey.TOOL_RESULT_MERMAID: palette.blue + " underline",
|
|
279
|
+
ThemeKey.SUB_AGENT_FOOTER.value: "dim " + palette.grey2,
|
|
274
280
|
# BASH SYNTAX
|
|
275
281
|
ThemeKey.BASH_COMMAND.value: "bold " + palette.green,
|
|
276
282
|
ThemeKey.BASH_ARGUMENT.value: palette.green,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import IO
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def print_kitty_image(file_path: str | Path, *, height: int | None = None, file: IO[str] | None = None) -> None:
|
|
9
|
+
"""Print an image to the terminal using Kitty graphics protocol.
|
|
10
|
+
|
|
11
|
+
This intentionally bypasses Rich rendering to avoid interleaving Live refreshes
|
|
12
|
+
with raw escape sequences.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
path = Path(file_path) if isinstance(file_path, str) else file_path
|
|
16
|
+
if not path.exists():
|
|
17
|
+
print(f"Image not found: {path}", file=file or sys.stdout, flush=True)
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from term_image.image import KittyImage # type: ignore[import-untyped]
|
|
22
|
+
|
|
23
|
+
KittyImage.forced_support = True # type: ignore[reportUnknownMemberType]
|
|
24
|
+
img = KittyImage.from_file(path) # type: ignore[reportUnknownMemberType]
|
|
25
|
+
if height is not None:
|
|
26
|
+
img.height = height # type: ignore[reportUnknownMemberType]
|
|
27
|
+
|
|
28
|
+
out = file or sys.stdout
|
|
29
|
+
print("", file=out)
|
|
30
|
+
print(str(img), file=out)
|
|
31
|
+
print("", file=out)
|
|
32
|
+
out.flush()
|
|
33
|
+
except Exception:
|
|
34
|
+
print(f"Saved image: {path}", file=file or sys.stdout, flush=True)
|
|
@@ -7,6 +7,7 @@ from dataclasses import dataclass
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from typing import TextIO, cast
|
|
9
9
|
|
|
10
|
+
from klaude_code.const import NOTIFY_COMPACT_LIMIT
|
|
10
11
|
from klaude_code.trace import DebugType, log_debug
|
|
11
12
|
|
|
12
13
|
# Environment variable for tmux test signal channel
|
|
@@ -102,7 +103,7 @@ class TerminalNotifier:
|
|
|
102
103
|
return term.lower() not in {"", "dumb"}
|
|
103
104
|
|
|
104
105
|
|
|
105
|
-
def _compact(text: str, limit: int =
|
|
106
|
+
def _compact(text: str, limit: int = NOTIFY_COMPACT_LIMIT) -> str:
|
|
106
107
|
squashed = " ".join(text.split())
|
|
107
108
|
if len(squashed) > limit:
|
|
108
109
|
return squashed[: limit - 3] + "…"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Use OSC 9;4
|
|
2
|
+
Use OSC 9;4;… to control progress bar in terminal like Ghostty
|
|
3
3
|
States:
|
|
4
4
|
0/hidden
|
|
5
5
|
1/normal
|
|
@@ -71,17 +71,17 @@ if __name__ == "__main__":
|
|
|
71
71
|
# Clear progress bar
|
|
72
72
|
emit_osc94(OSC94States.HIDDEN)
|
|
73
73
|
|
|
74
|
-
print("Waiting
|
|
74
|
+
print("Waiting…")
|
|
75
75
|
# Indeterminate
|
|
76
76
|
emit_osc94(OSC94States.INDETERMINATE)
|
|
77
77
|
|
|
78
78
|
time.sleep(2)
|
|
79
|
-
print("Error
|
|
79
|
+
print("Error…")
|
|
80
80
|
# Error
|
|
81
81
|
emit_osc94(OSC94States.ERROR)
|
|
82
82
|
|
|
83
83
|
time.sleep(2)
|
|
84
|
-
print("Warning
|
|
84
|
+
print("Warning…")
|
|
85
85
|
# Warning
|
|
86
86
|
emit_osc94(OSC94States.WARNING)
|
|
87
87
|
time.sleep(2)
|
|
@@ -88,12 +88,30 @@ def build_model_select_items(models: list[Any]) -> list[SelectItem[str]]:
|
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
def _restyle_title(title: list[tuple[str, str]], cls: str) -> list[tuple[str, str]]:
|
|
91
|
-
"""Re-apply a style class while keeping
|
|
92
|
-
|
|
91
|
+
"""Re-apply a style class while keeping existing style tokens.
|
|
92
|
+
|
|
93
|
+
This is used to highlight the currently-pointed item. We want to:
|
|
94
|
+
- preserve explicit colors (e.g. `fg:ansibrightblack`) defined by callers
|
|
95
|
+
- preserve existing classes (e.g. `class:msg`, `class:meta`) so their
|
|
96
|
+
non-color attributes remain in effect
|
|
97
|
+
- preserve text attributes like bold/italic/dim
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
keep_attrs = {"bold", "italic", "underline", "reverse", "blink", "strike", "dim"}
|
|
93
101
|
restyled: list[tuple[str, str]] = []
|
|
94
102
|
for old_style, text in title:
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
tokens = old_style.split()
|
|
104
|
+
attrs = [tok for tok in tokens if tok in keep_attrs]
|
|
105
|
+
style_tokens = [tok for tok in tokens if tok not in keep_attrs]
|
|
106
|
+
|
|
107
|
+
if cls in style_tokens:
|
|
108
|
+
style_tokens = [tok for tok in style_tokens if tok != cls]
|
|
109
|
+
|
|
110
|
+
# Place the highlight class first, so existing per-token styles (classes
|
|
111
|
+
# or explicit fg/bg) keep their precedence. This prevents highlight from
|
|
112
|
+
# accidentally overriding caller-defined colors.
|
|
113
|
+
combined = [cls, *style_tokens, *attrs]
|
|
114
|
+
style = " ".join(tok for tok in combined if tok)
|
|
97
115
|
restyled.append((style, text))
|
|
98
116
|
return restyled
|
|
99
117
|
|
klaude_code/ui/utils/common.py
CHANGED
|
@@ -101,7 +101,7 @@ def format_model_params(model_params: "LLMConfigModelParameter") -> list[str]:
|
|
|
101
101
|
- "reasoning medium"
|
|
102
102
|
- "thinking budget 10000"
|
|
103
103
|
- "verbosity 2"
|
|
104
|
-
- "provider-routing: {
|
|
104
|
+
- "provider-routing: {…}"
|
|
105
105
|
"""
|
|
106
106
|
parts: list[str] = []
|
|
107
107
|
|
|
@@ -109,7 +109,7 @@ def format_model_params(model_params: "LLMConfigModelParameter") -> list[str]:
|
|
|
109
109
|
if model_params.thinking.reasoning_effort:
|
|
110
110
|
parts.append(f"reasoning {model_params.thinking.reasoning_effort}")
|
|
111
111
|
if model_params.thinking.reasoning_summary:
|
|
112
|
-
parts.append(f"
|
|
112
|
+
parts.append(f"summary {model_params.thinking.reasoning_summary}")
|
|
113
113
|
if model_params.thinking.budget_tokens:
|
|
114
114
|
parts.append(f"thinking budget {model_params.thinking.budget_tokens}")
|
|
115
115
|
|
|
@@ -119,6 +119,15 @@ def format_model_params(model_params: "LLMConfigModelParameter") -> list[str]:
|
|
|
119
119
|
if model_params.provider_routing:
|
|
120
120
|
parts.append(f"provider routing {_format_provider_routing(model_params.provider_routing)}")
|
|
121
121
|
|
|
122
|
+
if model_params.modalities:
|
|
123
|
+
parts.append(f"modalities {','.join(model_params.modalities)}")
|
|
124
|
+
|
|
125
|
+
if model_params.image_config:
|
|
126
|
+
if model_params.image_config.aspect_ratio:
|
|
127
|
+
parts.append(f"image aspect {model_params.image_config.aspect_ratio}")
|
|
128
|
+
if model_params.image_config.image_size:
|
|
129
|
+
parts.append(f"image size {model_params.image_config.image_size}")
|
|
130
|
+
|
|
122
131
|
return parts
|
|
123
132
|
|
|
124
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -9,11 +9,12 @@ Requires-Dist: diff-match-patch>=20241021
|
|
|
9
9
|
Requires-Dist: google-genai>=1.56.0
|
|
10
10
|
Requires-Dist: markdown-it-py>=4.0.0
|
|
11
11
|
Requires-Dist: openai>=1.102.0
|
|
12
|
-
Requires-Dist: pillow>=
|
|
12
|
+
Requires-Dist: pillow>=9.1,<11.0
|
|
13
13
|
Requires-Dist: prompt-toolkit>=3.0.52
|
|
14
14
|
Requires-Dist: pydantic>=2.11.7
|
|
15
15
|
Requires-Dist: pyyaml>=6.0.2
|
|
16
16
|
Requires-Dist: rich>=14.1.0
|
|
17
|
+
Requires-Dist: term-image>=0.7.2
|
|
17
18
|
Requires-Dist: trafilatura>=2.0.0
|
|
18
19
|
Requires-Dist: typer>=0.17.3
|
|
19
20
|
Requires-Python: >=3.13
|
|
@@ -395,3 +396,4 @@ The main agent can spawn specialized sub-agents for specific tasks:
|
|
|
395
396
|
| **Explore** | Fast codebase exploration - find files, search code, answer questions about the codebase |
|
|
396
397
|
| **Task** | Handle complex multi-step tasks autonomously |
|
|
397
398
|
| **WebAgent** | Search the web, fetch pages, and analyze content |
|
|
399
|
+
| **ImageGen** | Generate images from text prompts via OpenRouter Nano Banana Pro |
|