klaude-code 1.2.17__py3-none-any.whl → 1.2.19__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.
Files changed (70) hide show
  1. klaude_code/cli/config_cmd.py +1 -1
  2. klaude_code/cli/debug.py +1 -1
  3. klaude_code/cli/main.py +45 -31
  4. klaude_code/cli/runtime.py +49 -13
  5. klaude_code/{version.py → cli/self_update.py} +110 -2
  6. klaude_code/command/__init__.py +4 -1
  7. klaude_code/command/clear_cmd.py +2 -7
  8. klaude_code/command/command_abc.py +33 -5
  9. klaude_code/command/debug_cmd.py +79 -0
  10. klaude_code/command/diff_cmd.py +2 -6
  11. klaude_code/command/export_cmd.py +7 -7
  12. klaude_code/command/export_online_cmd.py +9 -8
  13. klaude_code/command/help_cmd.py +4 -9
  14. klaude_code/command/model_cmd.py +10 -6
  15. klaude_code/command/prompt_command.py +2 -6
  16. klaude_code/command/refresh_cmd.py +2 -7
  17. klaude_code/command/registry.py +69 -26
  18. klaude_code/command/release_notes_cmd.py +2 -6
  19. klaude_code/command/status_cmd.py +2 -7
  20. klaude_code/command/terminal_setup_cmd.py +2 -6
  21. klaude_code/command/thinking_cmd.py +16 -10
  22. klaude_code/config/select_model.py +81 -5
  23. klaude_code/const/__init__.py +1 -1
  24. klaude_code/core/executor.py +257 -110
  25. klaude_code/core/manager/__init__.py +2 -4
  26. klaude_code/core/prompts/prompt-claude-code.md +1 -1
  27. klaude_code/core/prompts/prompt-sub-agent-explore.md +14 -2
  28. klaude_code/core/prompts/prompt-sub-agent-web.md +8 -5
  29. klaude_code/core/reminders.py +9 -35
  30. klaude_code/core/task.py +9 -7
  31. klaude_code/core/tool/file/read_tool.md +1 -1
  32. klaude_code/core/tool/file/read_tool.py +41 -12
  33. klaude_code/core/tool/memory/skill_loader.py +12 -10
  34. klaude_code/core/tool/shell/bash_tool.py +22 -2
  35. klaude_code/core/tool/tool_registry.py +1 -1
  36. klaude_code/core/tool/tool_runner.py +26 -23
  37. klaude_code/core/tool/truncation.py +23 -9
  38. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  39. klaude_code/core/tool/web/web_fetch_tool.py +36 -1
  40. klaude_code/core/turn.py +28 -0
  41. klaude_code/llm/anthropic/client.py +25 -9
  42. klaude_code/llm/openai_compatible/client.py +5 -2
  43. klaude_code/llm/openrouter/client.py +7 -3
  44. klaude_code/llm/responses/client.py +6 -1
  45. klaude_code/protocol/commands.py +1 -0
  46. klaude_code/protocol/sub_agent/web.py +3 -2
  47. klaude_code/session/session.py +35 -15
  48. klaude_code/session/templates/export_session.html +45 -32
  49. klaude_code/trace/__init__.py +20 -2
  50. klaude_code/ui/modes/repl/completers.py +231 -73
  51. klaude_code/ui/modes/repl/event_handler.py +8 -6
  52. klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
  53. klaude_code/ui/modes/repl/renderer.py +2 -2
  54. klaude_code/ui/renderers/common.py +54 -0
  55. klaude_code/ui/renderers/developer.py +2 -3
  56. klaude_code/ui/renderers/errors.py +1 -1
  57. klaude_code/ui/renderers/metadata.py +12 -5
  58. klaude_code/ui/renderers/thinking.py +24 -8
  59. klaude_code/ui/renderers/tools.py +82 -14
  60. klaude_code/ui/rich/code_panel.py +112 -0
  61. klaude_code/ui/rich/markdown.py +3 -4
  62. klaude_code/ui/rich/status.py +0 -2
  63. klaude_code/ui/rich/theme.py +10 -1
  64. klaude_code/ui/utils/common.py +0 -18
  65. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/METADATA +32 -7
  66. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/RECORD +69 -68
  67. klaude_code/core/manager/agent_manager.py +0 -132
  68. /klaude_code/{config → cli}/list_model.py +0 -0
  69. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/WHEEL +0 -0
  70. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  from rich.console import RenderableType
2
4
  from rich.padding import Padding
3
5
  from rich.text import Text
@@ -10,14 +12,28 @@ def thinking_prefix() -> Text:
10
12
  return Text.from_markup("[not italic]⸫[/not italic] Thinking …", style=ThemeKey.THINKING)
11
13
 
12
14
 
13
- def _normalize_thinking_content(content: str) -> str:
15
+ def normalize_thinking_content(content: str) -> str:
14
16
  """Normalize thinking content for display."""
15
- return (
16
- content.rstrip()
17
- .replace("**\n\n", "** \n")
18
- .replace("\\n\\n\n\n", "") # Weird case of Gemini 3
19
- .replace("****", "**\n\n**") # Remove extra newlines after bold titles
20
- )
17
+ text = content.rstrip()
18
+
19
+ # Weird case of Gemini 3
20
+ text = text.replace("\\n\\n\n\n", "")
21
+
22
+ # Fix OpenRouter OpenAI reasoning formatting where segments like
23
+ # "text**Title**\n\n" lose the blank line between segments.
24
+ # We want: "text\n**Title**\n" so that each bold title starts on
25
+ # its own line and uses a single trailing newline.
26
+ text = re.sub(r"([^\n])(\*\*[^*]+?\*\*)\n\n", r"\1 \n\n\2 \n", text)
27
+
28
+ # Remove extra newlines between back-to-back bold titles, eg
29
+ # "**Title1****Title2**" -> "**Title1**\n\n**Title2**".
30
+ text = text.replace("****", "**\n\n**")
31
+
32
+ # Compact double-newline after bold so the body text follows
33
+ # directly after the title line, using a markdown line break.
34
+ text = text.replace("**\n\n", "** \n")
35
+
36
+ return text
21
37
 
22
38
 
23
39
  def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableType | None:
@@ -31,7 +47,7 @@ def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableT
31
47
 
32
48
  return Padding.indent(
33
49
  ThinkingMarkdown(
34
- _normalize_thinking_content(content),
50
+ normalize_thinking_content(content),
35
51
  code_theme=code_theme,
36
52
  style=style,
37
53
  ),
@@ -2,7 +2,7 @@ import json
2
2
  from pathlib import Path
3
3
  from typing import Any, cast
4
4
 
5
- from rich.console import RenderableType
5
+ from rich.console import Group, RenderableType
6
6
  from rich.padding import Padding
7
7
  from rich.text import Text
8
8
 
@@ -10,9 +10,8 @@ from klaude_code import const
10
10
  from klaude_code.protocol import events, model, tools
11
11
  from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
12
12
  from klaude_code.ui.renderers import diffs as r_diffs
13
- from klaude_code.ui.renderers.common import create_grid
13
+ from klaude_code.ui.renderers.common import create_grid, truncate_display
14
14
  from klaude_code.ui.rich.theme import ThemeKey
15
- from klaude_code.ui.utils.common import truncate_display
16
15
 
17
16
 
18
17
  def is_sub_agent_tool(tool_name: str) -> bool:
@@ -290,7 +289,7 @@ def render_todo(tr: events.ToolResultEvent) -> RenderableType:
290
289
  def render_generic_tool_result(result: str, *, is_error: bool = False) -> RenderableType:
291
290
  """Render a generic tool result as indented, truncated text."""
292
291
  style = ThemeKey.ERROR if is_error else ThemeKey.TOOL_RESULT
293
- return Padding.indent(Text(truncate_display(result), style=style), level=2)
292
+ return Padding.indent(truncate_display(result, base_style=style), level=2)
294
293
 
295
294
 
296
295
  def _extract_mermaid_link(
@@ -379,6 +378,75 @@ def render_mermaid_tool_call(arguments: str) -> RenderableType:
379
378
  return grid
380
379
 
381
380
 
381
+ def _truncate_url(url: str, max_length: int = 400) -> str:
382
+ """Truncate URL for display, preserving domain and path structure."""
383
+ if len(url) <= max_length:
384
+ return url
385
+ # Remove protocol for display
386
+ display_url = url
387
+ for prefix in ("https://", "http://"):
388
+ if display_url.startswith(prefix):
389
+ display_url = display_url[len(prefix) :]
390
+ break
391
+ if len(display_url) <= max_length:
392
+ return display_url
393
+ # Truncate with ellipsis
394
+ return display_url[: max_length - 3] + "..."
395
+
396
+
397
+ def render_web_fetch_tool_call(arguments: str) -> RenderableType:
398
+ grid = create_grid()
399
+ tool_name_column = Text.assemble(("↓", ThemeKey.TOOL_MARK), " ", ("Fetch", ThemeKey.TOOL_NAME))
400
+
401
+ try:
402
+ payload: dict[str, str] = json.loads(arguments)
403
+ except json.JSONDecodeError:
404
+ summary = Text(
405
+ arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
406
+ style=ThemeKey.INVALID_TOOL_CALL_ARGS,
407
+ )
408
+ grid.add_row(tool_name_column, summary)
409
+ return grid
410
+
411
+ url = payload.get("url", "")
412
+ summary = Text(_truncate_url(url), ThemeKey.TOOL_PARAM_FILE_PATH) if url else Text("(no url)", ThemeKey.TOOL_PARAM)
413
+
414
+ grid.add_row(tool_name_column, summary)
415
+ return grid
416
+
417
+
418
+ def render_web_search_tool_call(arguments: str) -> RenderableType:
419
+ grid = create_grid()
420
+ tool_name_column = Text.assemble(("◉", ThemeKey.TOOL_MARK), " ", ("Search", ThemeKey.TOOL_NAME))
421
+
422
+ try:
423
+ payload: dict[str, Any] = json.loads(arguments)
424
+ except json.JSONDecodeError:
425
+ summary = Text(
426
+ arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
427
+ style=ThemeKey.INVALID_TOOL_CALL_ARGS,
428
+ )
429
+ grid.add_row(tool_name_column, summary)
430
+ return grid
431
+
432
+ query = payload.get("query", "")
433
+ max_results = payload.get("max_results")
434
+
435
+ summary = Text("", ThemeKey.TOOL_PARAM)
436
+ if query:
437
+ # Truncate long queries
438
+ display_query = query if len(query) <= 80 else query[:77] + "..."
439
+ summary.append(display_query, ThemeKey.TOOL_PARAM)
440
+ else:
441
+ summary.append("(no query)", ThemeKey.TOOL_PARAM)
442
+
443
+ if isinstance(max_results, int) and max_results != 10:
444
+ summary.append(f" (max {max_results})", ThemeKey.TOOL_TIMEOUT)
445
+
446
+ grid.add_row(tool_name_column, summary)
447
+ return grid
448
+
449
+
382
450
  def render_mermaid_tool_result(tr: events.ToolResultEvent) -> RenderableType:
383
451
  from klaude_code.ui.terminal import supports_osc8_hyperlinks
384
452
 
@@ -409,16 +477,12 @@ def _extract_truncation(
409
477
 
410
478
  def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
411
479
  """Render truncation info for the user."""
412
- original_kb = ui_extra.original_length / 1024
413
480
  truncated_kb = ui_extra.truncated_length / 1024
481
+
414
482
  text = Text.assemble(
415
- ("Output truncated: ", ThemeKey.TOOL_RESULT),
416
- (f"{original_kb:.1f}KB", ThemeKey.TOOL_RESULT),
417
- (" total, ", ThemeKey.TOOL_RESULT),
418
- (f"{truncated_kb:.1f}KB", ThemeKey.TOOL_RESULT_BOLD),
419
- (" hidden\nFull output saved to ", ThemeKey.TOOL_RESULT),
420
- (ui_extra.saved_file_path, ThemeKey.TOOL_RESULT),
421
- ("\nUse Read with limit+offset or rg/grep to inspect", ThemeKey.TOOL_RESULT),
483
+ ("Offload context to ", ThemeKey.TOOL_RESULT_TRUNCATED),
484
+ (ui_extra.saved_file_path, ThemeKey.TOOL_RESULT_TRUNCATED),
485
+ (f", {truncated_kb:.1f}KB truncated", ThemeKey.TOOL_RESULT_TRUNCATED),
422
486
  )
423
487
  return Padding.indent(text, level=2)
424
488
 
@@ -506,6 +570,10 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
506
570
  return render_generic_tool_call(e.tool_name, e.arguments, "◈")
507
571
  case tools.REPORT_BACK:
508
572
  return render_report_back_tool_call()
573
+ case tools.WEB_FETCH:
574
+ return render_web_fetch_tool_call(e.arguments)
575
+ case tools.WEB_SEARCH:
576
+ return render_web_search_tool_call(e.arguments)
509
577
  case _:
510
578
  return render_generic_tool_call(e.tool_name, e.arguments)
511
579
 
@@ -528,13 +596,13 @@ def render_tool_result(e: events.ToolResultEvent) -> RenderableType | None:
528
596
 
529
597
  # Handle error case
530
598
  if e.status == "error" and e.ui_extra is None:
531
- error_msg = Text(truncate_display(e.result))
599
+ error_msg = truncate_display(e.result)
532
600
  return r_errors.render_error(error_msg)
533
601
 
534
602
  # Show truncation info if output was truncated and saved to file
535
603
  truncation_info = get_truncation_info(e)
536
604
  if truncation_info:
537
- return render_truncation_info(truncation_info)
605
+ return Group(render_truncation_info(truncation_info), render_generic_tool_result(e.result))
538
606
 
539
607
  diff_text = _extract_diff_text(e.ui_extra)
540
608
 
@@ -0,0 +1,112 @@
1
+ """A panel that only has top and bottom borders, no left/right borders or padding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from rich.console import ConsoleRenderable, RichCast
8
+ from rich.jupyter import JupyterMixin
9
+ from rich.measure import Measurement, measure_renderables
10
+ from rich.segment import Segment
11
+ from rich.style import StyleType
12
+
13
+ if TYPE_CHECKING:
14
+ from rich.console import Console, ConsoleOptions, RenderResult
15
+
16
+ # Box drawing characters
17
+ TOP_LEFT = "┌" # ┌
18
+ TOP_RIGHT = "┐" # ┐
19
+ BOTTOM_LEFT = "└" # └
20
+ BOTTOM_RIGHT = "┘" # ┘
21
+ HORIZONTAL = "─" # ─
22
+
23
+
24
+ class CodePanel(JupyterMixin):
25
+ """A panel with only top and bottom borders, no left/right borders.
26
+
27
+ This is designed for code blocks where you want easy copy-paste without
28
+ picking up border characters on the sides.
29
+
30
+ Example:
31
+ >>> console.print(CodePanel(Syntax(code, "python")))
32
+
33
+ Renders as:
34
+ ┌──────────────────────────┐
35
+ code line 1
36
+ code line 2
37
+ └──────────────────────────┘
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ renderable: ConsoleRenderable | RichCast | str,
43
+ *,
44
+ border_style: StyleType = "none",
45
+ expand: bool = False,
46
+ padding: int = 1,
47
+ ) -> None:
48
+ """Initialize the CodePanel.
49
+
50
+ Args:
51
+ renderable: A console renderable object.
52
+ border_style: The style of the border. Defaults to "none".
53
+ expand: If True, expand to fill available width. Defaults to False.
54
+ padding: Left/right padding for content. Defaults to 1.
55
+ """
56
+ self.renderable = renderable
57
+ self.border_style = border_style
58
+ self.expand = expand
59
+ self.padding = padding
60
+
61
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
62
+ border_style = console.get_style(self.border_style)
63
+ max_width = options.max_width
64
+ pad = self.padding
65
+
66
+ # Measure the content width (account for padding)
67
+ if self.expand:
68
+ content_width = max_width - pad * 2
69
+ else:
70
+ content_width = console.measure(self.renderable, options=options.update(width=max_width - pad * 2)).maximum
71
+ content_width = min(content_width, max_width - pad * 2)
72
+
73
+ # Render content lines
74
+ child_options = options.update(width=content_width)
75
+ lines = console.render_lines(self.renderable, child_options)
76
+
77
+ # Calculate border width based on content width + padding
78
+ border_width = content_width + pad * 2
79
+
80
+ new_line = Segment.line()
81
+ pad_segment = Segment(" " * pad) if pad > 0 else None
82
+
83
+ # Top border: ┌───...───┐
84
+ top_border = (
85
+ TOP_LEFT + (HORIZONTAL * (border_width - 2)) + TOP_RIGHT if border_width >= 2 else HORIZONTAL * border_width
86
+ )
87
+ yield Segment(top_border, border_style)
88
+ yield new_line
89
+
90
+ # Content lines with padding
91
+ for line in lines:
92
+ if pad_segment:
93
+ yield pad_segment
94
+ yield from line
95
+ if pad_segment:
96
+ yield pad_segment
97
+ yield new_line
98
+
99
+ # Bottom border: └───...───┘
100
+ bottom_border = (
101
+ BOTTOM_LEFT + (HORIZONTAL * (border_width - 2)) + BOTTOM_RIGHT
102
+ if border_width >= 2
103
+ else HORIZONTAL * border_width
104
+ )
105
+ yield Segment(bottom_border, border_style)
106
+ yield new_line
107
+
108
+ def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
109
+ if self.expand:
110
+ return Measurement(options.max_width, options.max_width)
111
+ width = measure_renderables(console, options, [self.renderable]).maximum + self.padding * 2
112
+ return Measurement(width, width)
@@ -7,11 +7,9 @@ import time
7
7
  from collections.abc import Callable
8
8
  from typing import Any, ClassVar
9
9
 
10
- from rich import box
11
10
  from rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
12
11
  from rich.live import Live
13
12
  from rich.markdown import CodeBlock, Heading, Markdown
14
- from rich.panel import Panel
15
13
  from rich.rule import Rule
16
14
  from rich.spinner import Spinner
17
15
  from rich.style import Style
@@ -20,6 +18,7 @@ from rich.text import Text
20
18
  from rich.theme import Theme
21
19
 
22
20
  from klaude_code import const
21
+ from klaude_code.ui.rich.code_panel import CodePanel
23
22
 
24
23
 
25
24
  class NoInsetCodeBlock(CodeBlock):
@@ -34,7 +33,7 @@ class NoInsetCodeBlock(CodeBlock):
34
33
  word_wrap=True,
35
34
  padding=(0, 0),
36
35
  )
37
- yield Panel.fit(syntax, padding=(0, 0), box=box.HORIZONTALS, border_style="markdown.code.border")
36
+ yield CodePanel(syntax, border_style="markdown.code.border")
38
37
 
39
38
 
40
39
  class ThinkingCodeBlock(CodeBlock):
@@ -43,7 +42,7 @@ class ThinkingCodeBlock(CodeBlock):
43
42
  def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
44
43
  code = str(self.text).rstrip()
45
44
  text = Text(code, "markdown.code.block")
46
- yield text
45
+ yield CodePanel(text, border_style="markdown.code.border")
47
46
 
48
47
 
49
48
  class LeftHeading(Heading):
@@ -35,8 +35,6 @@ _BREATHING_SPINNER_GLYPHS_BASE = [
35
35
  "✴",
36
36
  "✷",
37
37
  "⟡",
38
- "⬡",
39
- "⬢",
40
38
  ]
41
39
 
42
40
  # Shuffle glyphs on module load for variety across sessions
@@ -86,6 +86,7 @@ class ThemeKey(str, Enum):
86
86
  # SPINNER_STATUS
87
87
  SPINNER_STATUS = "spinner.status"
88
88
  SPINNER_STATUS_TEXT = "spinner.status.text"
89
+ SPINNER_STATUS_TEXT_BOLD = "spinner.status.text.bold"
89
90
  # STATUS
90
91
  STATUS_HINT = "status.hint"
91
92
  # USER_INPUT
@@ -103,6 +104,7 @@ class ThemeKey(str, Enum):
103
104
  TOOL_PARAM = "tool.param"
104
105
  TOOL_PARAM_BOLD = "tool.param.bold"
105
106
  TOOL_RESULT = "tool.result"
107
+ TOOL_RESULT_TRUNCATED = "tool.result.truncated"
106
108
  TOOL_RESULT_BOLD = "tool.result.bold"
107
109
  TOOL_MARK = "tool.mark"
108
110
  TOOL_APPROVED = "tool.approved"
@@ -181,6 +183,7 @@ def get_theme(theme: str | None = None) -> Themes:
181
183
  # SPINNER_STATUS
182
184
  ThemeKey.SPINNER_STATUS.value: palette.blue,
183
185
  ThemeKey.SPINNER_STATUS_TEXT.value: palette.blue,
186
+ ThemeKey.SPINNER_STATUS_TEXT_BOLD.value: "bold " + palette.blue,
184
187
  # STATUS
185
188
  ThemeKey.STATUS_HINT.value: palette.grey2,
186
189
  # REMINDER
@@ -194,6 +197,7 @@ def get_theme(theme: str | None = None) -> Themes:
194
197
  ThemeKey.TOOL_PARAM_BOLD.value: "bold " + palette.green,
195
198
  ThemeKey.TOOL_RESULT.value: palette.grey_green,
196
199
  ThemeKey.TOOL_RESULT_BOLD.value: "bold " + palette.grey_green,
200
+ ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.yellow,
197
201
  ThemeKey.TOOL_MARK.value: "bold",
198
202
  ThemeKey.TOOL_APPROVED.value: palette.green + " bold reverse",
199
203
  ThemeKey.TOOL_REJECTED.value: palette.red + " bold reverse",
@@ -243,11 +247,15 @@ def get_theme(theme: str | None = None) -> Themes:
243
247
  "markdown.hr": palette.grey3,
244
248
  "markdown.item.bullet": palette.grey2,
245
249
  "markdown.item.number": palette.grey2,
250
+ "markdown.link": "underline " + palette.blue,
251
+ "markdown.link_url": "underline " + palette.blue,
246
252
  }
247
253
  ),
248
254
  thinking_markdown_theme=Theme(
249
255
  styles={
250
256
  "markdown.code": palette.grey1 + " italic on " + palette.text_background,
257
+ "markdown.code.block": palette.grey1,
258
+ "markdown.code.border": palette.grey3,
251
259
  "markdown.h1": "bold reverse",
252
260
  "markdown.h1.border": palette.grey3,
253
261
  "markdown.h2.border": palette.grey3,
@@ -256,7 +264,8 @@ def get_theme(theme: str | None = None) -> Themes:
256
264
  "markdown.hr": palette.grey3,
257
265
  "markdown.item.bullet": palette.grey2,
258
266
  "markdown.item.number": palette.grey2,
259
- "markdown.code.block": palette.grey1,
267
+ "markdown.link": "underline " + palette.blue,
268
+ "markdown.link_url": "underline " + palette.blue,
260
269
  "markdown.strong": "bold italic " + palette.grey1,
261
270
  }
262
271
  ),
@@ -2,8 +2,6 @@ import re
2
2
  import subprocess
3
3
  from pathlib import Path
4
4
 
5
- from klaude_code import const
6
-
7
5
  LEADING_NEWLINES_REGEX = re.compile(r"^\n{2,}")
8
6
 
9
7
 
@@ -90,19 +88,3 @@ def show_path_with_tilde(path: Path | None = None):
90
88
  return f"~/{relative_path}"
91
89
  except ValueError:
92
90
  return str(path)
93
-
94
-
95
- def truncate_display(
96
- text: str,
97
- max_lines: int = const.TRUNCATE_DISPLAY_MAX_LINES,
98
- max_line_length: int = const.TRUNCATE_DISPLAY_MAX_LINE_LENGTH,
99
- ) -> str:
100
- lines = text.split("\n")
101
- if len(lines) > max_lines:
102
- lines = [*lines[:max_lines], "… (more " + str(len(lines) - max_lines) + " lines)"]
103
- for i, line in enumerate(lines):
104
- if len(line) > max_line_length:
105
- lines[i] = (
106
- line[:max_line_length] + "… (more " + str(len(line) - max_line_length) + " characters in this line)"
107
- )
108
- return "\n".join(lines)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 1.2.17
3
+ Version: 1.2.19
4
4
  Summary: Add your description here
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: ddgs>=9.9.3
@@ -41,6 +41,21 @@ To update:
41
41
  uv tool upgrade klaude-code
42
42
  ```
43
43
 
44
+ Or use the built-in alias command:
45
+
46
+ ```bash
47
+ klaude update
48
+ klaude upgrade
49
+ ```
50
+
51
+ To show version:
52
+
53
+ ```bash
54
+ klaude --version
55
+ klaude -v
56
+ klaude version
57
+ ```
58
+
44
59
  ## Usage
45
60
 
46
61
  ### Interactive Mode
@@ -50,13 +65,18 @@ klaude [--model <name>] [--select-model]
50
65
  ```
51
66
 
52
67
  **Options:**
53
- - `--version`/`-V`: Show version and exit.
54
- - `--model`/`-m`: Select a model by logical name from config.
55
- - `--select-model`/`-s`: Interactively choose a model at startup.
68
+ - `--version`/`-V`/`-v`: Show version and exit.
69
+ - `--model`/`-m`: Preferred model name (exact match picks immediately; otherwise opens the interactive selector filtered by this value).
70
+ - `--select-model`/`-s`: Open the interactive model selector at startup (shows all models unless `--model` is also provided).
56
71
  - `--continue`/`-c`: Resume the most recent session.
57
72
  - `--resume`/`-r`: Select a session to resume for this project.
58
73
  - `--vanilla`: Minimal mode with only basic tools (Bash, Read, Edit) and no system prompts.
59
74
 
75
+ **Model selection behavior:**
76
+ - Default: uses `main_model` from config.
77
+ - `--select-model`: always prompts you to pick.
78
+ - `--model <value>`: tries to resolve `<value>` to a single model; if it can't, it prompts with a filtered list (and falls back to showing all models if there are no matches).
79
+
60
80
  **Debug Options:**
61
81
  - `--debug`/`-d`: Enable debug mode with verbose logging and LLM trace.
62
82
  - `--debug-filter`: Filter debug output by type (comma-separated).
@@ -64,7 +84,7 @@ klaude [--model <name>] [--select-model]
64
84
 
65
85
  ### Configuration
66
86
 
67
- An example config will be created in `~/.klaude/config.yaml` when first run.
87
+ An example config will be created in `~/.klaude/klaude-config.yaml` when first run.
68
88
 
69
89
  Open the configuration file in editor:
70
90
 
@@ -201,7 +221,7 @@ sub_agent_models:
201
221
  oracle: gpt-5.1
202
222
  explore: haiku
203
223
  task: opus
204
- webfetchagent: haiku
224
+ webagent: haiku
205
225
 
206
226
  ```
207
227
 
@@ -263,5 +283,10 @@ klaude exec "what is 2+2?"
263
283
  echo "hello world" | klaude exec
264
284
 
265
285
  # With model selection
286
+
287
+ # Exact model name (non-interactive)
266
288
  echo "generate quicksort in python" | klaude exec --model gpt-5.1
267
- ```
289
+
290
+ # Partial/ambiguous name opens the interactive selector (filtered)
291
+ echo "generate quicksort in python" | klaude exec --model gpt
292
+ ```