klaude-code 1.2.21__py3-none-any.whl → 1.2.23__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 (65) hide show
  1. klaude_code/cli/debug.py +8 -10
  2. klaude_code/command/__init__.py +0 -3
  3. klaude_code/command/status_cmd.py +1 -1
  4. klaude_code/const/__init__.py +10 -7
  5. klaude_code/core/manager/sub_agent_manager.py +1 -1
  6. klaude_code/core/prompt.py +5 -2
  7. klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
  8. klaude_code/core/prompts/{prompt-codex-gpt-5-1.md → prompt-codex.md} +9 -42
  9. klaude_code/core/reminders.py +87 -2
  10. klaude_code/core/task.py +37 -18
  11. klaude_code/core/tool/__init__.py +1 -9
  12. klaude_code/core/tool/file/_utils.py +6 -0
  13. klaude_code/core/tool/file/apply_patch_tool.py +30 -72
  14. klaude_code/core/tool/file/diff_builder.py +151 -0
  15. klaude_code/core/tool/file/edit_tool.py +35 -18
  16. klaude_code/core/tool/file/read_tool.py +45 -86
  17. klaude_code/core/tool/file/write_tool.py +40 -30
  18. klaude_code/core/tool/shell/bash_tool.py +147 -0
  19. klaude_code/core/tool/skill/__init__.py +0 -0
  20. klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -39
  21. klaude_code/protocol/commands.py +0 -1
  22. klaude_code/protocol/model.py +31 -11
  23. klaude_code/protocol/tools.py +1 -2
  24. klaude_code/session/export.py +76 -21
  25. klaude_code/session/store.py +4 -2
  26. klaude_code/session/templates/export_session.html +28 -0
  27. klaude_code/skill/__init__.py +27 -0
  28. klaude_code/skill/assets/deslop/SKILL.md +17 -0
  29. klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
  30. klaude_code/skill/assets/handoff/SKILL.md +39 -0
  31. klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
  32. klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
  33. klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +60 -24
  34. klaude_code/skill/manager.py +70 -0
  35. klaude_code/skill/system_skills.py +192 -0
  36. klaude_code/ui/modes/repl/completers.py +103 -3
  37. klaude_code/ui/modes/repl/event_handler.py +7 -3
  38. klaude_code/ui/modes/repl/input_prompt_toolkit.py +42 -3
  39. klaude_code/ui/renderers/assistant.py +7 -2
  40. klaude_code/ui/renderers/common.py +26 -11
  41. klaude_code/ui/renderers/developer.py +12 -5
  42. klaude_code/ui/renderers/diffs.py +85 -1
  43. klaude_code/ui/renderers/metadata.py +4 -2
  44. klaude_code/ui/renderers/thinking.py +1 -1
  45. klaude_code/ui/renderers/tools.py +75 -129
  46. klaude_code/ui/renderers/user_input.py +32 -2
  47. klaude_code/ui/rich/markdown.py +27 -12
  48. klaude_code/ui/rich/status.py +9 -24
  49. klaude_code/ui/rich/theme.py +17 -5
  50. {klaude_code-1.2.21.dist-info → klaude_code-1.2.23.dist-info}/METADATA +19 -13
  51. {klaude_code-1.2.21.dist-info → klaude_code-1.2.23.dist-info}/RECORD +54 -54
  52. klaude_code/command/diff_cmd.py +0 -136
  53. klaude_code/command/prompt-deslop.md +0 -14
  54. klaude_code/command/prompt-dev-docs-update.md +0 -56
  55. klaude_code/command/prompt-dev-docs.md +0 -46
  56. klaude_code/command/prompt-handoff.md +0 -33
  57. klaude_code/command/prompt-jj-workspace.md +0 -18
  58. klaude_code/core/tool/file/multi_edit_tool.md +0 -42
  59. klaude_code/core/tool/file/multi_edit_tool.py +0 -175
  60. klaude_code/core/tool/memory/__init__.py +0 -5
  61. klaude_code/core/tool/memory/memory_tool.md +0 -20
  62. klaude_code/core/tool/memory/memory_tool.py +0 -456
  63. /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
  64. {klaude_code-1.2.21.dist-info → klaude_code-1.2.23.dist-info}/WHEEL +0 -0
  65. {klaude_code-1.2.21.dist-info → klaude_code-1.2.23.dist-info}/entry_points.txt +0 -0
@@ -7,6 +7,7 @@ from typing import NamedTuple, override
7
7
 
8
8
  from prompt_toolkit import PromptSession
9
9
  from prompt_toolkit.completion import ThreadedCompleter
10
+ from prompt_toolkit.cursor_shapes import CursorShape
10
11
  from prompt_toolkit.formatted_text import FormattedText
11
12
  from prompt_toolkit.history import FileHistory
12
13
  from prompt_toolkit.patch_stdout import patch_stdout
@@ -17,6 +18,8 @@ from klaude_code.ui.core.input import InputProviderABC
17
18
  from klaude_code.ui.modes.repl.clipboard import capture_clipboard_tag, copy_to_clipboard, extract_images_from_text
18
19
  from klaude_code.ui.modes.repl.completers import AT_TOKEN_PATTERN, create_repl_completer
19
20
  from klaude_code.ui.modes.repl.key_bindings import create_key_bindings
21
+ from klaude_code.ui.renderers.user_input import USER_MESSAGE_MARK
22
+ from klaude_code.ui.terminal.color import is_light_terminal_background
20
23
  from klaude_code.ui.utils.common import get_current_git_branch, show_path_with_tilde
21
24
 
22
25
 
@@ -32,16 +35,23 @@ class REPLStatusSnapshot(NamedTuple):
32
35
 
33
36
  COMPLETION_SELECTED = "#5869f7"
34
37
  COMPLETION_MENU = "ansibrightblack"
35
- INPUT_PROMPT_STYLE = "ansimagenta"
38
+ INPUT_PROMPT_STYLE = "ansimagenta bold"
39
+ PLACEHOLDER_TEXT_STYLE_DARK_BG = "fg:#5a5a5a italic"
40
+ PLACEHOLDER_TEXT_STYLE_LIGHT_BG = "fg:#7a7a7a italic"
41
+ PLACEHOLDER_TEXT_STYLE_UNKNOWN_BG = "fg:#8a8a8a italic"
42
+ PLACEHOLDER_SYMBOL_STYLE_DARK_BG = "bg:#2a2a2a fg:#5a5a5a"
43
+ PLACEHOLDER_SYMBOL_STYLE_LIGHT_BG = "bg:#e6e6e6 fg:#7a7a7a"
44
+ PLACEHOLDER_SYMBOL_STYLE_UNKNOWN_BG = "bg:#2a2a2a fg:#8a8a8a"
36
45
 
37
46
 
38
47
  class PromptToolkitInput(InputProviderABC):
39
48
  def __init__(
40
49
  self,
41
- prompt: str = "❯ ",
50
+ prompt: str = USER_MESSAGE_MARK,
42
51
  status_provider: Callable[[], REPLStatusSnapshot] | None = None,
43
52
  ): # ▌
44
53
  self._status_provider = status_provider
54
+ self._is_light_terminal_background = is_light_terminal_background(timeout=0.2)
45
55
 
46
56
  project = str(Path.cwd()).strip("/").replace("/", "-")
47
57
  history_path = Path.home() / ".klaude" / "projects" / project / "input" / "input_history.txt"
@@ -60,6 +70,7 @@ class PromptToolkitInput(InputProviderABC):
60
70
  [(INPUT_PROMPT_STYLE, prompt)],
61
71
  history=FileHistory(str(history_path)),
62
72
  multiline=True,
73
+ cursor=CursorShape.BEAM,
63
74
  prompt_continuation=[(INPUT_PROMPT_STYLE, " ")],
64
75
  key_bindings=kb,
65
76
  completer=ThreadedCompleter(create_repl_completer()),
@@ -144,6 +155,34 @@ class PromptToolkitInput(InputProviderABC):
144
155
  toolbar_text = left_text + padding + right_text
145
156
  return FormattedText([("#2c7eac", toolbar_text)])
146
157
 
158
+ def _render_input_placeholder(self) -> FormattedText:
159
+ if self._is_light_terminal_background is True:
160
+ text_style = PLACEHOLDER_TEXT_STYLE_LIGHT_BG
161
+ symbol_style = PLACEHOLDER_SYMBOL_STYLE_LIGHT_BG
162
+ elif self._is_light_terminal_background is False:
163
+ text_style = PLACEHOLDER_TEXT_STYLE_DARK_BG
164
+ symbol_style = PLACEHOLDER_SYMBOL_STYLE_DARK_BG
165
+ else:
166
+ text_style = PLACEHOLDER_TEXT_STYLE_UNKNOWN_BG
167
+ symbol_style = PLACEHOLDER_SYMBOL_STYLE_UNKNOWN_BG
168
+
169
+ return FormattedText(
170
+ [
171
+ (text_style, " " * 10),
172
+ (symbol_style, " @ "),
173
+ (text_style, " "),
174
+ (text_style, "files"),
175
+ (text_style, " "),
176
+ (symbol_style, " $ "),
177
+ (text_style, " "),
178
+ (text_style, "skills"),
179
+ (text_style, " "),
180
+ (symbol_style, " / "),
181
+ (text_style, " "),
182
+ (text_style, "commands"),
183
+ ]
184
+ )
185
+
147
186
  async def start(self) -> None:
148
187
  pass
149
188
 
@@ -154,7 +193,7 @@ class PromptToolkitInput(InputProviderABC):
154
193
  async def iter_inputs(self) -> AsyncIterator[UserInputPayload]:
155
194
  while True:
156
195
  with patch_stdout():
157
- line: str = await self._session.prompt_async()
196
+ line: str = await self._session.prompt_async(placeholder=self._render_input_placeholder())
158
197
 
159
198
  # Extract images referenced in the input text
160
199
  images = extract_images_from_text(line)
@@ -1,8 +1,13 @@
1
1
  from rich.console import RenderableType
2
+ from rich.padding import Padding
2
3
 
4
+ from klaude_code import const
3
5
  from klaude_code.ui.renderers.common import create_grid
4
6
  from klaude_code.ui.rich.markdown import NoInsetMarkdown
5
7
 
8
+ # UI markers
9
+ ASSISTANT_MESSAGE_MARK = "➤"
10
+
6
11
 
7
12
  def render_assistant_message(content: str, *, code_theme: str) -> RenderableType | None:
8
13
  """Render assistant message for replay history display.
@@ -15,7 +20,7 @@ def render_assistant_message(content: str, *, code_theme: str) -> RenderableType
15
20
 
16
21
  grid = create_grid()
17
22
  grid.add_row(
18
- "•",
19
- NoInsetMarkdown(stripped, code_theme=code_theme),
23
+ ASSISTANT_MESSAGE_MARK,
24
+ Padding(NoInsetMarkdown(stripped, code_theme=code_theme), (0, const.MARKDOWN_RIGHT_MARGIN, 0, 0)),
20
25
  )
21
26
  return grid
@@ -31,16 +31,20 @@ def truncate_display(
31
31
  return Text(f"… (more {remaining} lines)", style=ThemeKey.TOOL_RESULT_TRUNCATED)
32
32
 
33
33
  lines = text.split("\n")
34
- extra_lines = 0
35
- if len(lines) > max_lines:
36
- extra_lines = len(lines) - max_lines
37
- lines = lines[:max_lines]
34
+ truncated_lines = 0
35
+ head_lines: list[str] = []
36
+ tail_lines: list[str] = []
38
37
 
39
- out = Text()
40
- if base_style is not None:
41
- out.style = base_style
38
+ if len(lines) > max_lines:
39
+ truncated_lines = len(lines) - max_lines
40
+ head_count = max_lines // 2
41
+ tail_count = max_lines - head_count
42
+ head_lines = lines[:head_count]
43
+ tail_lines = lines[-tail_count:]
44
+ else:
45
+ head_lines = lines
42
46
 
43
- for idx, line in enumerate(lines):
47
+ def append_line(out: Text, line: str) -> None:
44
48
  if len(line) > max_line_length:
45
49
  extra_chars = len(line) - max_line_length
46
50
  out.append(line[:max_line_length])
@@ -53,10 +57,21 @@ def truncate_display(
53
57
  else:
54
58
  out.append(line)
55
59
 
56
- if idx != len(lines) - 1 or extra_lines > 0:
60
+ out = Text()
61
+ if base_style is not None:
62
+ out.style = base_style
63
+
64
+ for idx, line in enumerate(head_lines):
65
+ append_line(out, line)
66
+ if idx < len(head_lines) - 1 or truncated_lines > 0 or tail_lines:
57
67
  out.append("\n")
58
68
 
59
- if extra_lines > 0:
60
- out.append_text(Text(f" (more {extra_lines} lines)", style=ThemeKey.TOOL_RESULT_TRUNCATED))
69
+ if truncated_lines > 0:
70
+ out.append_text(Text(f" (more {truncated_lines} lines)\n", style=ThemeKey.TOOL_RESULT_TRUNCATED))
71
+
72
+ for idx, line in enumerate(tail_lines):
73
+ append_line(out, line)
74
+ if idx < len(tail_lines) - 1:
75
+ out.append("\n")
61
76
 
62
77
  return out
@@ -4,7 +4,6 @@ from rich.table import Table
4
4
  from rich.text import Text
5
5
 
6
6
  from klaude_code.protocol import commands, events, model
7
- from klaude_code.ui.renderers import diffs as r_diffs
8
7
  from klaude_code.ui.renderers.common import create_grid, truncate_display
9
8
  from klaude_code.ui.renderers.tools import render_path
10
9
  from klaude_code.ui.rich.markdown import NoInsetMarkdown
@@ -18,6 +17,7 @@ def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
18
17
  or e.item.todo_use
19
18
  or e.item.at_files
20
19
  or e.item.user_image_count
20
+ or e.item.skill_name
21
21
  )
22
22
 
23
23
 
@@ -94,6 +94,17 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
94
94
  )
95
95
  parts.append(grid)
96
96
 
97
+ if sn := e.item.skill_name:
98
+ grid = create_grid()
99
+ grid.add_row(
100
+ Text(" +", style=ThemeKey.REMINDER),
101
+ Text.assemble(
102
+ ("Activated skill ", ThemeKey.REMINDER),
103
+ (sn, ThemeKey.REMINDER_BOLD),
104
+ ),
105
+ )
106
+ parts.append(grid)
107
+
97
108
  return Group(*parts) if parts else Text("")
98
109
 
99
110
 
@@ -103,10 +114,6 @@ def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
103
114
  return Text("")
104
115
 
105
116
  match e.item.command_output.command_name:
106
- case commands.CommandName.DIFF:
107
- if e.item.content is None or len(e.item.content) == 0:
108
- return Padding.indent(Text("(no changes)", style=ThemeKey.TOOL_RESULT), level=2)
109
- return r_diffs.render_diff_panel(e.item.content, show_file_name=True)
110
117
  case commands.CommandName.HELP:
111
118
  return Padding.indent(Text.from_markup(e.item.content or ""), level=2)
112
119
  case commands.CommandName.STATUS:
@@ -5,6 +5,7 @@ from rich.panel import Panel
5
5
  from rich.text import Text
6
6
 
7
7
  from klaude_code import const
8
+ from klaude_code.protocol import model
8
9
  from klaude_code.ui.renderers.common import create_grid
9
10
  from klaude_code.ui.rich.theme import ThemeKey
10
11
 
@@ -179,11 +180,35 @@ def render_diff(diff_text: str, show_file_name: bool = False) -> RenderableType:
179
180
  return grid
180
181
 
181
182
 
183
+ def render_structured_diff(ui_extra: model.DiffUIExtra, show_file_name: bool = False) -> RenderableType:
184
+ files = ui_extra.files
185
+ if not files:
186
+ return Text("")
187
+
188
+ grid = create_grid()
189
+ grid.padding = (0, 0)
190
+ show_headers = show_file_name or len(files) > 1
191
+
192
+ for idx, file_diff in enumerate(files):
193
+ if idx > 0:
194
+ grid.add_row("", "")
195
+
196
+ if show_headers:
197
+ grid.add_row(*_render_file_header(file_diff))
198
+
199
+ for line in file_diff.lines:
200
+ prefix = _make_structured_prefix(line, const.DIFF_PREFIX_WIDTH)
201
+ text = _render_structured_line(line)
202
+ grid.add_row(Text(prefix, ThemeKey.TOOL_RESULT), text)
203
+
204
+ return grid
205
+
206
+
182
207
  def render_diff_panel(
183
208
  diff_text: str,
184
209
  *,
185
210
  show_file_name: bool = True,
186
- heading: str = "Git Diff",
211
+ heading: str = "DIFF",
187
212
  indent: int = 2,
188
213
  ) -> RenderableType:
189
214
  lines = diff_text.splitlines()
@@ -210,3 +235,62 @@ def render_diff_panel(
210
235
  if indent <= 0:
211
236
  return panel
212
237
  return Padding.indent(panel, level=indent)
238
+
239
+
240
+ def _render_file_header(file_diff: model.DiffFileDiff) -> tuple[Text, Text]:
241
+ file_text = Text(file_diff.file_path, style=ThemeKey.DIFF_FILE_NAME)
242
+ stats_text = Text()
243
+ if file_diff.stats_add > 0:
244
+ stats_text.append(f"+{file_diff.stats_add}", style=ThemeKey.DIFF_STATS_ADD)
245
+ if file_diff.stats_remove > 0:
246
+ if stats_text.plain:
247
+ stats_text.append(" ")
248
+ stats_text.append(f"-{file_diff.stats_remove}", style=ThemeKey.DIFF_STATS_REMOVE)
249
+
250
+ file_line = Text(style=ThemeKey.DIFF_FILE_NAME)
251
+ file_line.append_text(file_text)
252
+ if stats_text.plain:
253
+ file_line.append(" (")
254
+ file_line.append_text(stats_text)
255
+ file_line.append(")")
256
+
257
+ if file_diff.stats_add > 0 and file_diff.stats_remove == 0:
258
+ file_mark = "+"
259
+ elif file_diff.stats_remove > 0 and file_diff.stats_add == 0:
260
+ file_mark = "-"
261
+ else:
262
+ file_mark = "±"
263
+
264
+ prefix = Text(f"{file_mark:>{const.DIFF_PREFIX_WIDTH}} ", style=ThemeKey.DIFF_FILE_NAME)
265
+ return prefix, file_line
266
+
267
+
268
+ def _make_structured_prefix(line: model.DiffLine, width: int) -> str:
269
+ if line.kind == "gap":
270
+ return f"{'⋮':>{width}} "
271
+ number = " " * width
272
+ if line.kind in {"add", "ctx"} and line.new_line_no is not None:
273
+ number = f"{line.new_line_no:>{width}}"
274
+ marker = "+" if line.kind == "add" else "-" if line.kind == "remove" else " "
275
+ return f"{number} {marker}"
276
+
277
+
278
+ def _render_structured_line(line: model.DiffLine) -> Text:
279
+ if line.kind == "gap":
280
+ return Text("")
281
+ text = Text()
282
+ for span in line.spans:
283
+ text.append(span.text, style=_span_style(line.kind, span.op))
284
+ return text
285
+
286
+
287
+ def _span_style(line_kind: str, span_op: str) -> ThemeKey:
288
+ if line_kind == "add":
289
+ if span_op == "insert":
290
+ return ThemeKey.DIFF_ADD_CHAR
291
+ return ThemeKey.DIFF_ADD
292
+ if line_kind == "remove":
293
+ if span_op == "delete":
294
+ return ThemeKey.DIFF_REMOVE_CHAR
295
+ return ThemeKey.DIFF_REMOVE
296
+ return ThemeKey.TOOL_RESULT
@@ -45,7 +45,7 @@ def _render_task_metadata_block(
45
45
  currency_symbol = "¥" if currency == "CNY" else "$"
46
46
 
47
47
  # First column: mark only
48
- mark = Text("└", style=ThemeKey.METADATA_DIM) if is_sub_agent else Text("", style=ThemeKey.METADATA_BOLD)
48
+ mark = Text("└", style=ThemeKey.METADATA_DIM) if is_sub_agent else Text("", style=ThemeKey.METADATA)
49
49
 
50
50
  # Second column: model@provider / tokens / cost / ...
51
51
  content = Text()
@@ -151,7 +151,9 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
151
151
  """Render task metadata including main agent and sub-agents, aggregated by model+provider."""
152
152
  renderables: list[RenderableType] = []
153
153
 
154
- renderables.append(_render_task_metadata_block(e.metadata.main, is_sub_agent=False, show_context_and_time=True))
154
+ renderables.append(
155
+ _render_task_metadata_block(e.metadata.main_agent, is_sub_agent=False, show_context_and_time=True)
156
+ )
155
157
 
156
158
  # Aggregate by (model_name, provider), sorted by total_cost descending
157
159
  sorted_items = model.TaskMetadata.aggregate_by_model(e.metadata.sub_agent_task_metadata)
@@ -9,7 +9,7 @@ from klaude_code.ui.rich.theme import ThemeKey
9
9
 
10
10
 
11
11
  def thinking_prefix() -> Text:
12
- return Text.from_markup("[not italic]⸫[/not italic] Thinking …", style=ThemeKey.THINKING)
12
+ return Text.from_markup("[not italic]⸫[/not italic] Thinking …", style=ThemeKey.THINKING_BOLD)
13
13
 
14
14
 
15
15
  def normalize_thinking_content(content: str) -> str: