klaude-code 1.4.2__py3-none-any.whl → 1.5.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/cli/main.py +75 -11
- klaude_code/cli/runtime.py +171 -34
- klaude_code/command/__init__.py +4 -0
- klaude_code/command/help_cmd.py +2 -1
- klaude_code/command/model_cmd.py +3 -5
- klaude_code/command/model_select.py +84 -0
- klaude_code/command/registry.py +23 -0
- klaude_code/command/resume_cmd.py +52 -2
- klaude_code/command/thinking_cmd.py +30 -199
- klaude_code/config/select_model.py +47 -97
- klaude_code/config/thinking.py +255 -0
- klaude_code/core/executor.py +53 -63
- klaude_code/protocol/commands.py +11 -0
- klaude_code/protocol/op.py +15 -0
- klaude_code/session/__init__.py +2 -2
- klaude_code/session/selector.py +33 -61
- klaude_code/ui/modes/repl/completers.py +48 -58
- klaude_code/ui/modes/repl/event_handler.py +2 -1
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +498 -50
- klaude_code/ui/modes/repl/key_bindings.py +30 -10
- klaude_code/ui/renderers/metadata.py +3 -6
- klaude_code/ui/renderers/user_input.py +18 -1
- klaude_code/ui/rich/theme.py +2 -2
- klaude_code/ui/terminal/notifier.py +42 -0
- klaude_code/ui/terminal/selector.py +419 -136
- {klaude_code-1.4.2.dist-info → klaude_code-1.5.0.dist-info}/METADATA +1 -1
- {klaude_code-1.4.2.dist-info → klaude_code-1.5.0.dist-info}/RECORD +29 -27
- {klaude_code-1.4.2.dist-info → klaude_code-1.5.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.4.2.dist-info → klaude_code-1.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -19,15 +19,15 @@ import re
|
|
|
19
19
|
import shutil
|
|
20
20
|
import subprocess
|
|
21
21
|
import time
|
|
22
|
-
from collections.abc import Iterable
|
|
22
|
+
from collections.abc import Callable, Iterable
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import NamedTuple
|
|
25
25
|
|
|
26
26
|
from prompt_toolkit.completion import Completer, Completion
|
|
27
27
|
from prompt_toolkit.document import Document
|
|
28
|
-
from prompt_toolkit.formatted_text import
|
|
28
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
29
29
|
|
|
30
|
-
from klaude_code.
|
|
30
|
+
from klaude_code.protocol.commands import CommandInfo
|
|
31
31
|
from klaude_code.trace.log import DebugType, log_debug
|
|
32
32
|
|
|
33
33
|
# Pattern to match @token for completion refresh (used by key bindings).
|
|
@@ -40,12 +40,18 @@ AT_TOKEN_PATTERN = re.compile(r'(^|\s)@(?P<frag>"[^"]*"|[^\s]*)$')
|
|
|
40
40
|
SKILL_TOKEN_PATTERN = re.compile(r"^[$¥](?P<frag>\S*)$")
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def create_repl_completer(
|
|
43
|
+
def create_repl_completer(
|
|
44
|
+
command_info_provider: Callable[[], list[CommandInfo]] | None = None,
|
|
45
|
+
) -> Completer:
|
|
44
46
|
"""Create and return the combined REPL completer.
|
|
45
47
|
|
|
48
|
+
Args:
|
|
49
|
+
command_info_provider: Optional callable that returns command metadata.
|
|
50
|
+
If None, slash command completion is disabled.
|
|
51
|
+
|
|
46
52
|
Returns a completer that handles both @ file paths and / slash commands.
|
|
47
53
|
"""
|
|
48
|
-
return _ComboCompleter()
|
|
54
|
+
return _ComboCompleter(command_info_provider=command_info_provider)
|
|
49
55
|
|
|
50
56
|
|
|
51
57
|
class _CmdResult(NamedTuple):
|
|
@@ -66,6 +72,9 @@ class _SlashCommandCompleter(Completer):
|
|
|
66
72
|
|
|
67
73
|
_SLASH_TOKEN_RE = re.compile(r"^/(?P<frag>\S*)$")
|
|
68
74
|
|
|
75
|
+
def __init__(self, command_info_provider: Callable[[], list[CommandInfo]] | None = None) -> None:
|
|
76
|
+
self._command_info_provider = command_info_provider
|
|
77
|
+
|
|
69
78
|
def get_completions(
|
|
70
79
|
self,
|
|
71
80
|
document: Document,
|
|
@@ -75,6 +84,9 @@ class _SlashCommandCompleter(Completer):
|
|
|
75
84
|
if document.cursor_position_row != 0:
|
|
76
85
|
return
|
|
77
86
|
|
|
87
|
+
if self._command_info_provider is None:
|
|
88
|
+
return
|
|
89
|
+
|
|
78
90
|
text_before = document.current_line_before_cursor
|
|
79
91
|
m = self._SLASH_TOKEN_RE.search(text_before)
|
|
80
92
|
if not m:
|
|
@@ -84,38 +96,28 @@ class _SlashCommandCompleter(Completer):
|
|
|
84
96
|
token_start = len(text_before) - len(f"/{frag}")
|
|
85
97
|
start_position = token_start - len(text_before) # negative offset
|
|
86
98
|
|
|
87
|
-
# Get available commands
|
|
88
|
-
|
|
99
|
+
# Get available commands from provider
|
|
100
|
+
command_infos = self._command_info_provider()
|
|
89
101
|
|
|
90
102
|
# Filter commands that match the fragment (preserve registration order)
|
|
91
|
-
matched: list[tuple[str,
|
|
92
|
-
for
|
|
93
|
-
if
|
|
94
|
-
hint = f" [{
|
|
95
|
-
matched.append((
|
|
103
|
+
matched: list[tuple[str, CommandInfo, str]] = []
|
|
104
|
+
for cmd_info in command_infos:
|
|
105
|
+
if cmd_info.name.startswith(frag):
|
|
106
|
+
hint = f" [{cmd_info.placeholder}]" if cmd_info.support_addition_params else ""
|
|
107
|
+
matched.append((cmd_info.name, cmd_info, hint))
|
|
96
108
|
|
|
97
109
|
if not matched:
|
|
98
110
|
return
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
# Find the longest command+hint length
|
|
102
|
-
max_len = max(len(name) + len(hint) for name, _, hint in matched)
|
|
103
|
-
# Set a minimum width (e.g. 20) and add some padding
|
|
104
|
-
align_width = max(max_len, 20) + 2
|
|
105
|
-
|
|
106
|
-
for cmd_name, cmd_obj, hint in matched:
|
|
107
|
-
label_len = len(cmd_name) + len(hint)
|
|
108
|
-
padding = " " * (align_width - label_len)
|
|
109
|
-
|
|
110
|
-
# Using HTML for formatting: default color command name, normal hint, gray summary
|
|
111
|
-
display_text = HTML(
|
|
112
|
-
f"<style color='ansidefault'>{cmd_name}</style>{hint}{padding}<style color='ansibrightblack'>{cmd_obj.summary}</style>" # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
|
113
|
-
)
|
|
112
|
+
for cmd_name, cmd_info, hint in matched:
|
|
114
113
|
completion_text = f"/{cmd_name} "
|
|
114
|
+
# Use FormattedText to style the hint (placeholder) in bright black
|
|
115
|
+
display = FormattedText([("", cmd_name), ("ansibrightblack", hint)]) if hint else cmd_name
|
|
115
116
|
yield Completion(
|
|
116
117
|
text=completion_text,
|
|
117
118
|
start_position=start_position,
|
|
118
|
-
display=
|
|
119
|
+
display=display,
|
|
120
|
+
display_meta=cmd_info.summary,
|
|
119
121
|
)
|
|
120
122
|
|
|
121
123
|
def is_slash_command_context(self, document: Document) -> bool:
|
|
@@ -171,26 +173,18 @@ class _SkillCompleter(Completer):
|
|
|
171
173
|
if not matched:
|
|
172
174
|
return
|
|
173
175
|
|
|
174
|
-
# Calculate max
|
|
175
|
-
|
|
176
|
-
align_width = max(max_name_len, 20) + 2
|
|
176
|
+
# Calculate max location length for alignment
|
|
177
|
+
max_loc_len = max(len(loc) for _, _, loc in matched)
|
|
177
178
|
|
|
178
179
|
for name, desc, location in matched:
|
|
179
|
-
# Format: name [location] description
|
|
180
|
-
# Align location tags (max length is "project" = 7, plus brackets = 9)
|
|
181
|
-
padding_name = " " * (align_width - len(name))
|
|
182
|
-
location_tag = f"[{location}]".ljust(9)
|
|
183
|
-
|
|
184
|
-
# Using HTML for formatting: default color skill name, cyan location tag, gray description
|
|
185
|
-
display_text = HTML(
|
|
186
|
-
f"<style color='ansidefault'>{name}</style>{padding_name}<style color='ansicyan'>{location_tag}</style> "
|
|
187
|
-
f"<style color='ansibrightblack'>{desc}</style>"
|
|
188
|
-
)
|
|
189
180
|
completion_text = f"${name} "
|
|
181
|
+
# Pad location to align descriptions
|
|
182
|
+
padded_location = f"[{location}]".ljust(max_loc_len + 2) # +2 for brackets
|
|
190
183
|
yield Completion(
|
|
191
184
|
text=completion_text,
|
|
192
185
|
start_position=start_position,
|
|
193
|
-
display=
|
|
186
|
+
display=name,
|
|
187
|
+
display_meta=f"{padded_location} {desc}",
|
|
194
188
|
)
|
|
195
189
|
|
|
196
190
|
def _get_available_skills(self) -> list[tuple[str, str, str]]:
|
|
@@ -218,9 +212,9 @@ class _SkillCompleter(Completer):
|
|
|
218
212
|
class _ComboCompleter(Completer):
|
|
219
213
|
"""Combined completer that handles @ file paths, / slash commands, and $ skills."""
|
|
220
214
|
|
|
221
|
-
def __init__(self) -> None:
|
|
215
|
+
def __init__(self, command_info_provider: Callable[[], list[CommandInfo]] | None = None) -> None:
|
|
222
216
|
self._at_completer = _AtFilesCompleter()
|
|
223
|
-
self._slash_completer = _SlashCommandCompleter()
|
|
217
|
+
self._slash_completer = _SlashCommandCompleter(command_info_provider=command_info_provider)
|
|
224
218
|
self._skill_completer = _SkillCompleter()
|
|
225
219
|
|
|
226
220
|
def get_completions(
|
|
@@ -318,14 +312,12 @@ class _AtFilesCompleter(Completer):
|
|
|
318
312
|
if not suggestions:
|
|
319
313
|
return [] # type: ignore[reportUnknownVariableType]
|
|
320
314
|
start_position = token_start_in_input - len(text_before)
|
|
321
|
-
|
|
322
|
-
display_align = self._display_align_width(visible_suggestions)
|
|
323
|
-
|
|
324
|
-
for s in visible_suggestions:
|
|
315
|
+
for s in suggestions[: self._max_results]:
|
|
325
316
|
yield Completion(
|
|
326
317
|
text=self._format_completion_text(s, is_quoted=is_quoted),
|
|
327
318
|
start_position=start_position,
|
|
328
|
-
display=self._format_display_label(s,
|
|
319
|
+
display=self._format_display_label(s, 0),
|
|
320
|
+
display_meta=s,
|
|
329
321
|
)
|
|
330
322
|
return [] # type: ignore[reportUnknownVariableType]
|
|
331
323
|
|
|
@@ -336,15 +328,13 @@ class _AtFilesCompleter(Completer):
|
|
|
336
328
|
|
|
337
329
|
# Prepare Completion objects. Replace from the '@' character.
|
|
338
330
|
start_position = token_start_in_input - len(text_before) # negative
|
|
339
|
-
|
|
340
|
-
display_align = self._display_align_width(visible_suggestions)
|
|
341
|
-
|
|
342
|
-
for s in visible_suggestions:
|
|
331
|
+
for s in suggestions[: self._max_results]:
|
|
343
332
|
# Insert formatted text (with quoting when needed) so that subsequent typing does not keep triggering
|
|
344
333
|
yield Completion(
|
|
345
334
|
text=self._format_completion_text(s, is_quoted=is_quoted),
|
|
346
335
|
start_position=start_position,
|
|
347
|
-
display=self._format_display_label(s,
|
|
336
|
+
display=self._format_display_label(s, 0),
|
|
337
|
+
display_meta=s,
|
|
348
338
|
)
|
|
349
339
|
|
|
350
340
|
# ---- Core logic ----
|
|
@@ -539,15 +529,15 @@ class _AtFilesCompleter(Completer):
|
|
|
539
529
|
return f'@"{suggestion}" '
|
|
540
530
|
return f"@{suggestion} "
|
|
541
531
|
|
|
542
|
-
def _format_display_label(self, suggestion: str, align_width: int) ->
|
|
532
|
+
def _format_display_label(self, suggestion: str, align_width: int) -> str:
|
|
543
533
|
"""Format visible label for a completion option.
|
|
544
534
|
|
|
545
|
-
|
|
535
|
+
Keep this unstyled so that the completion menu's selection style can
|
|
536
|
+
fully override the selected row.
|
|
546
537
|
"""
|
|
547
538
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return HTML(f"<style color='ansidefault'>{name}</style>{padding}<style color='ansibrightblack'>{suggestion}</style>")
|
|
539
|
+
_ = align_width
|
|
540
|
+
return self._display_name(suggestion)
|
|
551
541
|
|
|
552
542
|
def _display_align_width(self, suggestions: list[str]) -> int:
|
|
553
543
|
"""Calculate alignment width for display labels."""
|
|
@@ -15,7 +15,7 @@ from klaude_code.ui.renderers.thinking import THINKING_MESSAGE_MARK, normalize_t
|
|
|
15
15
|
from klaude_code.ui.rich import status as r_status
|
|
16
16
|
from klaude_code.ui.rich.markdown import MarkdownStream, ThinkingMarkdown
|
|
17
17
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
18
|
-
from klaude_code.ui.terminal.notifier import Notification, NotificationType, TerminalNotifier
|
|
18
|
+
from klaude_code.ui.terminal.notifier import Notification, NotificationType, TerminalNotifier, emit_tmux_signal
|
|
19
19
|
from klaude_code.ui.terminal.progress_bar import OSC94States, emit_osc94
|
|
20
20
|
|
|
21
21
|
|
|
@@ -509,6 +509,7 @@ class DisplayEventHandler:
|
|
|
509
509
|
self.spinner_status.reset()
|
|
510
510
|
self.renderer.spinner_stop()
|
|
511
511
|
self.renderer.console.print(Rule(characters="─", style=ThemeKey.LINES))
|
|
512
|
+
emit_tmux_signal() # Signal test harness if KLAUDE_TEST_SIGNAL is set
|
|
512
513
|
await self.stage_manager.transition_to(Stage.WAITING)
|
|
513
514
|
self._maybe_notify_task_finish(event)
|
|
514
515
|
|