klaude-code 1.2.6__py3-none-any.whl → 1.8.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.
Files changed (205) hide show
  1. klaude_code/auth/__init__.py +24 -0
  2. klaude_code/auth/codex/__init__.py +20 -0
  3. klaude_code/auth/codex/exceptions.py +17 -0
  4. klaude_code/auth/codex/jwt_utils.py +45 -0
  5. klaude_code/auth/codex/oauth.py +229 -0
  6. klaude_code/auth/codex/token_manager.py +84 -0
  7. klaude_code/cli/auth_cmd.py +73 -0
  8. klaude_code/cli/config_cmd.py +91 -0
  9. klaude_code/cli/cost_cmd.py +338 -0
  10. klaude_code/cli/debug.py +78 -0
  11. klaude_code/cli/list_model.py +307 -0
  12. klaude_code/cli/main.py +233 -134
  13. klaude_code/cli/runtime.py +309 -117
  14. klaude_code/{version.py → cli/self_update.py} +114 -5
  15. klaude_code/cli/session_cmd.py +37 -21
  16. klaude_code/command/__init__.py +88 -27
  17. klaude_code/command/clear_cmd.py +8 -7
  18. klaude_code/command/command_abc.py +31 -31
  19. klaude_code/command/debug_cmd.py +79 -0
  20. klaude_code/command/export_cmd.py +19 -53
  21. klaude_code/command/export_online_cmd.py +154 -0
  22. klaude_code/command/fork_session_cmd.py +267 -0
  23. klaude_code/command/help_cmd.py +7 -8
  24. klaude_code/command/model_cmd.py +60 -10
  25. klaude_code/command/model_select.py +84 -0
  26. klaude_code/command/prompt-jj-describe.md +32 -0
  27. klaude_code/command/prompt_command.py +19 -11
  28. klaude_code/command/refresh_cmd.py +8 -10
  29. klaude_code/command/registry.py +139 -40
  30. klaude_code/command/release_notes_cmd.py +84 -0
  31. klaude_code/command/resume_cmd.py +111 -0
  32. klaude_code/command/status_cmd.py +104 -60
  33. klaude_code/command/terminal_setup_cmd.py +7 -9
  34. klaude_code/command/thinking_cmd.py +98 -0
  35. klaude_code/config/__init__.py +14 -6
  36. klaude_code/config/assets/__init__.py +1 -0
  37. klaude_code/config/assets/builtin_config.yaml +303 -0
  38. klaude_code/config/builtin_config.py +38 -0
  39. klaude_code/config/config.py +378 -109
  40. klaude_code/config/select_model.py +117 -53
  41. klaude_code/config/thinking.py +269 -0
  42. klaude_code/{const/__init__.py → const.py} +50 -19
  43. klaude_code/core/agent.py +20 -28
  44. klaude_code/core/executor.py +327 -112
  45. klaude_code/core/manager/__init__.py +2 -4
  46. klaude_code/core/manager/llm_clients.py +1 -15
  47. klaude_code/core/manager/llm_clients_builder.py +10 -11
  48. klaude_code/core/manager/sub_agent_manager.py +37 -6
  49. klaude_code/core/prompt.py +63 -44
  50. klaude_code/core/prompts/prompt-claude-code.md +2 -13
  51. klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
  52. klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
  53. klaude_code/core/prompts/prompt-codex.md +9 -42
  54. klaude_code/core/prompts/prompt-minimal.md +12 -0
  55. klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
  56. klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
  57. klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
  58. klaude_code/core/reminders.py +283 -95
  59. klaude_code/core/task.py +113 -75
  60. klaude_code/core/tool/__init__.py +24 -31
  61. klaude_code/core/tool/file/_utils.py +36 -0
  62. klaude_code/core/tool/file/apply_patch.py +17 -25
  63. klaude_code/core/tool/file/apply_patch_tool.py +57 -77
  64. klaude_code/core/tool/file/diff_builder.py +151 -0
  65. klaude_code/core/tool/file/edit_tool.py +50 -63
  66. klaude_code/core/tool/file/move_tool.md +41 -0
  67. klaude_code/core/tool/file/move_tool.py +435 -0
  68. klaude_code/core/tool/file/read_tool.md +1 -1
  69. klaude_code/core/tool/file/read_tool.py +86 -86
  70. klaude_code/core/tool/file/write_tool.py +59 -69
  71. klaude_code/core/tool/report_back_tool.py +84 -0
  72. klaude_code/core/tool/shell/bash_tool.py +265 -22
  73. klaude_code/core/tool/shell/command_safety.py +3 -6
  74. klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
  75. klaude_code/core/tool/sub_agent_tool.py +13 -2
  76. klaude_code/core/tool/todo/todo_write_tool.md +0 -157
  77. klaude_code/core/tool/todo/todo_write_tool.py +1 -1
  78. klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
  79. klaude_code/core/tool/todo/update_plan_tool.py +1 -1
  80. klaude_code/core/tool/tool_abc.py +18 -0
  81. klaude_code/core/tool/tool_context.py +27 -12
  82. klaude_code/core/tool/tool_registry.py +7 -7
  83. klaude_code/core/tool/tool_runner.py +44 -36
  84. klaude_code/core/tool/truncation.py +29 -14
  85. klaude_code/core/tool/web/mermaid_tool.md +43 -0
  86. klaude_code/core/tool/web/mermaid_tool.py +2 -5
  87. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  88. klaude_code/core/tool/web/web_fetch_tool.py +112 -22
  89. klaude_code/core/tool/web/web_search_tool.md +23 -0
  90. klaude_code/core/tool/web/web_search_tool.py +130 -0
  91. klaude_code/core/turn.py +168 -66
  92. klaude_code/llm/__init__.py +2 -10
  93. klaude_code/llm/anthropic/client.py +190 -178
  94. klaude_code/llm/anthropic/input.py +39 -15
  95. klaude_code/llm/bedrock/__init__.py +3 -0
  96. klaude_code/llm/bedrock/client.py +60 -0
  97. klaude_code/llm/client.py +7 -21
  98. klaude_code/llm/codex/__init__.py +5 -0
  99. klaude_code/llm/codex/client.py +149 -0
  100. klaude_code/llm/google/__init__.py +3 -0
  101. klaude_code/llm/google/client.py +309 -0
  102. klaude_code/llm/google/input.py +215 -0
  103. klaude_code/llm/input_common.py +3 -9
  104. klaude_code/llm/openai_compatible/client.py +72 -164
  105. klaude_code/llm/openai_compatible/input.py +6 -4
  106. klaude_code/llm/openai_compatible/stream.py +273 -0
  107. klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
  108. klaude_code/llm/openrouter/client.py +89 -160
  109. klaude_code/llm/openrouter/input.py +18 -30
  110. klaude_code/llm/openrouter/reasoning.py +118 -0
  111. klaude_code/llm/registry.py +39 -7
  112. klaude_code/llm/responses/client.py +184 -171
  113. klaude_code/llm/responses/input.py +20 -1
  114. klaude_code/llm/usage.py +17 -12
  115. klaude_code/protocol/commands.py +17 -1
  116. klaude_code/protocol/events.py +31 -4
  117. klaude_code/protocol/llm_param.py +13 -10
  118. klaude_code/protocol/model.py +232 -29
  119. klaude_code/protocol/op.py +90 -1
  120. klaude_code/protocol/op_handler.py +35 -1
  121. klaude_code/protocol/sub_agent/__init__.py +117 -0
  122. klaude_code/protocol/sub_agent/explore.py +63 -0
  123. klaude_code/protocol/sub_agent/oracle.py +91 -0
  124. klaude_code/protocol/sub_agent/task.py +61 -0
  125. klaude_code/protocol/sub_agent/web.py +79 -0
  126. klaude_code/protocol/tools.py +4 -2
  127. klaude_code/session/__init__.py +2 -2
  128. klaude_code/session/codec.py +71 -0
  129. klaude_code/session/export.py +293 -86
  130. klaude_code/session/selector.py +89 -67
  131. klaude_code/session/session.py +320 -309
  132. klaude_code/session/store.py +220 -0
  133. klaude_code/session/templates/export_session.html +595 -83
  134. klaude_code/session/templates/mermaid_viewer.html +926 -0
  135. klaude_code/skill/__init__.py +27 -0
  136. klaude_code/skill/assets/deslop/SKILL.md +17 -0
  137. klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
  138. klaude_code/skill/assets/handoff/SKILL.md +39 -0
  139. klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
  140. klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
  141. klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
  142. klaude_code/skill/manager.py +70 -0
  143. klaude_code/skill/system_skills.py +192 -0
  144. klaude_code/trace/__init__.py +20 -2
  145. klaude_code/trace/log.py +150 -5
  146. klaude_code/ui/__init__.py +4 -9
  147. klaude_code/ui/core/input.py +1 -1
  148. klaude_code/ui/core/stage_manager.py +7 -7
  149. klaude_code/ui/modes/debug/display.py +2 -1
  150. klaude_code/ui/modes/repl/__init__.py +3 -48
  151. klaude_code/ui/modes/repl/clipboard.py +5 -5
  152. klaude_code/ui/modes/repl/completers.py +487 -123
  153. klaude_code/ui/modes/repl/display.py +5 -4
  154. klaude_code/ui/modes/repl/event_handler.py +370 -117
  155. klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
  156. klaude_code/ui/modes/repl/key_bindings.py +146 -23
  157. klaude_code/ui/modes/repl/renderer.py +189 -99
  158. klaude_code/ui/renderers/assistant.py +9 -2
  159. klaude_code/ui/renderers/bash_syntax.py +178 -0
  160. klaude_code/ui/renderers/common.py +78 -0
  161. klaude_code/ui/renderers/developer.py +104 -48
  162. klaude_code/ui/renderers/diffs.py +87 -6
  163. klaude_code/ui/renderers/errors.py +11 -6
  164. klaude_code/ui/renderers/mermaid_viewer.py +57 -0
  165. klaude_code/ui/renderers/metadata.py +112 -76
  166. klaude_code/ui/renderers/sub_agent.py +92 -7
  167. klaude_code/ui/renderers/thinking.py +40 -18
  168. klaude_code/ui/renderers/tools.py +405 -227
  169. klaude_code/ui/renderers/user_input.py +73 -13
  170. klaude_code/ui/rich/__init__.py +10 -1
  171. klaude_code/ui/rich/cjk_wrap.py +228 -0
  172. klaude_code/ui/rich/code_panel.py +131 -0
  173. klaude_code/ui/rich/live.py +17 -0
  174. klaude_code/ui/rich/markdown.py +305 -170
  175. klaude_code/ui/rich/searchable_text.py +10 -13
  176. klaude_code/ui/rich/status.py +190 -49
  177. klaude_code/ui/rich/theme.py +135 -39
  178. klaude_code/ui/terminal/__init__.py +55 -0
  179. klaude_code/ui/terminal/color.py +1 -1
  180. klaude_code/ui/terminal/control.py +13 -22
  181. klaude_code/ui/terminal/notifier.py +44 -4
  182. klaude_code/ui/terminal/selector.py +658 -0
  183. klaude_code/ui/utils/common.py +0 -18
  184. klaude_code-1.8.0.dist-info/METADATA +377 -0
  185. klaude_code-1.8.0.dist-info/RECORD +219 -0
  186. {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
  187. klaude_code/command/diff_cmd.py +0 -138
  188. klaude_code/command/prompt-dev-docs-update.md +0 -56
  189. klaude_code/command/prompt-dev-docs.md +0 -46
  190. klaude_code/config/list_model.py +0 -162
  191. klaude_code/core/manager/agent_manager.py +0 -127
  192. klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
  193. klaude_code/core/tool/file/multi_edit_tool.md +0 -42
  194. klaude_code/core/tool/file/multi_edit_tool.py +0 -199
  195. klaude_code/core/tool/memory/memory_tool.md +0 -16
  196. klaude_code/core/tool/memory/memory_tool.py +0 -462
  197. klaude_code/llm/openrouter/reasoning_handler.py +0 -209
  198. klaude_code/protocol/sub_agent.py +0 -348
  199. klaude_code/ui/utils/debouncer.py +0 -42
  200. klaude_code-1.2.6.dist-info/METADATA +0 -178
  201. klaude_code-1.2.6.dist-info/RECORD +0 -167
  202. /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
  203. /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
  204. /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
  205. {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
1
- """Version checking utilities for klaude-code."""
1
+ """Self-update and version utilities for klaude-code."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -8,8 +8,14 @@ import subprocess
8
8
  import threading
9
9
  import time
10
10
  import urllib.request
11
+ from importlib.metadata import PackageNotFoundError
12
+ from importlib.metadata import version as pkg_version
11
13
  from typing import NamedTuple
12
14
 
15
+ import typer
16
+
17
+ from klaude_code.trace import log
18
+
13
19
  PACKAGE_NAME = "klaude-code"
14
20
  PYPI_URL = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
15
21
  CHECK_INTERVAL_SECONDS = 3600 # Check at most once per hour
@@ -57,7 +63,7 @@ def _get_installed_version() -> str | None:
57
63
  ver = ver[1:]
58
64
  return ver
59
65
  return None
60
- except Exception:
66
+ except (OSError, subprocess.SubprocessError):
61
67
  return None
62
68
 
63
69
 
@@ -67,7 +73,7 @@ def _get_latest_version() -> str | None:
67
73
  with urllib.request.urlopen(PYPI_URL, timeout=5) as response:
68
74
  data = json.loads(response.read().decode())
69
75
  return data.get("info", {}).get("version")
70
- except Exception:
76
+ except (OSError, json.JSONDecodeError, ValueError):
71
77
  return None
72
78
 
73
79
 
@@ -93,7 +99,7 @@ def _compare_versions(installed: str, latest: str) -> bool:
93
99
  installed_tuple = _parse_version(installed)
94
100
  latest_tuple = _parse_version(latest)
95
101
  return latest_tuple > installed_tuple
96
- except Exception:
102
+ except ValueError:
97
103
  return False
98
104
 
99
105
 
@@ -160,4 +166,107 @@ def get_update_message() -> str | None:
160
166
  info = check_for_updates()
161
167
  if info is None or not info.update_available:
162
168
  return None
163
- return f"New version available: {info.latest}. Please run `uv tool upgrade {PACKAGE_NAME}` to upgrade."
169
+ return f"New version available: {info.latest}. Please run `klaude upgrade` to upgrade."
170
+
171
+
172
+ def _print_version() -> None:
173
+ try:
174
+ ver = pkg_version(PACKAGE_NAME)
175
+ except PackageNotFoundError:
176
+ ver = "unknown"
177
+ except (ValueError, TypeError):
178
+ # Catch invalid package name format or type errors
179
+ ver = "unknown"
180
+ print(f"{PACKAGE_NAME} {ver}")
181
+
182
+
183
+ def version_option_callback(value: bool) -> None:
184
+ """Show version and exit."""
185
+ if value:
186
+ _print_version()
187
+ raise typer.Exit(0)
188
+
189
+
190
+ def version_command() -> None:
191
+ """Show version and exit."""
192
+
193
+ _print_version()
194
+
195
+
196
+ def update_command(
197
+ check: bool = typer.Option(
198
+ False,
199
+ "--check",
200
+ help="Check for updates and exit without upgrading",
201
+ ),
202
+ ) -> None:
203
+ """Upgrade klaude-code when installed via `uv tool`."""
204
+
205
+ info = check_for_updates_blocking()
206
+
207
+ if check:
208
+ if info is None:
209
+ log(("Error: `uv` is not available; cannot check for updates.", "red"))
210
+ log(f"Install uv, then run `uv tool upgrade {PACKAGE_NAME}`.")
211
+ raise typer.Exit(1)
212
+
213
+ installed_display = info.installed or "unknown"
214
+ latest_display = info.latest or "unknown"
215
+ status = "update available" if info.update_available else "up to date"
216
+
217
+ log(f"{PACKAGE_NAME} installed: {installed_display}")
218
+ log(f"{PACKAGE_NAME} latest: {latest_display}")
219
+ log(f"Status: {status}")
220
+
221
+ if info.update_available:
222
+ log("Run `klaude upgrade` to upgrade.")
223
+
224
+ return
225
+
226
+ if shutil.which("uv") is None:
227
+ log(("Error: `uv` not found in PATH.", "red"))
228
+ log(f"To update, install uv and run `uv tool upgrade {PACKAGE_NAME}`.")
229
+ raise typer.Exit(1)
230
+
231
+ log(f"Running `uv tool upgrade {PACKAGE_NAME}`...")
232
+ result = subprocess.run(["uv", "tool", "upgrade", PACKAGE_NAME], check=False)
233
+ if result.returncode != 0:
234
+ log((f"Error: update failed (exit code {result.returncode}).", "red"))
235
+ raise typer.Exit(result.returncode or 1)
236
+
237
+ log("Update complete. Please re-run `klaude` to use the new version.")
238
+
239
+
240
+ def register_self_update_commands(app: typer.Typer) -> None:
241
+ """Register self-update and version subcommands to the given Typer app."""
242
+
243
+ app.command("update")(update_command)
244
+ app.command("upgrade", help="Alias for `klaude update`.")(update_command)
245
+ app.command("version", help="Alias for `klaude --version`.")(version_command)
246
+
247
+
248
+ def check_for_updates_blocking() -> VersionInfo | None:
249
+ """Check for updates to klaude-code synchronously.
250
+
251
+ This is intended for CLI commands (e.g. `klaude update --check`) that need
252
+ a deterministic result instead of the async cached behavior.
253
+
254
+ Returns:
255
+ VersionInfo if uv is available, otherwise None.
256
+ """
257
+
258
+ if not _has_uv():
259
+ return None
260
+
261
+ installed = _get_installed_version()
262
+ latest = _get_latest_version()
263
+
264
+ update_available = False
265
+ if installed and latest:
266
+ update_available = _compare_versions(installed, latest)
267
+
268
+ return VersionInfo(
269
+ installed=installed,
270
+ latest=latest,
271
+ update_available=update_available,
272
+ )
@@ -7,31 +7,47 @@ from klaude_code.trace import log
7
7
 
8
8
 
9
9
  def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) -> bool:
10
- """Show session list and confirm deletion using questionary."""
11
- import questionary
10
+ """Show session list and confirm deletion using prompt_toolkit."""
11
+
12
+ from prompt_toolkit.styles import Style
13
+
14
+ from klaude_code.ui.terminal.selector import SelectItem, select_one
12
15
 
13
16
  def _fmt(ts: float) -> str:
14
17
  try:
15
18
  return time.strftime("%m-%d %H:%M:%S", time.localtime(ts))
16
- except Exception:
19
+ except (OSError, OverflowError, ValueError):
17
20
  return str(ts)
18
21
 
19
22
  log(f"Sessions to delete ({len(sessions)}):")
20
23
  for s in sessions:
21
24
  msg_count_display = "N/A" if s.messages_count == -1 else str(s.messages_count)
22
- first_msg = (s.first_user_message or "").strip().replace("\n", " ")[:50]
23
- if len(s.first_user_message or "") > 50:
25
+ first_msg_text = s.user_messages[0] if s.user_messages else ""
26
+ first_msg = first_msg_text.strip().replace("\n", " ")[:50]
27
+ if len(first_msg_text) > 50:
24
28
  first_msg += "..."
25
29
  log(f" {_fmt(s.updated_at)} {msg_count_display:>3} msgs {first_msg}")
26
30
 
27
- return (
28
- questionary.confirm(
29
- message,
30
- default=False,
31
- style=questionary.Style([("question", "bold")]),
32
- ).ask()
33
- or False
31
+ items: list[SelectItem[bool]] = [
32
+ SelectItem(title=[("class:text", "No\n")], value=False, search_text="No"),
33
+ SelectItem(title=[("class:text", "Yes\n")], value=True, search_text="Yes"),
34
+ ]
35
+
36
+ result = select_one(
37
+ message=message,
38
+ items=items,
39
+ pointer="→",
40
+ style=Style(
41
+ [
42
+ ("question", "bold"),
43
+ ("pointer", "ansigreen"),
44
+ ("highlighted", "ansigreen"),
45
+ ("text", ""),
46
+ ]
47
+ ),
48
+ use_search_filter=False,
34
49
  )
50
+ return bool(result)
35
51
 
36
52
 
37
53
  def session_clean(
@@ -46,10 +62,9 @@ def session_clean(
46
62
  log(f"No sessions with fewer than {min_messages} messages found.")
47
63
  return
48
64
 
49
- if not yes:
50
- if not _session_confirm(to_delete, "Delete these sessions?"):
51
- log("Aborted.")
52
- return
65
+ if not yes and not _session_confirm(to_delete, "Delete these sessions?"):
66
+ log("Aborted.")
67
+ return
53
68
 
54
69
  deleted = Session.clean_small_sessions(min_messages)
55
70
  log(f"Deleted {deleted} session(s).")
@@ -65,16 +80,17 @@ def session_clean_all(
65
80
  log("No sessions found.")
66
81
  return
67
82
 
68
- if not yes:
69
- if not _session_confirm(sessions, "Delete ALL sessions? This cannot be undone."):
70
- log("Aborted.")
71
- return
83
+ if not yes and not _session_confirm(sessions, "Delete ALL sessions? This cannot be undone."):
84
+ log("Aborted.")
85
+ return
72
86
 
73
87
  deleted = Session.clean_all_sessions()
74
88
  log(f"Deleted {deleted} session(s).")
75
89
 
76
90
 
77
- def register_session_commands(session_app: typer.Typer) -> None:
91
+ def register_session_commands(app: typer.Typer) -> None:
78
92
  """Register session subcommands to the given Typer app."""
93
+ session_app = typer.Typer(help="Manage sessions for the current project")
79
94
  session_app.command("clean")(session_clean)
80
95
  session_app.command("clean-all")(session_clean_all)
96
+ app.add_typer(session_app, name="session")
@@ -1,43 +1,104 @@
1
- from .clear_cmd import ClearCommand
2
- from .command_abc import CommandABC, CommandResult, InputAction, InputActionType
3
- from .diff_cmd import DiffCommand
4
- from .export_cmd import ExportCommand
5
- from .help_cmd import HelpCommand
6
-
7
- # InitCommand is now dynamically loaded via prompt_init.md
8
- # from .init_cmd import InitCommand
9
- from .model_cmd import ModelCommand
10
- from .refresh_cmd import RefreshTerminalCommand
1
+ from .command_abc import CommandABC, CommandResult
11
2
  from .registry import (
12
3
  dispatch_command,
4
+ get_command_info_list,
5
+ get_command_names,
13
6
  get_commands,
14
7
  has_interactive_command,
15
8
  is_slash_command_name,
16
9
  load_prompt_commands,
17
- register_command,
10
+ register,
18
11
  )
19
- from .status_cmd import StatusCommand
20
- from .terminal_setup_cmd import TerminalSetupCommand
21
12
 
22
- # Dynamically load prompt commands
23
- load_prompt_commands()
13
+ # Lazy load commands to avoid heavy imports at module load time
14
+ _commands_loaded = False
15
+
16
+
17
+ def ensure_commands_loaded() -> None:
18
+ """Ensure all commands are loaded (lazy initialization).
19
+
20
+ This function is called internally by registry functions like get_commands(),
21
+ dispatch_command(), etc. It can also be called explicitly if early loading is desired.
22
+
23
+ Commands are registered in display order - the order here determines
24
+ the order shown in slash command completion.
25
+ """
26
+ global _commands_loaded
27
+ if _commands_loaded:
28
+ return
29
+ _commands_loaded = True
30
+
31
+ # Import and register commands in display order
32
+ from .clear_cmd import ClearCommand
33
+ from .debug_cmd import DebugCommand
34
+ from .export_cmd import ExportCommand
35
+ from .export_online_cmd import ExportOnlineCommand
36
+ from .fork_session_cmd import ForkSessionCommand
37
+ from .help_cmd import HelpCommand
38
+ from .model_cmd import ModelCommand
39
+ from .refresh_cmd import RefreshTerminalCommand
40
+ from .release_notes_cmd import ReleaseNotesCommand
41
+ from .resume_cmd import ResumeCommand
42
+ from .status_cmd import StatusCommand
43
+ from .terminal_setup_cmd import TerminalSetupCommand
44
+ from .thinking_cmd import ThinkingCommand
45
+
46
+ # Register in desired display order
47
+ register(ExportCommand())
48
+ register(ExportOnlineCommand())
49
+ register(RefreshTerminalCommand())
50
+ register(ThinkingCommand())
51
+ register(ModelCommand())
52
+ register(ForkSessionCommand())
53
+ register(ResumeCommand())
54
+ load_prompt_commands()
55
+ register(StatusCommand())
56
+ register(HelpCommand())
57
+ register(ReleaseNotesCommand())
58
+ register(TerminalSetupCommand())
59
+ register(DebugCommand())
60
+ register(ClearCommand())
61
+
62
+ # Load prompt-based commands (appended after built-in commands)
63
+
64
+
65
+ # Lazy accessors for command classes
66
+ def __getattr__(name: str) -> object:
67
+ _commands_map = {
68
+ "ClearCommand": "clear_cmd",
69
+ "DebugCommand": "debug_cmd",
70
+ "ExportCommand": "export_cmd",
71
+ "ExportOnlineCommand": "export_online_cmd",
72
+ "ForkSessionCommand": "fork_session_cmd",
73
+ "HelpCommand": "help_cmd",
74
+ "ModelCommand": "model_cmd",
75
+ "RefreshTerminalCommand": "refresh_cmd",
76
+ "ReleaseNotesCommand": "release_notes_cmd",
77
+ "ResumeCommand": "resume_cmd",
78
+ "StatusCommand": "status_cmd",
79
+ "TerminalSetupCommand": "terminal_setup_cmd",
80
+ "ThinkingCommand": "thinking_cmd",
81
+ }
82
+ if name in _commands_map:
83
+ import importlib
84
+
85
+ module = importlib.import_module(f".{_commands_map[name]}", __package__)
86
+ return getattr(module, name)
87
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
88
+
24
89
 
25
90
  __all__ = [
26
- "ClearCommand",
27
- "DiffCommand",
28
- "HelpCommand",
29
- "ModelCommand",
30
- "ExportCommand",
31
- "RefreshTerminalCommand",
32
- "StatusCommand",
33
- "TerminalSetupCommand",
34
- "register_command",
91
+ # Command classes are lazily loaded via __getattr__
92
+ # "ClearCommand", "DiffCommand", "HelpCommand", "ModelCommand",
93
+ # "ExportCommand", "RefreshTerminalCommand", "ReleaseNotesCommand",
94
+ # "StatusCommand", "TerminalSetupCommand",
35
95
  "CommandABC",
36
96
  "CommandResult",
37
- "InputAction",
38
- "InputActionType",
39
97
  "dispatch_command",
98
+ "ensure_commands_loaded",
99
+ "get_command_info_list",
100
+ "get_command_names",
40
101
  "get_commands",
41
- "is_slash_command_name",
42
102
  "has_interactive_command",
103
+ "is_slash_command_name",
43
104
  ]
@@ -1,10 +1,7 @@
1
- from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
2
- from klaude_code.command.registry import register_command
3
- from klaude_code.core.agent import Agent
4
- from klaude_code.protocol import commands
1
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
2
+ from klaude_code.protocol import commands, model, op
5
3
 
6
4
 
7
- @register_command
8
5
  class ClearCommand(CommandABC):
9
6
  """Clear current session and start a new conversation"""
10
7
 
@@ -16,5 +13,9 @@ class ClearCommand(CommandABC):
16
13
  def summary(self) -> str:
17
14
  return "Clear conversation history and free up context"
18
15
 
19
- async def run(self, raw: str, agent: Agent) -> CommandResult:
20
- return CommandResult(actions=[InputAction.clear()])
16
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
17
+ del user_input # unused
18
+ return CommandResult(
19
+ operations=[op.ClearSessionOperation(session_id=agent.session.id)],
20
+ persist_user_input=False,
21
+ )
@@ -1,45 +1,36 @@
1
1
  from abc import ABC, abstractmethod
2
- from enum import Enum
2
+ from typing import Protocol
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
6
- from klaude_code.core.agent import Agent
7
- from klaude_code.protocol import commands
6
+ from klaude_code.llm import LLMClientABC
7
+ from klaude_code.protocol import commands, llm_param, model, op
8
8
  from klaude_code.protocol import events as protocol_events
9
+ from klaude_code.session.session import Session
9
10
 
10
11
 
11
- class InputActionType(str, Enum):
12
- """Supported input action kinds."""
12
+ class AgentProfile(Protocol):
13
+ """Protocol for the agent's active model profile."""
13
14
 
14
- RUN_AGENT = "run_agent"
15
- CHANGE_MODEL = "change_model"
16
- CLEAR = "clear"
17
-
18
-
19
- class InputAction(BaseModel):
20
- """Structured executor action derived from a user input."""
15
+ @property
16
+ def llm_client(self) -> LLMClientABC: ...
21
17
 
22
- type: InputActionType
23
- text: str = ""
24
- model_name: str | None = None
18
+ @property
19
+ def system_prompt(self) -> str | None: ...
25
20
 
26
- @classmethod
27
- def run_agent(cls, text: str) -> "InputAction":
28
- """Create a RunAgent action preserving the provided text."""
21
+ @property
22
+ def tools(self) -> list[llm_param.ToolSchema]: ...
29
23
 
30
- return cls(type=InputActionType.RUN_AGENT, text=text)
31
24
 
32
- @classmethod
33
- def change_model(cls, model_name: str) -> "InputAction":
34
- """Create a ChangeModel action for the provided model name."""
25
+ class Agent(Protocol):
26
+ """Protocol for Agent objects passed to commands."""
35
27
 
36
- return cls(type=InputActionType.CHANGE_MODEL, model_name=model_name)
28
+ session: Session
37
29
 
38
- @classmethod
39
- def clear(cls) -> "InputAction":
40
- """Create a Clear action to reset the session."""
30
+ @property
31
+ def profile(self) -> AgentProfile | None: ...
41
32
 
42
- return cls(type=InputActionType.CLEAR)
33
+ def get_llm_client(self) -> LLMClientABC: ...
43
34
 
44
35
 
45
36
  class CommandResult(BaseModel):
@@ -49,7 +40,11 @@ class CommandResult(BaseModel):
49
40
  list[protocol_events.DeveloperMessageEvent | protocol_events.WelcomeEvent | protocol_events.ReplayHistoryEvent]
50
41
  | None
51
42
  ) = None # List of UI events to display immediately
52
- actions: list[InputAction] | None = None
43
+ operations: list[op.Operation] | None = None
44
+
45
+ # Persistence controls: some slash commands are UI/control actions and should not be written to session history.
46
+ persist_user_input: bool = True
47
+ persist_events: bool = True
53
48
 
54
49
 
55
50
  class CommandABC(ABC):
@@ -77,14 +72,19 @@ class CommandABC(ABC):
77
72
  """Whether this command support additional parameters."""
78
73
  return False
79
74
 
75
+ @property
76
+ def placeholder(self) -> str:
77
+ """Placeholder text for additional parameters in help display."""
78
+ return "additional instructions"
79
+
80
80
  @abstractmethod
81
- async def run(self, raw: str, agent: Agent) -> CommandResult:
81
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
82
82
  """
83
83
  Execute the command.
84
84
 
85
85
  Args:
86
- raw: The full command string as typed by user (e.g., "/help" or "/model gpt-4")
87
- session_id: Current session ID, may be None if no session initialized yet
86
+ agent: The agent instance
87
+ user_input: User input with text containing command arguments (without command name)
88
88
 
89
89
  Returns:
90
90
  CommandResult: Result of the command execution
@@ -0,0 +1,79 @@
1
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
2
+ from klaude_code.protocol import commands, events, model
3
+ from klaude_code.trace import DebugType, get_current_log_file, is_debug_enabled, set_debug_logging
4
+
5
+
6
+ def _format_status() -> str:
7
+ """Format the current debug status for display."""
8
+ if not is_debug_enabled():
9
+ return "Debug: OFF"
10
+
11
+ log_file = get_current_log_file()
12
+ log_path_str = str(log_file) if log_file else "(console)"
13
+ return f"Debug: ON\nLog file: {log_path_str}"
14
+
15
+
16
+ def _parse_debug_filters(raw: str) -> set[DebugType] | None:
17
+ filters: set[DebugType] = set()
18
+ for chunk in raw.split(","):
19
+ normalized = chunk.strip().lower().replace("-", "_")
20
+ if not normalized:
21
+ continue
22
+ try:
23
+ filters.add(DebugType(normalized))
24
+ except ValueError as exc:
25
+ raise ValueError(normalized) from exc
26
+ return filters or None
27
+
28
+
29
+ class DebugCommand(CommandABC):
30
+ """Toggle debug mode and configure debug filters."""
31
+
32
+ @property
33
+ def name(self) -> commands.CommandName:
34
+ return commands.CommandName.DEBUG
35
+
36
+ @property
37
+ def summary(self) -> str:
38
+ return "Toggle debug mode (optional: filter types)"
39
+
40
+ @property
41
+ def support_addition_params(self) -> bool:
42
+ return True
43
+
44
+ @property
45
+ def placeholder(self) -> str:
46
+ return "filter types"
47
+
48
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
49
+ raw = user_input.text.strip()
50
+
51
+ # /debug (no args) - enable debug
52
+ if not raw:
53
+ set_debug_logging(True, write_to_file=True)
54
+ return self._message_result(agent, _format_status())
55
+
56
+ # /debug <filters> - enable with filters
57
+ try:
58
+ filters = _parse_debug_filters(raw)
59
+ if filters:
60
+ set_debug_logging(True, write_to_file=True, filters=filters)
61
+ filter_names = ", ".join(sorted(dt.value for dt in filters))
62
+ return self._message_result(agent, f"Filters: {filter_names}\n{_format_status()}")
63
+ except ValueError:
64
+ pass
65
+
66
+ return self._message_result(agent, f"Invalid filter: {raw}\nValid: {', '.join(dt.value for dt in DebugType)}")
67
+
68
+ def _message_result(self, agent: "Agent", content: str) -> CommandResult:
69
+ return CommandResult(
70
+ events=[
71
+ events.DeveloperMessageEvent(
72
+ session_id=agent.session.id,
73
+ item=model.DeveloperMessageItem(
74
+ content=content,
75
+ command_output=model.CommandOutput(command_name=self.name),
76
+ ),
77
+ )
78
+ ]
79
+ )