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.
- klaude_code/cli/main.py +11 -5
- klaude_code/cli/runtime.py +21 -21
- klaude_code/command/__init__.py +68 -23
- klaude_code/command/clear_cmd.py +6 -2
- klaude_code/command/command_abc.py +5 -2
- klaude_code/command/diff_cmd.py +5 -2
- klaude_code/command/export_cmd.py +7 -4
- klaude_code/command/help_cmd.py +6 -2
- klaude_code/command/model_cmd.py +5 -2
- klaude_code/command/prompt_command.py +8 -3
- klaude_code/command/refresh_cmd.py +6 -2
- klaude_code/command/registry.py +17 -5
- klaude_code/command/release_notes_cmd.py +5 -2
- klaude_code/command/status_cmd.py +8 -4
- klaude_code/command/terminal_setup_cmd.py +7 -4
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/agent.py +62 -9
- klaude_code/core/executor.py +1 -4
- klaude_code/core/manager/agent_manager.py +19 -14
- klaude_code/core/manager/llm_clients.py +47 -22
- klaude_code/core/manager/llm_clients_builder.py +22 -13
- klaude_code/core/manager/sub_agent_manager.py +1 -1
- klaude_code/core/prompt.py +4 -4
- klaude_code/core/prompts/prompt-claude-code.md +1 -12
- klaude_code/core/prompts/prompt-minimal.md +12 -0
- klaude_code/core/reminders.py +0 -3
- klaude_code/core/task.py +6 -2
- klaude_code/core/tool/file/_utils.py +30 -0
- klaude_code/core/tool/file/edit_tool.py +5 -30
- klaude_code/core/tool/file/multi_edit_tool.py +6 -31
- klaude_code/core/tool/file/read_tool.py +6 -18
- klaude_code/core/tool/file/write_tool.py +5 -30
- klaude_code/core/tool/memory/__init__.py +5 -0
- klaude_code/core/tool/memory/memory_tool.md +4 -0
- klaude_code/core/tool/memory/skill_loader.py +3 -2
- klaude_code/core/tool/memory/skill_tool.py +13 -0
- klaude_code/core/tool/todo/todo_write_tool.md +0 -157
- klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
- klaude_code/core/tool/tool_registry.py +3 -4
- klaude_code/llm/__init__.py +2 -12
- klaude_code/llm/anthropic/client.py +2 -1
- klaude_code/llm/client.py +2 -2
- klaude_code/llm/codex/client.py +1 -1
- klaude_code/llm/openai_compatible/client.py +3 -2
- klaude_code/llm/openrouter/client.py +3 -3
- klaude_code/llm/registry.py +33 -7
- klaude_code/llm/responses/client.py +2 -1
- klaude_code/llm/responses/input.py +1 -1
- klaude_code/llm/usage.py +17 -8
- klaude_code/protocol/model.py +15 -7
- klaude_code/protocol/op.py +5 -1
- klaude_code/protocol/sub_agent.py +1 -0
- klaude_code/session/export.py +16 -6
- klaude_code/session/session.py +10 -4
- klaude_code/session/templates/export_session.html +155 -0
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/modes/repl/clipboard.py +5 -5
- klaude_code/ui/modes/repl/event_handler.py +1 -5
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +3 -34
- klaude_code/ui/renderers/metadata.py +22 -1
- klaude_code/ui/renderers/tools.py +13 -2
- klaude_code/ui/rich/markdown.py +4 -1
- klaude_code/ui/terminal/__init__.py +55 -0
- klaude_code/ui/terminal/control.py +2 -2
- klaude_code/version.py +3 -3
- {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/METADATA +1 -4
- {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/RECORD +69 -66
- {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
|
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,
|
|
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
|
|
klaude_code/cli/runtime.py
CHANGED
|
@@ -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
|
-
#
|
|
217
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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=
|
|
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=
|
|
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
|
klaude_code/command/__init__.py
CHANGED
|
@@ -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
|
-
#
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
"DiffCommand",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
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
|
]
|
klaude_code/command/clear_cmd.py
CHANGED
|
@@ -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
|
|
klaude_code/command/diff_cmd.py
CHANGED
|
@@ -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 []
|
klaude_code/command/help_cmd.py
CHANGED
|
@@ -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:
|
klaude_code/command/model_cmd.py
CHANGED
|
@@ -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
|
|
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")
|
klaude_code/command/registry.py
CHANGED
|
@@ -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
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
|
60
|
-
total.
|
|
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=[
|
klaude_code/const/__init__.py
CHANGED
|
@@ -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 =
|
|
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
|