klaude-code 1.2.18__py3-none-any.whl → 1.2.20__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 (74) hide show
  1. klaude_code/cli/main.py +42 -22
  2. klaude_code/cli/runtime.py +46 -2
  3. klaude_code/{version.py → cli/self_update.py} +110 -2
  4. klaude_code/command/__init__.py +1 -3
  5. klaude_code/command/clear_cmd.py +5 -4
  6. klaude_code/command/command_abc.py +5 -40
  7. klaude_code/command/debug_cmd.py +2 -2
  8. klaude_code/command/diff_cmd.py +2 -1
  9. klaude_code/command/export_cmd.py +14 -49
  10. klaude_code/command/export_online_cmd.py +10 -4
  11. klaude_code/command/help_cmd.py +2 -1
  12. klaude_code/command/model_cmd.py +7 -5
  13. klaude_code/command/prompt-jj-workspace.md +18 -0
  14. klaude_code/command/prompt_command.py +16 -9
  15. klaude_code/command/refresh_cmd.py +3 -2
  16. klaude_code/command/registry.py +98 -28
  17. klaude_code/command/release_notes_cmd.py +2 -1
  18. klaude_code/command/status_cmd.py +2 -1
  19. klaude_code/command/terminal_setup_cmd.py +2 -1
  20. klaude_code/command/thinking_cmd.py +6 -4
  21. klaude_code/core/executor.py +187 -180
  22. klaude_code/core/manager/sub_agent_manager.py +3 -0
  23. klaude_code/core/prompt.py +4 -1
  24. klaude_code/core/prompts/prompt-sub-agent-explore.md +14 -2
  25. klaude_code/core/prompts/prompt-sub-agent-web.md +3 -3
  26. klaude_code/core/reminders.py +70 -26
  27. klaude_code/core/task.py +13 -12
  28. klaude_code/core/tool/__init__.py +2 -0
  29. klaude_code/core/tool/file/apply_patch_tool.py +3 -1
  30. klaude_code/core/tool/file/edit_tool.py +7 -5
  31. klaude_code/core/tool/file/multi_edit_tool.py +7 -5
  32. klaude_code/core/tool/file/read_tool.md +1 -1
  33. klaude_code/core/tool/file/read_tool.py +8 -4
  34. klaude_code/core/tool/file/write_tool.py +8 -6
  35. klaude_code/core/tool/memory/skill_loader.py +12 -10
  36. klaude_code/core/tool/shell/bash_tool.py +89 -17
  37. klaude_code/core/tool/sub_agent_tool.py +5 -1
  38. klaude_code/core/tool/tool_abc.py +18 -0
  39. klaude_code/core/tool/tool_context.py +6 -6
  40. klaude_code/core/tool/tool_registry.py +1 -1
  41. klaude_code/core/tool/tool_runner.py +7 -7
  42. klaude_code/core/tool/web/web_fetch_tool.py +77 -22
  43. klaude_code/core/tool/web/web_search_tool.py +5 -1
  44. klaude_code/llm/anthropic/client.py +25 -9
  45. klaude_code/llm/openai_compatible/client.py +5 -2
  46. klaude_code/llm/openrouter/client.py +7 -3
  47. klaude_code/llm/responses/client.py +6 -1
  48. klaude_code/protocol/model.py +8 -1
  49. klaude_code/protocol/op.py +47 -0
  50. klaude_code/protocol/op_handler.py +25 -1
  51. klaude_code/protocol/sub_agent/web.py +1 -1
  52. klaude_code/session/codec.py +71 -0
  53. klaude_code/session/export.py +21 -11
  54. klaude_code/session/session.py +186 -322
  55. klaude_code/session/store.py +215 -0
  56. klaude_code/session/templates/export_session.html +48 -47
  57. klaude_code/ui/modes/repl/completers.py +211 -71
  58. klaude_code/ui/modes/repl/event_handler.py +7 -23
  59. klaude_code/ui/modes/repl/input_prompt_toolkit.py +5 -7
  60. klaude_code/ui/modes/repl/renderer.py +2 -2
  61. klaude_code/ui/renderers/common.py +54 -0
  62. klaude_code/ui/renderers/developer.py +2 -3
  63. klaude_code/ui/renderers/errors.py +1 -1
  64. klaude_code/ui/renderers/metadata.py +10 -1
  65. klaude_code/ui/renderers/tools.py +3 -4
  66. klaude_code/ui/rich/__init__.py +10 -1
  67. klaude_code/ui/rich/cjk_wrap.py +228 -0
  68. klaude_code/ui/rich/status.py +0 -1
  69. klaude_code/ui/utils/common.py +0 -18
  70. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/METADATA +18 -2
  71. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/RECORD +73 -70
  72. klaude_code/ui/utils/debouncer.py +0 -42
  73. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/WHEEL +0 -0
  74. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/entry_points.txt +0 -0
@@ -2,8 +2,8 @@ from importlib.resources import files
2
2
 
3
3
  import yaml
4
4
 
5
- from klaude_code.command.command_abc import Agent, CommandABC, CommandResult, InputAction
6
- from klaude_code.protocol import commands
5
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
+ from klaude_code.protocol import commands, model, op
7
7
  from klaude_code.trace import log_debug
8
8
 
9
9
 
@@ -55,16 +55,23 @@ class PromptCommand(CommandABC):
55
55
  def support_addition_params(self) -> bool:
56
56
  return True
57
57
 
58
- async def run(self, raw: str, agent: Agent) -> CommandResult:
58
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
59
59
  self._ensure_loaded()
60
60
  template_content = self._content or ""
61
- user_input = raw.strip() or "<none>"
61
+ user_input_text = user_input.text.strip() or "<none>"
62
62
 
63
63
  if "$ARGUMENTS" in template_content:
64
- final_prompt = template_content.replace("$ARGUMENTS", user_input)
64
+ final_prompt = template_content.replace("$ARGUMENTS", user_input_text)
65
65
  else:
66
66
  final_prompt = template_content
67
- if user_input:
68
- final_prompt += f"\n\nAdditional Instructions:\n{user_input}"
69
-
70
- return CommandResult(actions=[InputAction.run_agent(final_prompt)])
67
+ if user_input_text:
68
+ final_prompt += f"\n\nAdditional Instructions:\n{user_input_text}"
69
+
70
+ return CommandResult(
71
+ operations=[
72
+ op.RunAgentOperation(
73
+ session_id=agent.session.id,
74
+ input=model.UserInputPayload(text=final_prompt, images=user_input.images),
75
+ )
76
+ ]
77
+ )
@@ -1,5 +1,5 @@
1
1
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
2
- from klaude_code.protocol import commands, events
2
+ from klaude_code.protocol import commands, events, model
3
3
 
4
4
 
5
5
  class RefreshTerminalCommand(CommandABC):
@@ -17,7 +17,8 @@ class RefreshTerminalCommand(CommandABC):
17
17
  def is_interactive(self) -> bool:
18
18
  return True
19
19
 
20
- async def run(self, raw: str, agent: Agent) -> CommandResult:
20
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
21
+ del user_input # unused
21
22
  import os
22
23
 
23
24
  os.system("cls" if os.name == "nt" else "clear")
@@ -1,9 +1,9 @@
1
1
  from importlib.resources import files
2
2
  from typing import TYPE_CHECKING
3
3
 
4
- from klaude_code.command.command_abc import Agent, CommandResult, InputAction
4
+ from klaude_code.command.command_abc import Agent, CommandResult
5
5
  from klaude_code.command.prompt_command import PromptCommand
6
- from klaude_code.protocol import commands, events, model
6
+ from klaude_code.protocol import commands, events, model, op
7
7
  from klaude_code.trace import log_debug
8
8
 
9
9
  if TYPE_CHECKING:
@@ -12,6 +12,68 @@ if TYPE_CHECKING:
12
12
  _COMMANDS: dict[commands.CommandName | str, "CommandABC"] = {}
13
13
 
14
14
 
15
+ def _command_key_to_str(key: commands.CommandName | str) -> str:
16
+ if isinstance(key, commands.CommandName):
17
+ return key.value
18
+ return key
19
+
20
+
21
+ def _resolve_command_key(command_name_raw: str) -> commands.CommandName | str | None:
22
+ """Resolve raw command token to a registered command key.
23
+
24
+ Resolution order:
25
+ 1) Exact match
26
+ 2) Enum conversion (for standard commands)
27
+ 3) Prefix match (supports abbreviations like `exp` -> `export`)
28
+
29
+ Prefix match rules:
30
+ - If there's exactly one prefix match, use it.
31
+ - If multiple matches exist and one command name is a prefix of all others,
32
+ treat it as the base command and use it (e.g. `export` over `export-online`).
33
+ - Otherwise, consider it ambiguous and return None.
34
+ """
35
+
36
+ if not command_name_raw:
37
+ return None
38
+
39
+ # Exact string match (works for both Enum and str keys because CommandName is a str Enum)
40
+ if command_name_raw in _COMMANDS:
41
+ return command_name_raw
42
+
43
+ # Enum conversion for standard commands
44
+ try:
45
+ enum_key = commands.CommandName(command_name_raw)
46
+ except ValueError:
47
+ enum_key = None
48
+ else:
49
+ if enum_key in _COMMANDS:
50
+ return enum_key
51
+
52
+ # Prefix match across all registered names
53
+ matching_keys: list[commands.CommandName | str] = []
54
+ matching_names: list[str] = []
55
+ for key in _COMMANDS:
56
+ key_str = _command_key_to_str(key)
57
+ if key_str.startswith(command_name_raw):
58
+ matching_keys.append(key)
59
+ matching_names.append(key_str)
60
+
61
+ if len(matching_keys) == 1:
62
+ return matching_keys[0]
63
+
64
+ if len(matching_keys) > 1:
65
+ # Prefer the base command when one is a prefix of all other matches.
66
+ base_matches = [
67
+ key
68
+ for key, key_name in zip(matching_keys, matching_names, strict=True)
69
+ if all(other.startswith(key_name) for other in matching_names if other != key_name)
70
+ ]
71
+ if len(base_matches) == 1:
72
+ return base_matches[0]
73
+
74
+ return None
75
+
76
+
15
77
  def register(cmd: "CommandABC") -> None:
16
78
  """Register a command instance. Order of registration determines display order."""
17
79
  _COMMANDS[cmd.name] = cmd
@@ -45,42 +107,53 @@ def get_commands() -> dict[commands.CommandName | str, "CommandABC"]:
45
107
 
46
108
  def is_slash_command_name(name: str) -> bool:
47
109
  _ensure_commands_loaded()
48
- return name in _COMMANDS
110
+ return _resolve_command_key(name) is not None
49
111
 
50
112
 
51
- async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
113
+ async def dispatch_command(user_input: model.UserInputPayload, agent: Agent, *, submission_id: str) -> CommandResult:
52
114
  _ensure_commands_loaded()
53
115
  # Detect command name
116
+ raw = user_input.text
54
117
  if not raw.startswith("/"):
55
- return CommandResult(actions=[InputAction.run_agent(raw)])
118
+ return CommandResult(
119
+ operations=[
120
+ op.RunAgentOperation(
121
+ id=submission_id,
122
+ session_id=agent.session.id,
123
+ input=user_input,
124
+ )
125
+ ]
126
+ )
56
127
 
57
128
  splits = raw.split(" ", maxsplit=1)
58
129
  command_name_raw = splits[0][1:]
59
130
  rest = " ".join(splits[1:]) if len(splits) > 1 else ""
60
131
 
61
- # Try to match against registered commands (both Enum and string keys)
62
- command_key = None
63
-
64
- # First try exact string match
65
- if command_name_raw in _COMMANDS:
66
- command_key = command_name_raw
67
- else:
68
- # Then try Enum conversion for standard commands
69
- try:
70
- enum_key = commands.CommandName(command_name_raw)
71
- if enum_key in _COMMANDS:
72
- command_key = enum_key
73
- except ValueError:
74
- pass
75
-
132
+ command_key = _resolve_command_key(command_name_raw)
76
133
  if command_key is None:
77
- return CommandResult(actions=[InputAction.run_agent(raw)])
134
+ return CommandResult(
135
+ operations=[
136
+ op.RunAgentOperation(
137
+ id=submission_id,
138
+ session_id=agent.session.id,
139
+ input=user_input,
140
+ )
141
+ ]
142
+ )
78
143
 
79
144
  command = _COMMANDS[command_key]
80
145
  command_identifier: commands.CommandName | str = command.name
81
146
 
82
147
  try:
83
- return await command.run(rest, agent)
148
+ user_input_for_command = model.UserInputPayload(text=rest, images=user_input.images)
149
+ result = await command.run(agent, user_input_for_command)
150
+ ops = list(result.operations or [])
151
+ for operation in ops:
152
+ if isinstance(operation, op.RunAgentOperation):
153
+ operation.id = submission_id
154
+ if ops:
155
+ result.operations = ops
156
+ return result
84
157
  except Exception as e:
85
158
  command_output = (
86
159
  model.CommandOutput(command_name=command_identifier, is_error=True)
@@ -106,11 +179,8 @@ def has_interactive_command(raw: str) -> bool:
106
179
  return False
107
180
  splits = raw.split(" ", maxsplit=1)
108
181
  command_name_raw = splits[0][1:]
109
- try:
110
- command_name = commands.CommandName(command_name_raw)
111
- except ValueError:
112
- return False
113
- if command_name not in _COMMANDS:
182
+ command_key = _resolve_command_key(command_name_raw)
183
+ if command_key is None:
114
184
  return False
115
- command = _COMMANDS[command_name]
185
+ command = _COMMANDS[command_key]
116
186
  return command.is_interactive
@@ -68,7 +68,8 @@ class ReleaseNotesCommand(CommandABC):
68
68
  def summary(self) -> str:
69
69
  return "Show the latest release notes"
70
70
 
71
- async def run(self, raw: str, agent: Agent) -> CommandResult:
71
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
72
+ del user_input # unused
72
73
  changelog = _read_changelog()
73
74
  content = _extract_releases(changelog, count=10)
74
75
 
@@ -132,7 +132,8 @@ class StatusCommand(CommandABC):
132
132
  def summary(self) -> str:
133
133
  return "Show session usage statistics"
134
134
 
135
- async def run(self, raw: str, agent: Agent) -> CommandResult:
135
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
136
+ del user_input # unused
136
137
  session = agent.session
137
138
  aggregated = accumulate_session_usage(session)
138
139
 
@@ -21,7 +21,8 @@ class TerminalSetupCommand(CommandABC):
21
21
  def is_interactive(self) -> bool:
22
22
  return False
23
23
 
24
- async def run(self, raw: str, agent: Agent) -> CommandResult:
24
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
25
+ del user_input # unused
25
26
  term_program = os.environ.get("TERM_PROGRAM", "").lower()
26
27
 
27
28
  try:
@@ -6,9 +6,9 @@ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
6
  from klaude_code.protocol import commands, events, llm_param, model
7
7
 
8
8
  # Thinking level options for different protocols
9
- RESPONSES_LEVELS = ["minimal", "low", "medium", "high"]
10
- RESPONSES_GPT51_LEVELS = ["none", "minimal", "low", "medium", "high"]
11
- RESPONSES_GPT52_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh"]
9
+ RESPONSES_LEVELS = ["low", "medium", "high"]
10
+ RESPONSES_GPT51_LEVELS = ["none", "low", "medium", "high"]
11
+ RESPONSES_GPT52_LEVELS = ["none", "low", "medium", "high", "xhigh"]
12
12
  RESPONSES_CODEX_MAX_LEVELS = ["medium", "high", "xhigh"]
13
13
 
14
14
  ANTHROPIC_LEVELS: list[tuple[str, int | None]] = [
@@ -169,7 +169,8 @@ class ThinkingCommand(CommandABC):
169
169
  def is_interactive(self) -> bool:
170
170
  return True
171
171
 
172
- async def run(self, raw: str, agent: Agent) -> CommandResult:
172
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
173
+ del user_input # unused
173
174
  if not agent.profile:
174
175
  return self._no_change_result(agent, "No profile configured")
175
176
 
@@ -206,6 +207,7 @@ class ThinkingCommand(CommandABC):
206
207
 
207
208
  # Apply the new thinking configuration
208
209
  config.thinking = new_thinking
210
+ agent.session.model_thinking = new_thinking
209
211
  new_status = _format_current_thinking(config)
210
212
 
211
213
  return CommandResult(