klaude-code 1.2.12__py3-none-any.whl → 1.2.14__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/codex/oauth.py +3 -3
- klaude_code/cli/auth_cmd.py +73 -0
- klaude_code/cli/config_cmd.py +88 -0
- klaude_code/cli/debug.py +72 -0
- klaude_code/cli/main.py +31 -142
- klaude_code/cli/runtime.py +19 -58
- klaude_code/cli/session_cmd.py +9 -9
- klaude_code/command/__init__.py +6 -6
- klaude_code/command/export_cmd.py +3 -3
- klaude_code/command/model_cmd.py +1 -1
- klaude_code/command/registry.py +1 -1
- klaude_code/command/terminal_setup_cmd.py +2 -2
- klaude_code/command/thinking_cmd.py +8 -6
- klaude_code/config/__init__.py +1 -5
- klaude_code/config/config.py +31 -4
- klaude_code/config/list_model.py +1 -1
- klaude_code/const/__init__.py +8 -3
- klaude_code/core/agent.py +14 -62
- klaude_code/core/executor.py +11 -10
- klaude_code/core/manager/agent_manager.py +4 -4
- klaude_code/core/manager/llm_clients.py +10 -49
- klaude_code/core/manager/llm_clients_builder.py +8 -21
- klaude_code/core/manager/sub_agent_manager.py +3 -3
- klaude_code/core/prompt.py +12 -7
- klaude_code/core/reminders.py +1 -1
- klaude_code/core/task.py +2 -2
- klaude_code/core/tool/__init__.py +16 -25
- klaude_code/core/tool/file/_utils.py +1 -1
- klaude_code/core/tool/file/apply_patch.py +17 -25
- klaude_code/core/tool/file/apply_patch_tool.py +4 -7
- klaude_code/core/tool/file/edit_tool.py +4 -11
- klaude_code/core/tool/file/multi_edit_tool.py +2 -3
- klaude_code/core/tool/file/read_tool.py +3 -4
- klaude_code/core/tool/file/write_tool.py +2 -3
- klaude_code/core/tool/memory/memory_tool.py +2 -8
- klaude_code/core/tool/memory/skill_loader.py +3 -2
- klaude_code/core/tool/shell/command_safety.py +0 -1
- klaude_code/core/tool/tool_context.py +1 -3
- klaude_code/core/tool/tool_registry.py +2 -1
- klaude_code/core/tool/tool_runner.py +1 -1
- klaude_code/core/tool/truncation.py +2 -5
- klaude_code/core/turn.py +9 -3
- klaude_code/llm/anthropic/client.py +6 -2
- klaude_code/llm/client.py +5 -1
- klaude_code/llm/codex/client.py +2 -2
- klaude_code/llm/input_common.py +2 -2
- klaude_code/llm/openai_compatible/client.py +11 -8
- klaude_code/llm/openai_compatible/stream_processor.py +2 -1
- klaude_code/llm/openrouter/client.py +22 -9
- klaude_code/llm/openrouter/reasoning_handler.py +19 -132
- klaude_code/llm/registry.py +6 -5
- klaude_code/llm/responses/client.py +10 -5
- klaude_code/protocol/events.py +9 -2
- klaude_code/protocol/model.py +7 -1
- klaude_code/protocol/sub_agent.py +2 -2
- klaude_code/session/export.py +58 -0
- klaude_code/session/selector.py +2 -2
- klaude_code/session/session.py +37 -7
- klaude_code/session/templates/export_session.html +46 -0
- klaude_code/trace/__init__.py +2 -2
- klaude_code/trace/log.py +144 -5
- klaude_code/ui/__init__.py +4 -9
- klaude_code/ui/core/stage_manager.py +7 -4
- klaude_code/ui/modes/debug/display.py +2 -1
- klaude_code/ui/modes/repl/__init__.py +1 -1
- klaude_code/ui/modes/repl/completers.py +6 -7
- klaude_code/ui/modes/repl/display.py +3 -4
- klaude_code/ui/modes/repl/event_handler.py +63 -5
- klaude_code/ui/modes/repl/key_bindings.py +2 -3
- klaude_code/ui/modes/repl/renderer.py +52 -62
- klaude_code/ui/renderers/diffs.py +1 -4
- klaude_code/ui/renderers/tools.py +4 -0
- klaude_code/ui/rich/markdown.py +3 -3
- klaude_code/ui/rich/searchable_text.py +6 -6
- klaude_code/ui/rich/status.py +3 -4
- klaude_code/ui/rich/theme.py +2 -5
- klaude_code/ui/terminal/control.py +7 -16
- klaude_code/ui/terminal/notifier.py +2 -4
- klaude_code/ui/utils/common.py +1 -1
- klaude_code/ui/utils/debouncer.py +2 -2
- {klaude_code-1.2.12.dist-info → klaude_code-1.2.14.dist-info}/METADATA +1 -1
- {klaude_code-1.2.12.dist-info → klaude_code-1.2.14.dist-info}/RECORD +84 -81
- {klaude_code-1.2.12.dist-info → klaude_code-1.2.14.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.12.dist-info → klaude_code-1.2.14.dist-info}/entry_points.txt +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterator
|
|
3
4
|
from contextlib import contextmanager
|
|
4
5
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
from rich import box
|
|
8
9
|
from rich.box import Box
|
|
@@ -98,48 +99,21 @@ class REPLRenderer:
|
|
|
98
99
|
def print(self, *objects: Any, style: StyleType | None = None, end: str = "\n") -> None:
|
|
99
100
|
if self.current_sub_agent_color:
|
|
100
101
|
if objects:
|
|
101
|
-
|
|
102
|
+
content = objects[0] if len(objects) == 1 else objects
|
|
103
|
+
self.console.print(Quote(content, style=self.current_sub_agent_color))
|
|
102
104
|
return
|
|
103
105
|
self.console.print(*objects, style=style, end=end)
|
|
104
106
|
|
|
105
107
|
def display_tool_call(self, e: events.ToolCallEvent) -> None:
|
|
106
|
-
# Handle sub-agent tool calls in replay mode
|
|
107
108
|
if r_tools.is_sub_agent_tool(e.tool_name):
|
|
108
|
-
if e.is_replay:
|
|
109
|
-
state = r_sub_agent.build_sub_agent_state_from_tool_call(e)
|
|
110
|
-
if state is not None:
|
|
111
|
-
sub_agent_default_style = (
|
|
112
|
-
self.themes.sub_agent_colors[0] if self.themes.sub_agent_colors else Style()
|
|
113
|
-
)
|
|
114
|
-
self.print(
|
|
115
|
-
Quote(
|
|
116
|
-
r_sub_agent.render_sub_agent_call(state, sub_agent_default_style),
|
|
117
|
-
style=sub_agent_default_style,
|
|
118
|
-
)
|
|
119
|
-
)
|
|
120
109
|
return
|
|
121
|
-
|
|
122
110
|
renderable = r_tools.render_tool_call(e)
|
|
123
111
|
if renderable is not None:
|
|
124
112
|
self.print(renderable)
|
|
125
113
|
|
|
126
114
|
def display_tool_call_result(self, e: events.ToolResultEvent) -> None:
|
|
127
|
-
# Handle sub-agent tool results in replay mode
|
|
128
115
|
if r_tools.is_sub_agent_tool(e.tool_name):
|
|
129
|
-
if e.is_replay:
|
|
130
|
-
sub_agent_default_style = self.themes.sub_agent_colors[0] if self.themes.sub_agent_colors else Style()
|
|
131
|
-
self.print(
|
|
132
|
-
Quote(
|
|
133
|
-
r_sub_agent.render_sub_agent_result(
|
|
134
|
-
e.result,
|
|
135
|
-
code_theme=self.themes.code_theme,
|
|
136
|
-
style=sub_agent_default_style,
|
|
137
|
-
),
|
|
138
|
-
style=sub_agent_default_style,
|
|
139
|
-
)
|
|
140
|
-
)
|
|
141
116
|
return
|
|
142
|
-
|
|
143
117
|
renderable = r_tools.render_tool_result(e)
|
|
144
118
|
if renderable is not None:
|
|
145
119
|
self.print(renderable)
|
|
@@ -159,39 +133,55 @@ class REPLRenderer:
|
|
|
159
133
|
async def replay_history(self, history_events: events.ReplayHistoryEvent) -> None:
|
|
160
134
|
tool_call_dict: dict[str, events.ToolCallEvent] = {}
|
|
161
135
|
for event in history_events.events:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
self.print(
|
|
136
|
+
event_session_id = getattr(event, "session_id", history_events.session_id)
|
|
137
|
+
is_sub_agent = self.is_sub_agent_session(event_session_id)
|
|
138
|
+
|
|
139
|
+
with self.session_print_context(event_session_id):
|
|
140
|
+
match event:
|
|
141
|
+
case events.TaskStartEvent() as e:
|
|
142
|
+
self.display_task_start(e)
|
|
143
|
+
case events.TurnStartEvent():
|
|
144
|
+
self.print()
|
|
145
|
+
case events.AssistantMessageEvent() as e:
|
|
146
|
+
if is_sub_agent:
|
|
147
|
+
continue
|
|
148
|
+
renderable = r_assistant.render_assistant_message(
|
|
149
|
+
e.content, code_theme=self.themes.code_theme
|
|
150
|
+
)
|
|
151
|
+
if renderable is not None:
|
|
152
|
+
self.print(renderable)
|
|
153
|
+
self.print()
|
|
154
|
+
case events.ThinkingEvent() as e:
|
|
155
|
+
if is_sub_agent:
|
|
156
|
+
continue
|
|
157
|
+
self.display_thinking(e.content)
|
|
158
|
+
case events.DeveloperMessageEvent() as e:
|
|
159
|
+
self.display_developer_message(e)
|
|
160
|
+
self.display_command_output(e)
|
|
161
|
+
case events.UserMessageEvent() as e:
|
|
162
|
+
if is_sub_agent:
|
|
163
|
+
continue
|
|
164
|
+
self.print(r_user_input.render_user_input(e.content))
|
|
165
|
+
case events.ToolCallEvent() as e:
|
|
166
|
+
tool_call_dict[e.tool_call_id] = e
|
|
167
|
+
case events.ToolResultEvent() as e:
|
|
168
|
+
tool_call_event = tool_call_dict.get(e.tool_call_id)
|
|
169
|
+
if tool_call_event is not None:
|
|
170
|
+
self.display_tool_call(tool_call_event)
|
|
171
|
+
tool_call_dict.pop(e.tool_call_id, None)
|
|
172
|
+
if is_sub_agent:
|
|
173
|
+
continue
|
|
174
|
+
self.display_tool_call_result(e)
|
|
175
|
+
case events.TaskMetadataEvent() as e:
|
|
176
|
+
self.print(r_metadata.render_task_metadata(e))
|
|
177
|
+
self.print()
|
|
178
|
+
case events.InterruptEvent():
|
|
171
179
|
self.print()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
case events.UserMessageEvent() as user_event:
|
|
178
|
-
self.print(r_user_input.render_user_input(user_event.content))
|
|
179
|
-
case events.ToolCallEvent() as tool_call_event:
|
|
180
|
-
tool_call_dict[tool_call_event.tool_call_id] = tool_call_event
|
|
181
|
-
case events.ToolResultEvent() as tool_result_event:
|
|
182
|
-
tool_call_event = tool_call_dict.get(tool_result_event.tool_call_id)
|
|
183
|
-
if tool_call_event is not None:
|
|
184
|
-
self.display_tool_call(tool_call_event)
|
|
185
|
-
tool_call_dict.pop(tool_result_event.tool_call_id, None)
|
|
186
|
-
self.display_tool_call_result(tool_result_event)
|
|
187
|
-
case events.TaskMetadataEvent() as metadata_event:
|
|
188
|
-
self.print(r_metadata.render_task_metadata(metadata_event))
|
|
189
|
-
self.print()
|
|
190
|
-
case events.InterruptEvent():
|
|
191
|
-
self.print()
|
|
192
|
-
self.print(r_user_input.render_interrupt())
|
|
193
|
-
case events.ErrorEvent() as e:
|
|
194
|
-
self.display_error(e)
|
|
180
|
+
self.print(r_user_input.render_interrupt())
|
|
181
|
+
case events.ErrorEvent() as e:
|
|
182
|
+
self.display_error(e)
|
|
183
|
+
case events.TaskFinishEvent() as e:
|
|
184
|
+
self.display_task_finish(e)
|
|
195
185
|
|
|
196
186
|
def display_developer_message(self, e: events.DeveloperMessageEvent) -> None:
|
|
197
187
|
if not r_developer.need_render_developer_message(e):
|
|
@@ -73,10 +73,7 @@ def render_diff(diff_text: str, show_file_name: bool = False) -> RenderableType:
|
|
|
73
73
|
if line.startswith("--- "):
|
|
74
74
|
raw = line[4:].strip()
|
|
75
75
|
if raw != "/dev/null":
|
|
76
|
-
if raw.startswith(("a/", "b/"))
|
|
77
|
-
from_file_name = raw[2:]
|
|
78
|
-
else:
|
|
79
|
-
from_file_name = raw
|
|
76
|
+
from_file_name = raw[2:] if raw.startswith(("a/", "b/")) else raw
|
|
80
77
|
continue
|
|
81
78
|
|
|
82
79
|
# Parse file name from diff headers
|
|
@@ -239,6 +239,10 @@ def render_todo(tr: events.ToolResultEvent) -> RenderableType:
|
|
|
239
239
|
mark = "✔"
|
|
240
240
|
mark_style = ThemeKey.TODO_NEW_COMPLETED_MARK if is_new_completed else ThemeKey.TODO_COMPLETED_MARK
|
|
241
241
|
text_style = ThemeKey.TODO_NEW_COMPLETED if is_new_completed else ThemeKey.TODO_COMPLETED
|
|
242
|
+
case _:
|
|
243
|
+
mark = "?"
|
|
244
|
+
mark_style = ThemeKey.TODO_PENDING_MARK
|
|
245
|
+
text_style = ThemeKey.TODO_PENDING
|
|
242
246
|
text = Text(todo.content)
|
|
243
247
|
text.stylize(text_style)
|
|
244
248
|
todo_grid.add_row(Text(mark, style=mark_style), text)
|
klaude_code/ui/rich/markdown.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# copy from https://github.com/Aider-AI/aider/blob/main/aider/mdstream.py
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import contextlib
|
|
4
5
|
import io
|
|
5
6
|
import time
|
|
6
7
|
from typing import Any, ClassVar
|
|
@@ -183,10 +184,9 @@ class MarkdownStream:
|
|
|
183
184
|
def __del__(self) -> None:
|
|
184
185
|
"""Destructor to ensure Live display is properly cleaned up."""
|
|
185
186
|
if self.live:
|
|
186
|
-
|
|
187
|
+
# Ignore any errors during cleanup
|
|
188
|
+
with contextlib.suppress(Exception):
|
|
187
189
|
self.live.stop()
|
|
188
|
-
except Exception:
|
|
189
|
-
pass # Ignore any errors during cleanup
|
|
190
190
|
|
|
191
191
|
def update(self, text: str, final: bool = False) -> None:
|
|
192
192
|
"""Update the displayed markdown content.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Iterable, Sequence
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class SearchableFormattedText:
|
|
@@ -16,8 +16,8 @@ class SearchableFormattedText:
|
|
|
16
16
|
concatenating the text parts of the fragments.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
def __init__(self, fragments: Sequence[
|
|
20
|
-
self._fragments:
|
|
19
|
+
def __init__(self, fragments: Sequence[tuple[str, str]], plain: str | None = None):
|
|
20
|
+
self._fragments: list[tuple[str, str]] = list(fragments)
|
|
21
21
|
if plain is None:
|
|
22
22
|
plain = "".join(text for _, text in self._fragments)
|
|
23
23
|
self._plain = plain
|
|
@@ -25,7 +25,7 @@ class SearchableFormattedText:
|
|
|
25
25
|
# Recognized by prompt_toolkit's to_formatted_text(value)
|
|
26
26
|
def __pt_formatted_text__(
|
|
27
27
|
self,
|
|
28
|
-
) -> Iterable[
|
|
28
|
+
) -> Iterable[tuple[str, str]]: # pragma: no cover - passthrough
|
|
29
29
|
return self._fragments
|
|
30
30
|
|
|
31
31
|
# Provide a human-readable representation.
|
|
@@ -45,7 +45,7 @@ class SearchableFormattedText:
|
|
|
45
45
|
return self._plain
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class SearchableFormattedList(list[
|
|
48
|
+
class SearchableFormattedList(list[tuple[str, str]]):
|
|
49
49
|
"""
|
|
50
50
|
List variant compatible with questionary's expected ``Choice.title`` type.
|
|
51
51
|
|
|
@@ -54,7 +54,7 @@ class SearchableFormattedList(list[Tuple[str, str]]):
|
|
|
54
54
|
- Provides ``.lower()``/``.upper()`` returning the plain text for search filtering.
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
-
def __init__(self, fragments: Sequence[
|
|
57
|
+
def __init__(self, fragments: Sequence[tuple[str, str]], plain: str | None = None):
|
|
58
58
|
super().__init__(fragments)
|
|
59
59
|
if plain is None:
|
|
60
60
|
plain = "".join(text for _, text in fragments)
|
klaude_code/ui/rich/status.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import math
|
|
4
5
|
import time
|
|
5
6
|
|
|
@@ -233,8 +234,6 @@ class BreathingSpinner(RichSpinner):
|
|
|
233
234
|
|
|
234
235
|
# Monkey-patch Rich's Status module to use the breathing spinner implementation
|
|
235
236
|
# for the configured spinner name, while preserving default behavior elsewhere.
|
|
236
|
-
|
|
237
|
+
# Best-effort patch; if it fails we silently fall back to default spinner.
|
|
238
|
+
with contextlib.suppress(Exception):
|
|
237
239
|
rich_status.Spinner = BreathingSpinner # type: ignore[assignment]
|
|
238
|
-
except Exception:
|
|
239
|
-
# Best-effort patch; if it fails we silently fall back to default spinner.
|
|
240
|
-
pass
|
klaude_code/ui/rich/theme.py
CHANGED
|
@@ -43,7 +43,7 @@ LIGHT_PALETTE = Palette(
|
|
|
43
43
|
diff_add="#2e5a32 on #e8f5e9",
|
|
44
44
|
diff_remove="#5a2e32 on #ffebee",
|
|
45
45
|
code_theme="ansi_light",
|
|
46
|
-
text_background="#
|
|
46
|
+
text_background="#e0e0e0",
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
DARK_PALETTE = Palette(
|
|
@@ -153,10 +153,7 @@ class Themes:
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
def get_theme(theme: str | None = None) -> Themes:
|
|
156
|
-
if theme == "light"
|
|
157
|
-
palette = LIGHT_PALETTE
|
|
158
|
-
else:
|
|
159
|
-
palette = DARK_PALETTE
|
|
156
|
+
palette = LIGHT_PALETTE if theme == "light" else DARK_PALETTE
|
|
160
157
|
return Themes(
|
|
161
158
|
app_theme=Theme(
|
|
162
159
|
styles={
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import os
|
|
3
4
|
import select
|
|
4
5
|
import signal
|
|
@@ -75,19 +76,15 @@ def start_esc_interrupt_monitor(
|
|
|
75
76
|
r2, _, _ = select.select([sys.stdin], [], [], 0.0)
|
|
76
77
|
|
|
77
78
|
if seq == "":
|
|
78
|
-
|
|
79
|
+
# Best-effort only; failures here should not crash the UI.
|
|
80
|
+
with contextlib.suppress(Exception):
|
|
79
81
|
asyncio.run_coroutine_threadsafe(on_interrupt(), loop)
|
|
80
|
-
except Exception:
|
|
81
|
-
# Best-effort only; failures here should not crash the UI.
|
|
82
|
-
pass
|
|
83
82
|
stop.set()
|
|
84
83
|
except Exception as exc: # pragma: no cover - environment dependent
|
|
85
84
|
log((f"esc monitor error: {exc}", "r red"))
|
|
86
85
|
finally:
|
|
87
|
-
|
|
86
|
+
with contextlib.suppress(Exception):
|
|
88
87
|
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
89
|
-
except Exception:
|
|
90
|
-
pass
|
|
91
88
|
|
|
92
89
|
esc_task: asyncio.Task[None] = asyncio.create_task(asyncio.to_thread(_esc_monitor, stop_event))
|
|
93
90
|
return stop_event, esc_task
|
|
@@ -119,18 +116,14 @@ def install_sigint_double_press_exit(
|
|
|
119
116
|
now = time.monotonic()
|
|
120
117
|
if now - last_sigint_time <= window_seconds:
|
|
121
118
|
# Second press within window: hide progress UI and exit.
|
|
122
|
-
|
|
119
|
+
with contextlib.suppress(Exception):
|
|
123
120
|
hide_progress()
|
|
124
|
-
except Exception:
|
|
125
|
-
pass
|
|
126
121
|
raise KeyboardInterrupt
|
|
127
122
|
|
|
128
123
|
# First press: remember timestamp and show toast.
|
|
129
124
|
last_sigint_time = now
|
|
130
|
-
|
|
125
|
+
with contextlib.suppress(Exception):
|
|
131
126
|
show_toast()
|
|
132
|
-
except Exception:
|
|
133
|
-
pass
|
|
134
127
|
|
|
135
128
|
try:
|
|
136
129
|
signal.signal(signal.SIGINT, _handler)
|
|
@@ -139,9 +132,7 @@ def install_sigint_double_press_exit(
|
|
|
139
132
|
return lambda: None
|
|
140
133
|
|
|
141
134
|
def restore() -> None:
|
|
142
|
-
|
|
135
|
+
with contextlib.suppress(Exception):
|
|
143
136
|
signal.signal(signal.SIGINT, original_handler)
|
|
144
|
-
except Exception:
|
|
145
|
-
pass
|
|
146
137
|
|
|
147
138
|
return restore
|
|
@@ -39,7 +39,7 @@ class TerminalNotifierConfig:
|
|
|
39
39
|
stream: TextIO | None = None
|
|
40
40
|
|
|
41
41
|
@classmethod
|
|
42
|
-
def from_env(cls) ->
|
|
42
|
+
def from_env(cls) -> TerminalNotifierConfig:
|
|
43
43
|
env = os.getenv("KLAUDE_NOTIFY", "").strip().lower()
|
|
44
44
|
if env in {"0", "off", "false", "disable", "disabled"}:
|
|
45
45
|
return cls(enabled=False)
|
|
@@ -95,9 +95,7 @@ class TerminalNotifier:
|
|
|
95
95
|
if not getattr(stream, "isatty", lambda: False)():
|
|
96
96
|
return False
|
|
97
97
|
term = os.getenv("TERM", "")
|
|
98
|
-
|
|
99
|
-
return False
|
|
100
|
-
return True
|
|
98
|
+
return term.lower() not in {"", "dumb"}
|
|
101
99
|
|
|
102
100
|
|
|
103
101
|
def _compact(text: str, limit: int = 160) -> str:
|
klaude_code/ui/utils/common.py
CHANGED
|
@@ -99,7 +99,7 @@ def truncate_display(
|
|
|
99
99
|
) -> str:
|
|
100
100
|
lines = text.split("\n")
|
|
101
101
|
if len(lines) > max_lines:
|
|
102
|
-
lines = lines[:max_lines]
|
|
102
|
+
lines = [*lines[:max_lines], "… (more " + str(len(lines) - max_lines) + " lines)"]
|
|
103
103
|
for i, line in enumerate(lines):
|
|
104
104
|
if len(line) > max_line_length:
|
|
105
105
|
lines[i] = (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Debouncer:
|
|
@@ -15,7 +15,7 @@ class Debouncer:
|
|
|
15
15
|
"""
|
|
16
16
|
self.interval = interval
|
|
17
17
|
self.callback = callback
|
|
18
|
-
self._task:
|
|
18
|
+
self._task: asyncio.Task[None] | None = None
|
|
19
19
|
|
|
20
20
|
def cancel(self) -> None:
|
|
21
21
|
"""Cancel current debounce task"""
|