klaude-code 1.2.9__py3-none-any.whl → 1.2.11__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 (69) hide show
  1. klaude_code/cli/main.py +11 -5
  2. klaude_code/cli/runtime.py +21 -21
  3. klaude_code/command/__init__.py +68 -23
  4. klaude_code/command/clear_cmd.py +6 -2
  5. klaude_code/command/command_abc.py +5 -2
  6. klaude_code/command/diff_cmd.py +5 -2
  7. klaude_code/command/export_cmd.py +7 -4
  8. klaude_code/command/help_cmd.py +6 -2
  9. klaude_code/command/model_cmd.py +5 -2
  10. klaude_code/command/prompt_command.py +8 -3
  11. klaude_code/command/refresh_cmd.py +6 -2
  12. klaude_code/command/registry.py +17 -5
  13. klaude_code/command/release_notes_cmd.py +5 -2
  14. klaude_code/command/status_cmd.py +8 -4
  15. klaude_code/command/terminal_setup_cmd.py +7 -4
  16. klaude_code/const/__init__.py +1 -1
  17. klaude_code/core/agent.py +62 -9
  18. klaude_code/core/executor.py +1 -4
  19. klaude_code/core/manager/agent_manager.py +19 -14
  20. klaude_code/core/manager/llm_clients.py +47 -22
  21. klaude_code/core/manager/llm_clients_builder.py +22 -13
  22. klaude_code/core/manager/sub_agent_manager.py +1 -1
  23. klaude_code/core/prompt.py +4 -4
  24. klaude_code/core/prompts/prompt-claude-code.md +1 -12
  25. klaude_code/core/prompts/prompt-minimal.md +12 -0
  26. klaude_code/core/reminders.py +0 -3
  27. klaude_code/core/task.py +6 -2
  28. klaude_code/core/tool/file/_utils.py +30 -0
  29. klaude_code/core/tool/file/edit_tool.py +5 -30
  30. klaude_code/core/tool/file/multi_edit_tool.py +6 -31
  31. klaude_code/core/tool/file/read_tool.py +6 -18
  32. klaude_code/core/tool/file/write_tool.py +5 -30
  33. klaude_code/core/tool/memory/__init__.py +5 -0
  34. klaude_code/core/tool/memory/memory_tool.md +4 -0
  35. klaude_code/core/tool/memory/skill_loader.py +3 -2
  36. klaude_code/core/tool/memory/skill_tool.py +13 -0
  37. klaude_code/core/tool/todo/todo_write_tool.md +0 -157
  38. klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
  39. klaude_code/core/tool/tool_registry.py +3 -4
  40. klaude_code/llm/__init__.py +2 -12
  41. klaude_code/llm/anthropic/client.py +2 -1
  42. klaude_code/llm/client.py +2 -2
  43. klaude_code/llm/codex/client.py +1 -1
  44. klaude_code/llm/openai_compatible/client.py +3 -2
  45. klaude_code/llm/openrouter/client.py +3 -3
  46. klaude_code/llm/registry.py +33 -7
  47. klaude_code/llm/responses/client.py +2 -1
  48. klaude_code/llm/responses/input.py +1 -1
  49. klaude_code/llm/usage.py +17 -8
  50. klaude_code/protocol/model.py +15 -7
  51. klaude_code/protocol/op.py +5 -1
  52. klaude_code/protocol/sub_agent.py +1 -0
  53. klaude_code/session/export.py +16 -6
  54. klaude_code/session/session.py +10 -4
  55. klaude_code/session/templates/export_session.html +155 -0
  56. klaude_code/ui/core/input.py +1 -1
  57. klaude_code/ui/modes/repl/clipboard.py +5 -5
  58. klaude_code/ui/modes/repl/event_handler.py +1 -5
  59. klaude_code/ui/modes/repl/input_prompt_toolkit.py +3 -34
  60. klaude_code/ui/renderers/metadata.py +22 -1
  61. klaude_code/ui/renderers/tools.py +13 -2
  62. klaude_code/ui/rich/markdown.py +4 -1
  63. klaude_code/ui/terminal/__init__.py +55 -0
  64. klaude_code/ui/terminal/control.py +2 -2
  65. klaude_code/version.py +3 -3
  66. {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/METADATA +1 -4
  67. {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/RECORD +69 -66
  68. {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/WHEEL +0 -0
  69. {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/entry_points.txt +0 -0
klaude_code/cli/main.py CHANGED
@@ -3,7 +3,7 @@ import datetime
3
3
  import os
4
4
  import subprocess
5
5
  import sys
6
- import uuid
6
+ from importlib.metadata import PackageNotFoundError
7
7
  from importlib.metadata import version as pkg_version
8
8
 
9
9
  import typer
@@ -27,6 +27,9 @@ def _version_callback(value: bool) -> None:
27
27
  if value:
28
28
  try:
29
29
  ver = pkg_version("klaude-code")
30
+ except PackageNotFoundError:
31
+ # Package is not installed or has no metadata; show a generic version string.
32
+ ver = "unknown"
30
33
  except Exception:
31
34
  ver = "unknown"
32
35
  print(f"klaude-code {ver}")
@@ -232,8 +235,12 @@ def exec_command(
232
235
  stdin = sys.stdin.read().rstrip("\n")
233
236
  if stdin:
234
237
  parts.append(stdin)
235
- except Exception as e:
238
+ except (OSError, ValueError) as e:
239
+ # Expected I/O-related errors when reading from stdin (e.g. broken pipe, closed stream).
236
240
  log((f"Error reading from stdin: {e}", "red"))
241
+ except Exception as e:
242
+ # Unexpected errors are still reported but kept from crashing the CLI.
243
+ log((f"Unexpected error reading from stdin: {e}", "red"))
237
244
 
238
245
  if input_content:
239
246
  parts.append(input_content)
@@ -332,6 +339,7 @@ def main_callback(
332
339
  return
333
340
 
334
341
  # Resolve session id before entering asyncio loop
342
+ # session_id=None means create a new session
335
343
  session_id: str | None = None
336
344
  if resume:
337
345
  session_id = resume_select_session()
@@ -340,9 +348,7 @@ def main_callback(
340
348
  # If user didn't pick, allow fallback to --continue
341
349
  if session_id is None and continue_:
342
350
  session_id = Session.most_recent_session_id()
343
- # If still no session_id, generate a new one for a new session
344
- if session_id is None:
345
- session_id = uuid.uuid4().hex
351
+ # If still no session_id, leave as None to create a new session
346
352
 
347
353
  debug_enabled, debug_filters = resolve_debug_settings(debug, debug_filter)
348
354
 
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import sys
3
- import uuid
4
3
  from dataclasses import dataclass
5
4
  from typing import Any, Protocol
6
5
 
@@ -13,10 +12,8 @@ from klaude_code.config import Config, load_config
13
12
  from klaude_code.core.agent import Agent, DefaultModelProfileProvider, VanillaModelProfileProvider
14
13
  from klaude_code.core.executor import Executor
15
14
  from klaude_code.core.manager import build_llm_clients
16
- from klaude_code.core.tool import SkillLoader, SkillTool
17
15
  from klaude_code.protocol import events, op
18
16
  from klaude_code.protocol.model import UserInputPayload
19
- from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
20
17
  from klaude_code.trace import DebugType, log, set_debug_logging
21
18
  from klaude_code.ui.modes.repl import build_repl_status_snapshot
22
19
  from klaude_code.ui.modes.repl.input_prompt_toolkit import REPLStatusSnapshot
@@ -96,18 +93,11 @@ async def initialize_app_components(init_config: AppInitConfig) -> AppComponents
96
93
  if config is None:
97
94
  raise typer.Exit(1)
98
95
 
99
- # Initialize skills
100
- skill_loader = SkillLoader()
101
- skill_loader.discover_skills()
102
- SkillTool.set_skill_loader(skill_loader)
103
-
104
96
  # Initialize LLM clients
105
97
  try:
106
- enabled_sub_agents = [p.name for p in iter_sub_agent_profiles()]
107
98
  llm_clients = build_llm_clients(
108
99
  config,
109
100
  model_override=init_config.model,
110
- enabled_sub_agents=enabled_sub_agents,
111
101
  )
112
102
  except ValueError as exc:
113
103
  if init_config.model:
@@ -213,13 +203,14 @@ async def run_exec(init_config: AppInitConfig, input_content: str) -> None:
213
203
  components = await initialize_app_components(init_config)
214
204
 
215
205
  try:
216
- # Generate a new session ID for exec mode
217
- session_id = uuid.uuid4().hex
218
-
219
- # Init Agent
220
- await components.executor.submit_and_wait(op.InitAgentOperation(session_id=session_id))
206
+ # Initialize a new session (session_id=None means create new)
207
+ await components.executor.submit_and_wait(op.InitAgentOperation())
221
208
  await components.event_queue.join()
222
209
 
210
+ # Get the session_id from the newly created agent
211
+ session_ids = components.executor.context.agent_manager.active_session_ids()
212
+ session_id = session_ids[0] if session_ids else None
213
+
223
214
  # Submit the input content directly
224
215
  await components.executor.submit_and_wait(
225
216
  op.UserInputOperation(input=UserInputPayload(text=input_content), session_id=session_id)
@@ -232,8 +223,11 @@ async def run_exec(init_config: AppInitConfig, input_content: str) -> None:
232
223
 
233
224
 
234
225
  async def run_interactive(init_config: AppInitConfig, session_id: str | None = None) -> None:
235
- """Run the interactive REPL using the provided configuration."""
226
+ """Run the interactive REPL using the provided configuration.
236
227
 
228
+ If session_id is None, a new session is created with an auto-generated ID.
229
+ If session_id is provided, attempts to resume that session.
230
+ """
237
231
  components = await initialize_app_components(init_config)
238
232
 
239
233
  # No theme persistence from CLI anymore; config.theme controls theme when set.
@@ -241,8 +235,10 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
241
235
  # Create status provider for bottom toolbar
242
236
  def _status_provider() -> REPLStatusSnapshot:
243
237
  agent: Agent | None = None
244
- if session_id and session_id in components.executor.context.active_agents:
245
- agent = components.executor.context.active_agents[session_id]
238
+ # Get the first active agent (there should only be one in interactive mode)
239
+ active_agents = components.executor.context.active_agents
240
+ if active_agents:
241
+ agent = next(iter(active_agents.values()), None)
246
242
 
247
243
  # Check for updates (returns None if uv not available)
248
244
  update_message = get_update_message()
@@ -284,9 +280,13 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
284
280
  restore_sigint = install_sigint_double_press_exit(_show_toast_once, _hide_progress)
285
281
 
286
282
  try:
287
- # Init Agent
288
283
  await components.executor.submit_and_wait(op.InitAgentOperation(session_id=session_id))
289
284
  await components.event_queue.join()
285
+
286
+ # Get the actual session_id (may have been auto-generated if None was passed)
287
+ active_session_ids = components.executor.context.agent_manager.active_session_ids()
288
+ active_session_id = active_session_ids[0] if active_session_ids else session_id
289
+
290
290
  # Input
291
291
  await input_provider.start()
292
292
  async for user_input in input_provider.iter_inputs():
@@ -297,7 +297,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
297
297
  continue
298
298
  # Submit user input operation - directly use the payload from iter_inputs
299
299
  submission_id = await components.executor.submit(
300
- op.UserInputOperation(input=user_input, session_id=session_id)
300
+ op.UserInputOperation(input=user_input, session_id=active_session_id)
301
301
  )
302
302
  # If it's an interactive command (e.g., /model), avoid starting the ESC monitor
303
303
  # to prevent TTY conflicts with interactive prompts (questionary/prompt_toolkit).
@@ -306,7 +306,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
306
306
  else:
307
307
  # Esc monitor for long-running, interruptible operations
308
308
  async def _on_esc_interrupt() -> None:
309
- await components.executor.submit(op.InterruptOperation(target_session_id=session_id))
309
+ await components.executor.submit(op.InterruptOperation(target_session_id=active_session_id))
310
310
 
311
311
  stop_event, esc_task = start_esc_interrupt_monitor(_on_esc_interrupt)
312
312
  # Wait for this specific task to complete before accepting next input
@@ -1,13 +1,4 @@
1
- from .clear_cmd import ClearCommand
2
1
  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
11
2
  from .registry import (
12
3
  dispatch_command,
13
4
  get_commands,
@@ -16,23 +7,76 @@ from .registry import (
16
7
  load_prompt_commands,
17
8
  register_command,
18
9
  )
19
- from .release_notes_cmd import ReleaseNotesCommand
20
- from .status_cmd import StatusCommand
21
- from .terminal_setup_cmd import TerminalSetupCommand
22
10
 
23
- # Dynamically load prompt commands
24
- load_prompt_commands()
11
+ # Lazy load commands to avoid heavy imports at module load time
12
+ _commands_loaded = False
13
+
14
+
15
+ def ensure_commands_loaded() -> None:
16
+ """Ensure all commands are loaded (lazy initialization).
17
+
18
+ This function is called internally by registry functions like get_commands(),
19
+ dispatch_command(), etc. It can also be called explicitly if early loading is desired.
20
+ """
21
+ global _commands_loaded
22
+ if _commands_loaded:
23
+ return
24
+ _commands_loaded = True
25
+
26
+ # Import command modules to trigger @register_command decorators
27
+ from . import clear_cmd as _clear_cmd # noqa: F401
28
+ from . import diff_cmd as _diff_cmd # noqa: F401
29
+ from . import export_cmd as _export_cmd # noqa: F401
30
+ from . import help_cmd as _help_cmd # noqa: F401
31
+ from . import model_cmd as _model_cmd # noqa: F401
32
+ from . import refresh_cmd as _refresh_cmd # noqa: F401
33
+ from . import release_notes_cmd as _release_notes_cmd # noqa: F401
34
+ from . import status_cmd as _status_cmd # noqa: F401
35
+ from . import terminal_setup_cmd as _terminal_setup_cmd # noqa: F401
36
+
37
+ # Suppress unused variable warnings
38
+ _ = (
39
+ _clear_cmd,
40
+ _diff_cmd,
41
+ _export_cmd,
42
+ _help_cmd,
43
+ _model_cmd,
44
+ _refresh_cmd,
45
+ _release_notes_cmd,
46
+ _status_cmd,
47
+ _terminal_setup_cmd,
48
+ )
49
+
50
+ # Load prompt-based commands
51
+ load_prompt_commands()
52
+
53
+
54
+ # Lazy accessors for command classes
55
+ def __getattr__(name: str) -> object:
56
+ _commands_map = {
57
+ "ClearCommand": "clear_cmd",
58
+ "DiffCommand": "diff_cmd",
59
+ "ExportCommand": "export_cmd",
60
+ "HelpCommand": "help_cmd",
61
+ "ModelCommand": "model_cmd",
62
+ "RefreshTerminalCommand": "refresh_cmd",
63
+ "ReleaseNotesCommand": "release_notes_cmd",
64
+ "StatusCommand": "status_cmd",
65
+ "TerminalSetupCommand": "terminal_setup_cmd",
66
+ }
67
+ if name in _commands_map:
68
+ import importlib
69
+
70
+ module = importlib.import_module(f".{_commands_map[name]}", __package__)
71
+ return getattr(module, name)
72
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
73
+
25
74
 
26
75
  __all__ = [
27
- "ClearCommand",
28
- "DiffCommand",
29
- "HelpCommand",
30
- "ModelCommand",
31
- "ExportCommand",
32
- "RefreshTerminalCommand",
33
- "ReleaseNotesCommand",
34
- "StatusCommand",
35
- "TerminalSetupCommand",
76
+ # Command classes are lazily loaded via __getattr__
77
+ # "ClearCommand", "DiffCommand", "HelpCommand", "ModelCommand",
78
+ # "ExportCommand", "RefreshTerminalCommand", "ReleaseNotesCommand",
79
+ # "StatusCommand", "TerminalSetupCommand",
36
80
  "register_command",
37
81
  "CommandABC",
38
82
  "CommandResult",
@@ -42,4 +86,5 @@ __all__ = [
42
86
  "get_commands",
43
87
  "is_slash_command_name",
44
88
  "has_interactive_command",
89
+ "ensure_commands_loaded",
45
90
  ]
@@ -1,8 +1,12 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
2
4
  from klaude_code.command.registry import register_command
3
- from klaude_code.core.agent import Agent
4
5
  from klaude_code.protocol import commands
5
6
 
7
+ if TYPE_CHECKING:
8
+ from klaude_code.core.agent import Agent
9
+
6
10
 
7
11
  @register_command
8
12
  class ClearCommand(CommandABC):
@@ -16,5 +20,5 @@ class ClearCommand(CommandABC):
16
20
  def summary(self) -> str:
17
21
  return "Clear conversation history and free up context"
18
22
 
19
- async def run(self, raw: str, agent: Agent) -> CommandResult:
23
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
20
24
  return CommandResult(actions=[InputAction.clear()])
@@ -1,12 +1,15 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from enum import Enum
3
+ from typing import TYPE_CHECKING
3
4
 
4
5
  from pydantic import BaseModel
5
6
 
6
- from klaude_code.core.agent import Agent
7
7
  from klaude_code.protocol import commands
8
8
  from klaude_code.protocol import events as protocol_events
9
9
 
10
+ if TYPE_CHECKING:
11
+ from klaude_code.core.agent import Agent
12
+
10
13
 
11
14
  class InputActionType(str, Enum):
12
15
  """Supported input action kinds."""
@@ -78,7 +81,7 @@ class CommandABC(ABC):
78
81
  return False
79
82
 
80
83
  @abstractmethod
81
- async def run(self, raw: str, agent: Agent) -> CommandResult:
84
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
82
85
  """
83
86
  Execute the command.
84
87
 
@@ -1,11 +1,14 @@
1
1
  import subprocess
2
2
  from pathlib import Path
3
+ from typing import TYPE_CHECKING
3
4
 
4
5
  from klaude_code.command.command_abc import CommandABC, CommandResult
5
6
  from klaude_code.command.registry import register_command
6
- from klaude_code.core.agent import Agent
7
7
  from klaude_code.protocol import commands, events, model
8
8
 
9
+ if TYPE_CHECKING:
10
+ from klaude_code.core.agent import Agent
11
+
9
12
 
10
13
  @register_command
11
14
  class DiffCommand(CommandABC):
@@ -19,7 +22,7 @@ class DiffCommand(CommandABC):
19
22
  def summary(self) -> str:
20
23
  return "Show git diff"
21
24
 
22
- async def run(self, raw: str, agent: Agent) -> CommandResult:
25
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
23
26
  try:
24
27
  # Check if current directory is in a git repository
25
28
  git_check = subprocess.run(
@@ -2,13 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import subprocess
4
4
  from pathlib import Path
5
+ from typing import TYPE_CHECKING
5
6
 
6
7
  from klaude_code.command.command_abc import CommandABC, CommandResult
7
8
  from klaude_code.command.registry import register_command
8
- from klaude_code.core.agent import Agent
9
9
  from klaude_code.protocol import commands, events, model
10
10
  from klaude_code.session.export import build_export_html, get_default_export_path
11
11
 
12
+ if TYPE_CHECKING:
13
+ from klaude_code.core.agent import Agent
14
+
12
15
 
13
16
  @register_command
14
17
  class ExportCommand(CommandABC):
@@ -30,7 +33,7 @@ class ExportCommand(CommandABC):
30
33
  def is_interactive(self) -> bool:
31
34
  return False
32
35
 
33
- async def run(self, raw: str, agent: Agent) -> CommandResult:
36
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
34
37
  try:
35
38
  output_path = self._resolve_output_path(raw, agent)
36
39
  html_doc = self._build_html(agent)
@@ -57,7 +60,7 @@ class ExportCommand(CommandABC):
57
60
  )
58
61
  return CommandResult(events=[event])
59
62
 
60
- def _resolve_output_path(self, raw: str, agent: Agent) -> Path:
63
+ def _resolve_output_path(self, raw: str, agent: "Agent") -> Path:
61
64
  trimmed = raw.strip()
62
65
  if trimmed:
63
66
  candidate = Path(trimmed).expanduser()
@@ -78,7 +81,7 @@ class ExportCommand(CommandABC):
78
81
  msg = f"Failed to open HTML with `open`: {exc}"
79
82
  raise RuntimeError(msg) from exc
80
83
 
81
- def _build_html(self, agent: Agent) -> str:
84
+ def _build_html(self, agent: "Agent") -> str:
82
85
  profile = agent.profile
83
86
  system_prompt = (profile.system_prompt if profile else "") or ""
84
87
  tools = profile.tools if profile else []
@@ -1,8 +1,12 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from klaude_code.command.command_abc import CommandABC, CommandResult
2
4
  from klaude_code.command.registry import register_command
3
- from klaude_code.core.agent import Agent
4
5
  from klaude_code.protocol import commands, events, model
5
6
 
7
+ if TYPE_CHECKING:
8
+ from klaude_code.core.agent import Agent
9
+
6
10
 
7
11
  @register_command
8
12
  class HelpCommand(CommandABC):
@@ -16,7 +20,7 @@ class HelpCommand(CommandABC):
16
20
  def summary(self) -> str:
17
21
  return "Show help and available commands"
18
22
 
19
- async def run(self, raw: str, agent: Agent) -> CommandResult:
23
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
20
24
  lines: list[str] = [
21
25
  """
22
26
  Usage:
@@ -1,11 +1,14 @@
1
1
  import asyncio
2
+ from typing import TYPE_CHECKING
2
3
 
3
4
  from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
4
5
  from klaude_code.command.registry import register_command
5
6
  from klaude_code.config import select_model_from_config
6
- from klaude_code.core.agent import Agent
7
7
  from klaude_code.protocol import commands, events, model
8
8
 
9
+ if TYPE_CHECKING:
10
+ from klaude_code.core.agent import Agent
11
+
9
12
 
10
13
  @register_command
11
14
  class ModelCommand(CommandABC):
@@ -23,7 +26,7 @@ class ModelCommand(CommandABC):
23
26
  def is_interactive(self) -> bool:
24
27
  return True
25
28
 
26
- async def run(self, raw: str, agent: Agent) -> CommandResult:
29
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
27
30
  selected_model = await asyncio.to_thread(select_model_from_config, preferred=raw)
28
31
 
29
32
  current_model = agent.profile.llm_client.model_name if agent.profile else None
@@ -1,10 +1,14 @@
1
1
  from importlib.resources import files
2
+ from typing import TYPE_CHECKING
2
3
 
3
4
  import yaml
4
5
 
5
6
  from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
6
- from klaude_code.core.agent import Agent
7
7
  from klaude_code.protocol import commands
8
+ from klaude_code.trace import log_debug
9
+
10
+ if TYPE_CHECKING:
11
+ from klaude_code.core.agent import Agent
8
12
 
9
13
 
10
14
  class PromptCommand(CommandABC):
@@ -41,7 +45,8 @@ class PromptCommand(CommandABC):
41
45
 
42
46
  self._metadata = {}
43
47
  self._content = raw_text
44
- except Exception:
48
+ except (OSError, yaml.YAMLError) as e:
49
+ log_debug(f"Failed to load prompt template {self.template_name}: {e}")
45
50
  self._metadata = {"description": "Error loading template"}
46
51
  self._content = f"Error loading template: {self.template_name}"
47
52
 
@@ -54,7 +59,7 @@ class PromptCommand(CommandABC):
54
59
  def support_addition_params(self) -> bool:
55
60
  return True
56
61
 
57
- async def run(self, raw: str, agent: Agent) -> CommandResult:
62
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
58
63
  self._ensure_loaded()
59
64
  template_content = self._content or ""
60
65
  user_input = raw.strip() or "<none>"
@@ -1,8 +1,12 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from klaude_code.command.command_abc import CommandABC, CommandResult
2
4
  from klaude_code.command.registry import register_command
3
- from klaude_code.core.agent import Agent
4
5
  from klaude_code.protocol import commands, events
5
6
 
7
+ if TYPE_CHECKING:
8
+ from klaude_code.core.agent import Agent
9
+
6
10
 
7
11
  @register_command
8
12
  class RefreshTerminalCommand(CommandABC):
@@ -20,7 +24,7 @@ class RefreshTerminalCommand(CommandABC):
20
24
  def is_interactive(self) -> bool:
21
25
  return True
22
26
 
23
- async def run(self, raw: str, agent: Agent) -> CommandResult:
27
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
24
28
  import os
25
29
 
26
30
  os.system("cls" if os.name == "nt" else "clear")
@@ -3,10 +3,12 @@ from typing import TYPE_CHECKING, TypeVar
3
3
 
4
4
  from klaude_code.command.command_abc import CommandResult, InputAction
5
5
  from klaude_code.command.prompt_command import PromptCommand
6
- from klaude_code.core.agent import Agent
7
6
  from klaude_code.protocol import commands, events, model
7
+ from klaude_code.trace import log_debug
8
8
 
9
9
  if TYPE_CHECKING:
10
+ from klaude_code.core.agent import Agent
11
+
10
12
  from .command_abc import CommandABC
11
13
 
12
14
  _COMMANDS: dict[commands.CommandName | str, "CommandABC"] = {}
@@ -30,21 +32,30 @@ def load_prompt_commands():
30
32
  if (name.startswith("prompt_") or name.startswith("prompt-")) and name.endswith(".md"):
31
33
  cmd = PromptCommand(name)
32
34
  _COMMANDS[cmd.name] = cmd
33
- except Exception:
34
- # If resource loading fails, just ignore
35
- pass
35
+ except OSError as e:
36
+ log_debug(f"Failed to load prompt commands: {e}")
37
+
38
+
39
+ def _ensure_commands_loaded() -> None:
40
+ """Ensure all commands are loaded (lazy initialization)."""
41
+ from klaude_code.command import ensure_commands_loaded
42
+
43
+ ensure_commands_loaded()
36
44
 
37
45
 
38
46
  def get_commands() -> dict[commands.CommandName | str, "CommandABC"]:
39
47
  """Get all registered commands."""
48
+ _ensure_commands_loaded()
40
49
  return _COMMANDS.copy()
41
50
 
42
51
 
43
52
  def is_slash_command_name(name: str) -> bool:
53
+ _ensure_commands_loaded()
44
54
  return name in _COMMANDS
45
55
 
46
56
 
47
- async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
57
+ async def dispatch_command(raw: str, agent: "Agent") -> CommandResult:
58
+ _ensure_commands_loaded()
48
59
  # Detect command name
49
60
  if not raw.startswith("/"):
50
61
  return CommandResult(actions=[InputAction.run_agent(raw)])
@@ -96,6 +107,7 @@ async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
96
107
 
97
108
 
98
109
  def has_interactive_command(raw: str) -> bool:
110
+ _ensure_commands_loaded()
99
111
  if not raw.startswith("/"):
100
112
  return False
101
113
  splits = raw.split(" ", maxsplit=1)
@@ -1,10 +1,13 @@
1
1
  from pathlib import Path
2
+ from typing import TYPE_CHECKING
2
3
 
3
4
  from klaude_code.command.command_abc import CommandABC, CommandResult
4
5
  from klaude_code.command.registry import register_command
5
- from klaude_code.core.agent import Agent
6
6
  from klaude_code.protocol import commands, events, model
7
7
 
8
+ if TYPE_CHECKING:
9
+ from klaude_code.core.agent import Agent
10
+
8
11
 
9
12
  def _read_changelog() -> str:
10
13
  """Read CHANGELOG.md from project root."""
@@ -71,7 +74,7 @@ class ReleaseNotesCommand(CommandABC):
71
74
  def summary(self) -> str:
72
75
  return "Show the latest release notes"
73
76
 
74
- async def run(self, raw: str, agent: Agent) -> CommandResult:
77
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
75
78
  changelog = _read_changelog()
76
79
  content = _extract_releases(changelog, count=10)
77
80
 
@@ -1,9 +1,13 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from klaude_code.command.command_abc import CommandABC, CommandResult
2
4
  from klaude_code.command.registry import register_command
3
- from klaude_code.core.agent import Agent
4
5
  from klaude_code.protocol import commands, events, model
5
6
  from klaude_code.session.session import Session
6
7
 
8
+ if TYPE_CHECKING:
9
+ from klaude_code.core.agent import Agent
10
+
7
11
 
8
12
  class AggregatedUsage(model.BaseModel):
9
13
  """Aggregated usage statistics including per-model breakdown."""
@@ -56,8 +60,8 @@ def accumulate_session_usage(session: Session) -> AggregatedUsage:
56
60
  total.cache_read_cost = (total.cache_read_cost or 0.0) + usage.cache_read_cost
57
61
 
58
62
  # Track peak context window size (max across all tasks)
59
- if usage.context_window_size is not None:
60
- total.context_window_size = usage.context_window_size
63
+ if usage.context_token is not None:
64
+ total.context_token = usage.context_token
61
65
 
62
66
  # Keep the latest context_limit for computed context_usage_percent
63
67
  if usage.context_limit is not None:
@@ -135,7 +139,7 @@ class StatusCommand(CommandABC):
135
139
  def summary(self) -> str:
136
140
  return "Show session usage statistics"
137
141
 
138
- async def run(self, raw: str, agent: Agent) -> CommandResult:
142
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
139
143
  session = agent.session
140
144
  aggregated = accumulate_session_usage(session)
141
145
 
@@ -1,12 +1,15 @@
1
1
  import os
2
2
  import subprocess
3
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  from klaude_code.command.command_abc import CommandABC, CommandResult
6
7
  from klaude_code.command.registry import register_command
7
- from klaude_code.core.agent import Agent
8
8
  from klaude_code.protocol import commands, events, model
9
9
 
10
+ if TYPE_CHECKING:
11
+ from klaude_code.core.agent import Agent
12
+
10
13
 
11
14
  @register_command
12
15
  class TerminalSetupCommand(CommandABC):
@@ -24,7 +27,7 @@ class TerminalSetupCommand(CommandABC):
24
27
  def is_interactive(self) -> bool:
25
28
  return False
26
29
 
27
- async def run(self, raw: str, agent: Agent) -> CommandResult:
30
+ async def run(self, raw: str, agent: "Agent") -> CommandResult:
28
31
  term_program = os.environ.get("TERM_PROGRAM", "").lower()
29
32
 
30
33
  try:
@@ -223,7 +226,7 @@ class TerminalSetupCommand(CommandABC):
223
226
 
224
227
  return message
225
228
 
226
- def _create_success_result(self, agent: Agent, message: str) -> CommandResult:
229
+ def _create_success_result(self, agent: "Agent", message: str) -> CommandResult:
227
230
  """Create success result"""
228
231
  return CommandResult(
229
232
  events=[
@@ -237,7 +240,7 @@ class TerminalSetupCommand(CommandABC):
237
240
  ]
238
241
  )
239
242
 
240
- def _create_error_result(self, agent: Agent, message: str) -> CommandResult:
243
+ def _create_error_result(self, agent: "Agent", message: str) -> CommandResult:
241
244
  """Create error result"""
242
245
  return CommandResult(
243
246
  events=[
@@ -91,7 +91,7 @@ INVALID_TOOL_CALL_MAX_LENGTH = 500
91
91
  TRUNCATE_DISPLAY_MAX_LINE_LENGTH = 1000
92
92
 
93
93
  # Maximum lines for truncated display output
94
- TRUNCATE_DISPLAY_MAX_LINES = 10
94
+ TRUNCATE_DISPLAY_MAX_LINES = 20
95
95
 
96
96
  # Maximum lines for sub-agent result display
97
97
  SUB_AGENT_RESULT_MAX_LINES = 12