klaude-code 1.2.6__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/__init__.py +0 -0
- klaude_code/cli/__init__.py +1 -0
- klaude_code/cli/main.py +298 -0
- klaude_code/cli/runtime.py +331 -0
- klaude_code/cli/session_cmd.py +80 -0
- klaude_code/command/__init__.py +43 -0
- klaude_code/command/clear_cmd.py +20 -0
- klaude_code/command/command_abc.py +92 -0
- klaude_code/command/diff_cmd.py +138 -0
- klaude_code/command/export_cmd.py +86 -0
- klaude_code/command/help_cmd.py +51 -0
- klaude_code/command/model_cmd.py +43 -0
- klaude_code/command/prompt-dev-docs-update.md +56 -0
- klaude_code/command/prompt-dev-docs.md +46 -0
- klaude_code/command/prompt-init.md +45 -0
- klaude_code/command/prompt_command.py +69 -0
- klaude_code/command/refresh_cmd.py +43 -0
- klaude_code/command/registry.py +110 -0
- klaude_code/command/status_cmd.py +111 -0
- klaude_code/command/terminal_setup_cmd.py +252 -0
- klaude_code/config/__init__.py +11 -0
- klaude_code/config/config.py +177 -0
- klaude_code/config/list_model.py +162 -0
- klaude_code/config/select_model.py +67 -0
- klaude_code/const/__init__.py +133 -0
- klaude_code/core/__init__.py +0 -0
- klaude_code/core/agent.py +165 -0
- klaude_code/core/executor.py +485 -0
- klaude_code/core/manager/__init__.py +19 -0
- klaude_code/core/manager/agent_manager.py +127 -0
- klaude_code/core/manager/llm_clients.py +42 -0
- klaude_code/core/manager/llm_clients_builder.py +49 -0
- klaude_code/core/manager/sub_agent_manager.py +86 -0
- klaude_code/core/prompt.py +89 -0
- klaude_code/core/prompts/prompt-claude-code.md +98 -0
- klaude_code/core/prompts/prompt-codex.md +331 -0
- klaude_code/core/prompts/prompt-gemini.md +43 -0
- klaude_code/core/prompts/prompt-subagent-explore.md +27 -0
- klaude_code/core/prompts/prompt-subagent-oracle.md +23 -0
- klaude_code/core/prompts/prompt-subagent-webfetch.md +46 -0
- klaude_code/core/prompts/prompt-subagent.md +8 -0
- klaude_code/core/reminders.py +445 -0
- klaude_code/core/task.py +237 -0
- klaude_code/core/tool/__init__.py +75 -0
- klaude_code/core/tool/file/__init__.py +0 -0
- klaude_code/core/tool/file/apply_patch.py +492 -0
- klaude_code/core/tool/file/apply_patch_tool.md +1 -0
- klaude_code/core/tool/file/apply_patch_tool.py +204 -0
- klaude_code/core/tool/file/edit_tool.md +9 -0
- klaude_code/core/tool/file/edit_tool.py +274 -0
- klaude_code/core/tool/file/multi_edit_tool.md +42 -0
- klaude_code/core/tool/file/multi_edit_tool.py +199 -0
- klaude_code/core/tool/file/read_tool.md +14 -0
- klaude_code/core/tool/file/read_tool.py +326 -0
- klaude_code/core/tool/file/write_tool.md +8 -0
- klaude_code/core/tool/file/write_tool.py +146 -0
- klaude_code/core/tool/memory/__init__.py +0 -0
- klaude_code/core/tool/memory/memory_tool.md +16 -0
- klaude_code/core/tool/memory/memory_tool.py +462 -0
- klaude_code/core/tool/memory/skill_loader.py +245 -0
- klaude_code/core/tool/memory/skill_tool.md +24 -0
- klaude_code/core/tool/memory/skill_tool.py +97 -0
- klaude_code/core/tool/shell/__init__.py +0 -0
- klaude_code/core/tool/shell/bash_tool.md +43 -0
- klaude_code/core/tool/shell/bash_tool.py +123 -0
- klaude_code/core/tool/shell/command_safety.py +363 -0
- klaude_code/core/tool/sub_agent_tool.py +83 -0
- klaude_code/core/tool/todo/__init__.py +0 -0
- klaude_code/core/tool/todo/todo_write_tool.md +182 -0
- klaude_code/core/tool/todo/todo_write_tool.py +121 -0
- klaude_code/core/tool/todo/update_plan_tool.md +3 -0
- klaude_code/core/tool/todo/update_plan_tool.py +104 -0
- klaude_code/core/tool/tool_abc.py +25 -0
- klaude_code/core/tool/tool_context.py +106 -0
- klaude_code/core/tool/tool_registry.py +78 -0
- klaude_code/core/tool/tool_runner.py +252 -0
- klaude_code/core/tool/truncation.py +170 -0
- klaude_code/core/tool/web/__init__.py +0 -0
- klaude_code/core/tool/web/mermaid_tool.md +21 -0
- klaude_code/core/tool/web/mermaid_tool.py +76 -0
- klaude_code/core/tool/web/web_fetch_tool.md +8 -0
- klaude_code/core/tool/web/web_fetch_tool.py +159 -0
- klaude_code/core/turn.py +220 -0
- klaude_code/llm/__init__.py +21 -0
- klaude_code/llm/anthropic/__init__.py +3 -0
- klaude_code/llm/anthropic/client.py +221 -0
- klaude_code/llm/anthropic/input.py +200 -0
- klaude_code/llm/client.py +49 -0
- klaude_code/llm/input_common.py +239 -0
- klaude_code/llm/openai_compatible/__init__.py +3 -0
- klaude_code/llm/openai_compatible/client.py +211 -0
- klaude_code/llm/openai_compatible/input.py +109 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +80 -0
- klaude_code/llm/openrouter/__init__.py +3 -0
- klaude_code/llm/openrouter/client.py +200 -0
- klaude_code/llm/openrouter/input.py +160 -0
- klaude_code/llm/openrouter/reasoning_handler.py +209 -0
- klaude_code/llm/registry.py +22 -0
- klaude_code/llm/responses/__init__.py +3 -0
- klaude_code/llm/responses/client.py +216 -0
- klaude_code/llm/responses/input.py +167 -0
- klaude_code/llm/usage.py +109 -0
- klaude_code/protocol/__init__.py +4 -0
- klaude_code/protocol/commands.py +21 -0
- klaude_code/protocol/events.py +163 -0
- klaude_code/protocol/llm_param.py +147 -0
- klaude_code/protocol/model.py +287 -0
- klaude_code/protocol/op.py +89 -0
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/protocol/sub_agent.py +348 -0
- klaude_code/protocol/tools.py +15 -0
- klaude_code/session/__init__.py +4 -0
- klaude_code/session/export.py +624 -0
- klaude_code/session/selector.py +76 -0
- klaude_code/session/session.py +474 -0
- klaude_code/session/templates/export_session.html +1434 -0
- klaude_code/trace/__init__.py +3 -0
- klaude_code/trace/log.py +168 -0
- klaude_code/ui/__init__.py +91 -0
- klaude_code/ui/core/__init__.py +1 -0
- klaude_code/ui/core/display.py +103 -0
- klaude_code/ui/core/input.py +71 -0
- klaude_code/ui/core/stage_manager.py +55 -0
- klaude_code/ui/modes/__init__.py +1 -0
- klaude_code/ui/modes/debug/__init__.py +1 -0
- klaude_code/ui/modes/debug/display.py +36 -0
- klaude_code/ui/modes/exec/__init__.py +1 -0
- klaude_code/ui/modes/exec/display.py +63 -0
- klaude_code/ui/modes/repl/__init__.py +51 -0
- klaude_code/ui/modes/repl/clipboard.py +152 -0
- klaude_code/ui/modes/repl/completers.py +429 -0
- klaude_code/ui/modes/repl/display.py +60 -0
- klaude_code/ui/modes/repl/event_handler.py +375 -0
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
- klaude_code/ui/modes/repl/key_bindings.py +170 -0
- klaude_code/ui/modes/repl/renderer.py +281 -0
- klaude_code/ui/renderers/__init__.py +0 -0
- klaude_code/ui/renderers/assistant.py +21 -0
- klaude_code/ui/renderers/common.py +8 -0
- klaude_code/ui/renderers/developer.py +158 -0
- klaude_code/ui/renderers/diffs.py +215 -0
- klaude_code/ui/renderers/errors.py +16 -0
- klaude_code/ui/renderers/metadata.py +190 -0
- klaude_code/ui/renderers/sub_agent.py +71 -0
- klaude_code/ui/renderers/thinking.py +39 -0
- klaude_code/ui/renderers/tools.py +551 -0
- klaude_code/ui/renderers/user_input.py +65 -0
- klaude_code/ui/rich/__init__.py +1 -0
- klaude_code/ui/rich/live.py +65 -0
- klaude_code/ui/rich/markdown.py +308 -0
- klaude_code/ui/rich/quote.py +34 -0
- klaude_code/ui/rich/searchable_text.py +71 -0
- klaude_code/ui/rich/status.py +240 -0
- klaude_code/ui/rich/theme.py +274 -0
- klaude_code/ui/terminal/__init__.py +1 -0
- klaude_code/ui/terminal/color.py +244 -0
- klaude_code/ui/terminal/control.py +147 -0
- klaude_code/ui/terminal/notifier.py +107 -0
- klaude_code/ui/terminal/progress_bar.py +87 -0
- klaude_code/ui/utils/__init__.py +1 -0
- klaude_code/ui/utils/common.py +108 -0
- klaude_code/ui/utils/debouncer.py +42 -0
- klaude_code/version.py +163 -0
- klaude_code-1.2.6.dist-info/METADATA +178 -0
- klaude_code-1.2.6.dist-info/RECORD +167 -0
- klaude_code-1.2.6.dist-info/WHEEL +4 -0
- klaude_code-1.2.6.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from klaude_code.session import Session
|
|
6
|
+
from klaude_code.trace import log
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) -> bool:
|
|
10
|
+
"""Show session list and confirm deletion using questionary."""
|
|
11
|
+
import questionary
|
|
12
|
+
|
|
13
|
+
def _fmt(ts: float) -> str:
|
|
14
|
+
try:
|
|
15
|
+
return time.strftime("%m-%d %H:%M:%S", time.localtime(ts))
|
|
16
|
+
except Exception:
|
|
17
|
+
return str(ts)
|
|
18
|
+
|
|
19
|
+
log(f"Sessions to delete ({len(sessions)}):")
|
|
20
|
+
for s in sessions:
|
|
21
|
+
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:
|
|
24
|
+
first_msg += "..."
|
|
25
|
+
log(f" {_fmt(s.updated_at)} {msg_count_display:>3} msgs {first_msg}")
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
questionary.confirm(
|
|
29
|
+
message,
|
|
30
|
+
default=False,
|
|
31
|
+
style=questionary.Style([("question", "bold")]),
|
|
32
|
+
).ask()
|
|
33
|
+
or False
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def session_clean(
|
|
38
|
+
min_messages: int = typer.Option(5, "--min", "-n", help="Minimum messages to keep a session"),
|
|
39
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Remove sessions with fewer than N messages (default: 5)"""
|
|
42
|
+
sessions = Session.list_sessions()
|
|
43
|
+
to_delete = [s for s in sessions if 0 <= s.messages_count < min_messages]
|
|
44
|
+
|
|
45
|
+
if not to_delete:
|
|
46
|
+
log(f"No sessions with fewer than {min_messages} messages found.")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if not yes:
|
|
50
|
+
if not _session_confirm(to_delete, "Delete these sessions?"):
|
|
51
|
+
log("Aborted.")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
deleted = Session.clean_small_sessions(min_messages)
|
|
55
|
+
log(f"Deleted {deleted} session(s).")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def session_clean_all(
|
|
59
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Remove all sessions for the current project"""
|
|
62
|
+
sessions = Session.list_sessions()
|
|
63
|
+
|
|
64
|
+
if not sessions:
|
|
65
|
+
log("No sessions found.")
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
if not yes:
|
|
69
|
+
if not _session_confirm(sessions, "Delete ALL sessions? This cannot be undone."):
|
|
70
|
+
log("Aborted.")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
deleted = Session.clean_all_sessions()
|
|
74
|
+
log(f"Deleted {deleted} session(s).")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def register_session_commands(session_app: typer.Typer) -> None:
|
|
78
|
+
"""Register session subcommands to the given Typer app."""
|
|
79
|
+
session_app.command("clean")(session_clean)
|
|
80
|
+
session_app.command("clean-all")(session_clean_all)
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
|
11
|
+
from .registry import (
|
|
12
|
+
dispatch_command,
|
|
13
|
+
get_commands,
|
|
14
|
+
has_interactive_command,
|
|
15
|
+
is_slash_command_name,
|
|
16
|
+
load_prompt_commands,
|
|
17
|
+
register_command,
|
|
18
|
+
)
|
|
19
|
+
from .status_cmd import StatusCommand
|
|
20
|
+
from .terminal_setup_cmd import TerminalSetupCommand
|
|
21
|
+
|
|
22
|
+
# Dynamically load prompt commands
|
|
23
|
+
load_prompt_commands()
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ClearCommand",
|
|
27
|
+
"DiffCommand",
|
|
28
|
+
"HelpCommand",
|
|
29
|
+
"ModelCommand",
|
|
30
|
+
"ExportCommand",
|
|
31
|
+
"RefreshTerminalCommand",
|
|
32
|
+
"StatusCommand",
|
|
33
|
+
"TerminalSetupCommand",
|
|
34
|
+
"register_command",
|
|
35
|
+
"CommandABC",
|
|
36
|
+
"CommandResult",
|
|
37
|
+
"InputAction",
|
|
38
|
+
"InputActionType",
|
|
39
|
+
"dispatch_command",
|
|
40
|
+
"get_commands",
|
|
41
|
+
"is_slash_command_name",
|
|
42
|
+
"has_interactive_command",
|
|
43
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register_command
|
|
8
|
+
class ClearCommand(CommandABC):
|
|
9
|
+
"""Clear current session and start a new conversation"""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> commands.CommandName:
|
|
13
|
+
return commands.CommandName.CLEAR
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def summary(self) -> str:
|
|
17
|
+
return "Clear conversation history and free up context"
|
|
18
|
+
|
|
19
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
20
|
+
return CommandResult(actions=[InputAction.clear()])
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from klaude_code.core.agent import Agent
|
|
7
|
+
from klaude_code.protocol import commands
|
|
8
|
+
from klaude_code.protocol import events as protocol_events
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InputActionType(str, Enum):
|
|
12
|
+
"""Supported input action kinds."""
|
|
13
|
+
|
|
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."""
|
|
21
|
+
|
|
22
|
+
type: InputActionType
|
|
23
|
+
text: str = ""
|
|
24
|
+
model_name: str | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def run_agent(cls, text: str) -> "InputAction":
|
|
28
|
+
"""Create a RunAgent action preserving the provided text."""
|
|
29
|
+
|
|
30
|
+
return cls(type=InputActionType.RUN_AGENT, text=text)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def change_model(cls, model_name: str) -> "InputAction":
|
|
34
|
+
"""Create a ChangeModel action for the provided model name."""
|
|
35
|
+
|
|
36
|
+
return cls(type=InputActionType.CHANGE_MODEL, model_name=model_name)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def clear(cls) -> "InputAction":
|
|
40
|
+
"""Create a Clear action to reset the session."""
|
|
41
|
+
|
|
42
|
+
return cls(type=InputActionType.CLEAR)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CommandResult(BaseModel):
|
|
46
|
+
"""Result of a command execution."""
|
|
47
|
+
|
|
48
|
+
events: (
|
|
49
|
+
list[protocol_events.DeveloperMessageEvent | protocol_events.WelcomeEvent | protocol_events.ReplayHistoryEvent]
|
|
50
|
+
| None
|
|
51
|
+
) = None # List of UI events to display immediately
|
|
52
|
+
actions: list[InputAction] | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CommandABC(ABC):
|
|
56
|
+
"""Abstract base class for slash commands."""
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def name(self) -> commands.CommandName | str:
|
|
61
|
+
"""Command name without the leading slash."""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def summary(self) -> str:
|
|
67
|
+
"""Brief description of what this command does."""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def is_interactive(self) -> bool:
|
|
72
|
+
"""Whether this command is interactive."""
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def support_addition_params(self) -> bool:
|
|
77
|
+
"""Whether this command support additional parameters."""
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
82
|
+
"""
|
|
83
|
+
Execute the command.
|
|
84
|
+
|
|
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
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
CommandResult: Result of the command execution
|
|
91
|
+
"""
|
|
92
|
+
pass
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult
|
|
5
|
+
from klaude_code.command.registry import register_command
|
|
6
|
+
from klaude_code.core.agent import Agent
|
|
7
|
+
from klaude_code.protocol import commands, events, model
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register_command
|
|
11
|
+
class DiffCommand(CommandABC):
|
|
12
|
+
"""Show git diff for the current repository."""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def name(self) -> commands.CommandName:
|
|
16
|
+
return commands.CommandName.DIFF
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def summary(self) -> str:
|
|
20
|
+
return "Show git diff"
|
|
21
|
+
|
|
22
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
23
|
+
try:
|
|
24
|
+
# Check if current directory is in a git repository
|
|
25
|
+
git_check = subprocess.run(
|
|
26
|
+
["git", "rev-parse", "--is-inside-work-tree"],
|
|
27
|
+
cwd=Path.cwd(),
|
|
28
|
+
capture_output=True,
|
|
29
|
+
text=True,
|
|
30
|
+
timeout=5.0,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if git_check.returncode != 0:
|
|
34
|
+
# Not in a git repository
|
|
35
|
+
event = events.DeveloperMessageEvent(
|
|
36
|
+
session_id=agent.session.id,
|
|
37
|
+
item=model.DeveloperMessageItem(
|
|
38
|
+
content="No in a git repo",
|
|
39
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
return CommandResult(events=[event])
|
|
43
|
+
|
|
44
|
+
# Run git diff in current directory
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
["git", "diff", "HEAD"],
|
|
47
|
+
cwd=Path.cwd(),
|
|
48
|
+
capture_output=True,
|
|
49
|
+
text=True,
|
|
50
|
+
timeout=10.0,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if result.returncode != 0:
|
|
54
|
+
# Git command failed
|
|
55
|
+
error_msg = result.stderr.strip() or "git diff command failed"
|
|
56
|
+
event = events.DeveloperMessageEvent(
|
|
57
|
+
session_id=agent.session.id,
|
|
58
|
+
item=model.DeveloperMessageItem(
|
|
59
|
+
content=f"Error: {error_msg}",
|
|
60
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
return CommandResult(events=[event])
|
|
64
|
+
|
|
65
|
+
diff_output = result.stdout.strip()
|
|
66
|
+
|
|
67
|
+
# Get untracked files
|
|
68
|
+
untracked_result = subprocess.run(
|
|
69
|
+
["git", "ls-files", "--others", "--exclude-standard"],
|
|
70
|
+
cwd=Path.cwd(),
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
73
|
+
timeout=10.0,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
untracked_files = untracked_result.stdout.strip()
|
|
77
|
+
|
|
78
|
+
# Combine diff output and untracked files
|
|
79
|
+
output_parts: list[str] = []
|
|
80
|
+
|
|
81
|
+
if diff_output:
|
|
82
|
+
output_parts.append(diff_output)
|
|
83
|
+
|
|
84
|
+
if untracked_files:
|
|
85
|
+
untracked_lines = untracked_files.split("\n")
|
|
86
|
+
untracked_section = "git ls-files --others --exclude-standard\n" + "\n".join(
|
|
87
|
+
f"{file}" for file in untracked_lines
|
|
88
|
+
)
|
|
89
|
+
output_parts.append(untracked_section)
|
|
90
|
+
|
|
91
|
+
if not output_parts:
|
|
92
|
+
# No changes and no untracked files
|
|
93
|
+
event = events.DeveloperMessageEvent(
|
|
94
|
+
session_id=agent.session.id,
|
|
95
|
+
item=model.DeveloperMessageItem(
|
|
96
|
+
content="", command_output=model.CommandOutput(command_name=self.name)
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
return CommandResult(events=[event])
|
|
100
|
+
|
|
101
|
+
# Has changes or untracked files
|
|
102
|
+
combined_output = "\n\n".join(output_parts)
|
|
103
|
+
event = events.DeveloperMessageEvent(
|
|
104
|
+
session_id=agent.session.id,
|
|
105
|
+
item=model.DeveloperMessageItem(
|
|
106
|
+
content=combined_output,
|
|
107
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
return CommandResult(events=[event])
|
|
111
|
+
|
|
112
|
+
except subprocess.TimeoutExpired:
|
|
113
|
+
event = events.DeveloperMessageEvent(
|
|
114
|
+
session_id=agent.session.id,
|
|
115
|
+
item=model.DeveloperMessageItem(
|
|
116
|
+
content="Error: git diff command timeout",
|
|
117
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
return CommandResult(events=[event])
|
|
121
|
+
except FileNotFoundError:
|
|
122
|
+
event = events.DeveloperMessageEvent(
|
|
123
|
+
session_id=agent.session.id,
|
|
124
|
+
item=model.DeveloperMessageItem(
|
|
125
|
+
content="Error: git command not found",
|
|
126
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
return CommandResult(events=[event])
|
|
130
|
+
except Exception as e:
|
|
131
|
+
event = events.DeveloperMessageEvent(
|
|
132
|
+
session_id=agent.session.id,
|
|
133
|
+
item=model.DeveloperMessageItem(
|
|
134
|
+
content=f"Error:{e}",
|
|
135
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
return CommandResult(events=[event])
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult
|
|
7
|
+
from klaude_code.command.registry import register_command
|
|
8
|
+
from klaude_code.core.agent import Agent
|
|
9
|
+
from klaude_code.protocol import commands, events, model
|
|
10
|
+
from klaude_code.session.export import build_export_html, get_default_export_path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@register_command
|
|
14
|
+
class ExportCommand(CommandABC):
|
|
15
|
+
"""Export the current session into a standalone HTML transcript."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self) -> commands.CommandName:
|
|
19
|
+
return commands.CommandName.EXPORT
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def summary(self) -> str:
|
|
23
|
+
return "Export current session to HTML"
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def support_addition_params(self) -> bool:
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def is_interactive(self) -> bool:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
34
|
+
try:
|
|
35
|
+
output_path = self._resolve_output_path(raw, agent)
|
|
36
|
+
html_doc = self._build_html(agent)
|
|
37
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
output_path.write_text(html_doc, encoding="utf-8")
|
|
39
|
+
self._open_file(output_path)
|
|
40
|
+
event = events.DeveloperMessageEvent(
|
|
41
|
+
session_id=agent.session.id,
|
|
42
|
+
item=model.DeveloperMessageItem(
|
|
43
|
+
content=f"Session exported and opened: {output_path}",
|
|
44
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
return CommandResult(events=[event])
|
|
48
|
+
except Exception as exc: # pragma: no cover - safeguard for unexpected errors
|
|
49
|
+
import traceback
|
|
50
|
+
|
|
51
|
+
event = events.DeveloperMessageEvent(
|
|
52
|
+
session_id=agent.session.id,
|
|
53
|
+
item=model.DeveloperMessageItem(
|
|
54
|
+
content=f"Failed to export session: {exc}\n{traceback.format_exc()}",
|
|
55
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
return CommandResult(events=[event])
|
|
59
|
+
|
|
60
|
+
def _resolve_output_path(self, raw: str, agent: Agent) -> Path:
|
|
61
|
+
trimmed = raw.strip()
|
|
62
|
+
if trimmed:
|
|
63
|
+
candidate = Path(trimmed).expanduser()
|
|
64
|
+
if not candidate.is_absolute():
|
|
65
|
+
candidate = Path(agent.session.work_dir) / candidate
|
|
66
|
+
if candidate.suffix.lower() != ".html":
|
|
67
|
+
candidate = candidate.with_suffix(".html")
|
|
68
|
+
return candidate
|
|
69
|
+
return get_default_export_path(agent.session)
|
|
70
|
+
|
|
71
|
+
def _open_file(self, path: Path) -> None:
|
|
72
|
+
try:
|
|
73
|
+
subprocess.run(["open", str(path)], check=True)
|
|
74
|
+
except FileNotFoundError as exc: # pragma: no cover - depends on platform
|
|
75
|
+
msg = "`open` command not found; please open the HTML manually."
|
|
76
|
+
raise RuntimeError(msg) from exc
|
|
77
|
+
except subprocess.CalledProcessError as exc: # pragma: no cover - depends on platform
|
|
78
|
+
msg = f"Failed to open HTML with `open`: {exc}"
|
|
79
|
+
raise RuntimeError(msg) from exc
|
|
80
|
+
|
|
81
|
+
def _build_html(self, agent: Agent) -> str:
|
|
82
|
+
profile = agent.profile
|
|
83
|
+
system_prompt = (profile.system_prompt if profile else "") or ""
|
|
84
|
+
tools = profile.tools if profile else []
|
|
85
|
+
model_name = profile.llm_client.model_name if profile else "unknown"
|
|
86
|
+
return build_export_html(agent.session, system_prompt, tools, model_name)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult
|
|
2
|
+
from klaude_code.command.registry import register_command
|
|
3
|
+
from klaude_code.core.agent import Agent
|
|
4
|
+
from klaude_code.protocol import commands, events, model
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register_command
|
|
8
|
+
class HelpCommand(CommandABC):
|
|
9
|
+
"""Display help information for all available slash commands."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> commands.CommandName:
|
|
13
|
+
return commands.CommandName.HELP
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def summary(self) -> str:
|
|
17
|
+
return "Show help and available commands"
|
|
18
|
+
|
|
19
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
20
|
+
lines: list[str] = [
|
|
21
|
+
"""
|
|
22
|
+
Usage:
|
|
23
|
+
[b]@[/b] to mention file
|
|
24
|
+
[b]esc[/b] to interrupt agent task
|
|
25
|
+
[b]shift-enter[/b] or [b]ctrl-j[/b] for new line
|
|
26
|
+
[b]ctrl-v[/b] for pasting image
|
|
27
|
+
[b]--continue[/b] or [b]--resume[/b] to continue an old session
|
|
28
|
+
[b]--select-model[/b] to switch model
|
|
29
|
+
|
|
30
|
+
Available slash commands:"""
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Import here to avoid circular dependency
|
|
34
|
+
from .registry import get_commands
|
|
35
|
+
|
|
36
|
+
commands = get_commands()
|
|
37
|
+
|
|
38
|
+
if commands:
|
|
39
|
+
for cmd_name, cmd_obj in sorted(commands.items()):
|
|
40
|
+
additional_instructions = " \\[additional instructions]" if cmd_obj.support_addition_params else ""
|
|
41
|
+
lines.append(f" [b]/{cmd_name}[/b]{additional_instructions} — {cmd_obj.summary}")
|
|
42
|
+
|
|
43
|
+
event = events.DeveloperMessageEvent(
|
|
44
|
+
session_id=agent.session.id,
|
|
45
|
+
item=model.DeveloperMessageItem(
|
|
46
|
+
content="\n".join(lines),
|
|
47
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return CommandResult(events=[event])
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
|
|
4
|
+
from klaude_code.command.registry import register_command
|
|
5
|
+
from klaude_code.config import select_model_from_config
|
|
6
|
+
from klaude_code.core.agent import Agent
|
|
7
|
+
from klaude_code.protocol import commands, events, model
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register_command
|
|
11
|
+
class ModelCommand(CommandABC):
|
|
12
|
+
"""Display or change the model configuration."""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def name(self) -> commands.CommandName:
|
|
16
|
+
return commands.CommandName.MODEL
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def summary(self) -> str:
|
|
20
|
+
return "Select and switch LLM"
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def is_interactive(self) -> bool:
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
27
|
+
selected_model = await asyncio.to_thread(select_model_from_config, preferred=raw)
|
|
28
|
+
|
|
29
|
+
current_model = agent.profile.llm_client.model_name if agent.profile else None
|
|
30
|
+
if selected_model is None or selected_model == current_model:
|
|
31
|
+
return CommandResult(
|
|
32
|
+
events=[
|
|
33
|
+
events.DeveloperMessageEvent(
|
|
34
|
+
session_id=agent.session.id,
|
|
35
|
+
item=model.DeveloperMessageItem(
|
|
36
|
+
content="(no change)",
|
|
37
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return CommandResult(actions=[InputAction.change_model(selected_model)])
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Update dev documentation before context compaction
|
|
3
|
+
from: https://github.com/diet103/claude-code-infrastructure-showcase/blob/main/.claude/commands/dev-docs-update.md
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
We're approaching context limits. Please update the development documentation to ensure seamless continuation after context reset.
|
|
7
|
+
|
|
8
|
+
## Required Updates
|
|
9
|
+
|
|
10
|
+
### 1. Update Active Task Documentation
|
|
11
|
+
For each task in `/dev/active/`:
|
|
12
|
+
- Update `[task-name]-context.md` with:
|
|
13
|
+
- Current implementation state
|
|
14
|
+
- Key decisions made this session
|
|
15
|
+
- Files modified and why
|
|
16
|
+
- Any blockers or issues discovered
|
|
17
|
+
- Next immediate steps
|
|
18
|
+
- Last Updated timestamp
|
|
19
|
+
|
|
20
|
+
- Update `[task-name]-tasks.md` with:
|
|
21
|
+
- Mark completed tasks as ✅
|
|
22
|
+
- Add any new tasks discovered
|
|
23
|
+
- Update in-progress tasks with current status
|
|
24
|
+
- Reorder priorities if needed
|
|
25
|
+
|
|
26
|
+
### 2. Capture Session Context
|
|
27
|
+
Include any relevant information about:
|
|
28
|
+
- Complex problems solved
|
|
29
|
+
- Architectural decisions made
|
|
30
|
+
- Tricky bugs found and fixed
|
|
31
|
+
- Integration points discovered
|
|
32
|
+
- Testing approaches used
|
|
33
|
+
- Performance optimizations made
|
|
34
|
+
|
|
35
|
+
### 3. Update Memory (if applicable)
|
|
36
|
+
- Store any new patterns or solutions in project memory/documentation
|
|
37
|
+
- Update entity relationships discovered
|
|
38
|
+
- Add observations about system behavior
|
|
39
|
+
|
|
40
|
+
### 4. Document Unfinished Work
|
|
41
|
+
- What was being worked on when context limit approached
|
|
42
|
+
- Exact state of any partially completed features
|
|
43
|
+
- Commands that need to be run on restart
|
|
44
|
+
- Any temporary workarounds that need permanent fixes
|
|
45
|
+
|
|
46
|
+
### 5. Create Handoff Notes
|
|
47
|
+
If switching to a new conversation:
|
|
48
|
+
- Exact file and line being edited
|
|
49
|
+
- The goal of current changes
|
|
50
|
+
- Any uncommitted changes that need attention
|
|
51
|
+
- Test commands to verify work
|
|
52
|
+
|
|
53
|
+
## Additional Context:
|
|
54
|
+
$ARGUMENTS
|
|
55
|
+
|
|
56
|
+
**Priority**: Focus on capturing information that would be hard to rediscover or reconstruct from code alone.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Create a comprehensive strategic plan with structured task breakdown
|
|
3
|
+
from: https://github.com/diet103/claude-code-infrastructure-showcase/blob/main/.claude/commands/dev-docs.md
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Create a comprehensive, actionable plan for:
|
|
7
|
+
$ARGUMENTS
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
1. **Analyze the request** and determine the scope of planning needed
|
|
12
|
+
2. **Examine relevant files** in the codebase to understand current state
|
|
13
|
+
3. **Create a structured plan** with:
|
|
14
|
+
- Executive Summary
|
|
15
|
+
- Current State Analysis
|
|
16
|
+
- Proposed Future State
|
|
17
|
+
- Implementation Phases (broken into sections)
|
|
18
|
+
- Detailed Tasks (actionable items with clear acceptance criteria)
|
|
19
|
+
- Risk Assessment and Mitigation Strategies
|
|
20
|
+
- Success Metrics
|
|
21
|
+
- Required Resources and Dependencies
|
|
22
|
+
- Timeline Estimates
|
|
23
|
+
|
|
24
|
+
4. **Task Breakdown Structure**:
|
|
25
|
+
- Each major section represents a phase or component
|
|
26
|
+
- Number and prioritize tasks within sections
|
|
27
|
+
- Include clear acceptance criteria for each task
|
|
28
|
+
- Specify dependencies between tasks
|
|
29
|
+
- Estimate effort levels (S/M/L/XL)
|
|
30
|
+
|
|
31
|
+
5. **Create task management structure**:
|
|
32
|
+
- Create directory: `dev/active/[task-name]/` (relative to project root)
|
|
33
|
+
- Generate three files:
|
|
34
|
+
- `[task-name]-plan.md` - The comprehensive plan
|
|
35
|
+
- `[task-name]-context.md` - Key files, decisions, dependencies
|
|
36
|
+
- `[task-name]-tasks.md` - Checklist format for tracking progress
|
|
37
|
+
- Include "Last Updated: YYYY-MM-DD" in each file
|
|
38
|
+
|
|
39
|
+
6. **Stop and Consult**: Pause and negotiate the plan with the user.
|
|
40
|
+
|
|
41
|
+
## Quality Standards
|
|
42
|
+
- Plans must be self-contained with all necessary context
|
|
43
|
+
- Use clear, actionable language
|
|
44
|
+
- Include specific technical details where relevant
|
|
45
|
+
- Consider both technical and business perspectives
|
|
46
|
+
- Account for potential risks and edge cases
|