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.
Files changed (167) hide show
  1. klaude_code/__init__.py +0 -0
  2. klaude_code/cli/__init__.py +1 -0
  3. klaude_code/cli/main.py +298 -0
  4. klaude_code/cli/runtime.py +331 -0
  5. klaude_code/cli/session_cmd.py +80 -0
  6. klaude_code/command/__init__.py +43 -0
  7. klaude_code/command/clear_cmd.py +20 -0
  8. klaude_code/command/command_abc.py +92 -0
  9. klaude_code/command/diff_cmd.py +138 -0
  10. klaude_code/command/export_cmd.py +86 -0
  11. klaude_code/command/help_cmd.py +51 -0
  12. klaude_code/command/model_cmd.py +43 -0
  13. klaude_code/command/prompt-dev-docs-update.md +56 -0
  14. klaude_code/command/prompt-dev-docs.md +46 -0
  15. klaude_code/command/prompt-init.md +45 -0
  16. klaude_code/command/prompt_command.py +69 -0
  17. klaude_code/command/refresh_cmd.py +43 -0
  18. klaude_code/command/registry.py +110 -0
  19. klaude_code/command/status_cmd.py +111 -0
  20. klaude_code/command/terminal_setup_cmd.py +252 -0
  21. klaude_code/config/__init__.py +11 -0
  22. klaude_code/config/config.py +177 -0
  23. klaude_code/config/list_model.py +162 -0
  24. klaude_code/config/select_model.py +67 -0
  25. klaude_code/const/__init__.py +133 -0
  26. klaude_code/core/__init__.py +0 -0
  27. klaude_code/core/agent.py +165 -0
  28. klaude_code/core/executor.py +485 -0
  29. klaude_code/core/manager/__init__.py +19 -0
  30. klaude_code/core/manager/agent_manager.py +127 -0
  31. klaude_code/core/manager/llm_clients.py +42 -0
  32. klaude_code/core/manager/llm_clients_builder.py +49 -0
  33. klaude_code/core/manager/sub_agent_manager.py +86 -0
  34. klaude_code/core/prompt.py +89 -0
  35. klaude_code/core/prompts/prompt-claude-code.md +98 -0
  36. klaude_code/core/prompts/prompt-codex.md +331 -0
  37. klaude_code/core/prompts/prompt-gemini.md +43 -0
  38. klaude_code/core/prompts/prompt-subagent-explore.md +27 -0
  39. klaude_code/core/prompts/prompt-subagent-oracle.md +23 -0
  40. klaude_code/core/prompts/prompt-subagent-webfetch.md +46 -0
  41. klaude_code/core/prompts/prompt-subagent.md +8 -0
  42. klaude_code/core/reminders.py +445 -0
  43. klaude_code/core/task.py +237 -0
  44. klaude_code/core/tool/__init__.py +75 -0
  45. klaude_code/core/tool/file/__init__.py +0 -0
  46. klaude_code/core/tool/file/apply_patch.py +492 -0
  47. klaude_code/core/tool/file/apply_patch_tool.md +1 -0
  48. klaude_code/core/tool/file/apply_patch_tool.py +204 -0
  49. klaude_code/core/tool/file/edit_tool.md +9 -0
  50. klaude_code/core/tool/file/edit_tool.py +274 -0
  51. klaude_code/core/tool/file/multi_edit_tool.md +42 -0
  52. klaude_code/core/tool/file/multi_edit_tool.py +199 -0
  53. klaude_code/core/tool/file/read_tool.md +14 -0
  54. klaude_code/core/tool/file/read_tool.py +326 -0
  55. klaude_code/core/tool/file/write_tool.md +8 -0
  56. klaude_code/core/tool/file/write_tool.py +146 -0
  57. klaude_code/core/tool/memory/__init__.py +0 -0
  58. klaude_code/core/tool/memory/memory_tool.md +16 -0
  59. klaude_code/core/tool/memory/memory_tool.py +462 -0
  60. klaude_code/core/tool/memory/skill_loader.py +245 -0
  61. klaude_code/core/tool/memory/skill_tool.md +24 -0
  62. klaude_code/core/tool/memory/skill_tool.py +97 -0
  63. klaude_code/core/tool/shell/__init__.py +0 -0
  64. klaude_code/core/tool/shell/bash_tool.md +43 -0
  65. klaude_code/core/tool/shell/bash_tool.py +123 -0
  66. klaude_code/core/tool/shell/command_safety.py +363 -0
  67. klaude_code/core/tool/sub_agent_tool.py +83 -0
  68. klaude_code/core/tool/todo/__init__.py +0 -0
  69. klaude_code/core/tool/todo/todo_write_tool.md +182 -0
  70. klaude_code/core/tool/todo/todo_write_tool.py +121 -0
  71. klaude_code/core/tool/todo/update_plan_tool.md +3 -0
  72. klaude_code/core/tool/todo/update_plan_tool.py +104 -0
  73. klaude_code/core/tool/tool_abc.py +25 -0
  74. klaude_code/core/tool/tool_context.py +106 -0
  75. klaude_code/core/tool/tool_registry.py +78 -0
  76. klaude_code/core/tool/tool_runner.py +252 -0
  77. klaude_code/core/tool/truncation.py +170 -0
  78. klaude_code/core/tool/web/__init__.py +0 -0
  79. klaude_code/core/tool/web/mermaid_tool.md +21 -0
  80. klaude_code/core/tool/web/mermaid_tool.py +76 -0
  81. klaude_code/core/tool/web/web_fetch_tool.md +8 -0
  82. klaude_code/core/tool/web/web_fetch_tool.py +159 -0
  83. klaude_code/core/turn.py +220 -0
  84. klaude_code/llm/__init__.py +21 -0
  85. klaude_code/llm/anthropic/__init__.py +3 -0
  86. klaude_code/llm/anthropic/client.py +221 -0
  87. klaude_code/llm/anthropic/input.py +200 -0
  88. klaude_code/llm/client.py +49 -0
  89. klaude_code/llm/input_common.py +239 -0
  90. klaude_code/llm/openai_compatible/__init__.py +3 -0
  91. klaude_code/llm/openai_compatible/client.py +211 -0
  92. klaude_code/llm/openai_compatible/input.py +109 -0
  93. klaude_code/llm/openai_compatible/tool_call_accumulator.py +80 -0
  94. klaude_code/llm/openrouter/__init__.py +3 -0
  95. klaude_code/llm/openrouter/client.py +200 -0
  96. klaude_code/llm/openrouter/input.py +160 -0
  97. klaude_code/llm/openrouter/reasoning_handler.py +209 -0
  98. klaude_code/llm/registry.py +22 -0
  99. klaude_code/llm/responses/__init__.py +3 -0
  100. klaude_code/llm/responses/client.py +216 -0
  101. klaude_code/llm/responses/input.py +167 -0
  102. klaude_code/llm/usage.py +109 -0
  103. klaude_code/protocol/__init__.py +4 -0
  104. klaude_code/protocol/commands.py +21 -0
  105. klaude_code/protocol/events.py +163 -0
  106. klaude_code/protocol/llm_param.py +147 -0
  107. klaude_code/protocol/model.py +287 -0
  108. klaude_code/protocol/op.py +89 -0
  109. klaude_code/protocol/op_handler.py +28 -0
  110. klaude_code/protocol/sub_agent.py +348 -0
  111. klaude_code/protocol/tools.py +15 -0
  112. klaude_code/session/__init__.py +4 -0
  113. klaude_code/session/export.py +624 -0
  114. klaude_code/session/selector.py +76 -0
  115. klaude_code/session/session.py +474 -0
  116. klaude_code/session/templates/export_session.html +1434 -0
  117. klaude_code/trace/__init__.py +3 -0
  118. klaude_code/trace/log.py +168 -0
  119. klaude_code/ui/__init__.py +91 -0
  120. klaude_code/ui/core/__init__.py +1 -0
  121. klaude_code/ui/core/display.py +103 -0
  122. klaude_code/ui/core/input.py +71 -0
  123. klaude_code/ui/core/stage_manager.py +55 -0
  124. klaude_code/ui/modes/__init__.py +1 -0
  125. klaude_code/ui/modes/debug/__init__.py +1 -0
  126. klaude_code/ui/modes/debug/display.py +36 -0
  127. klaude_code/ui/modes/exec/__init__.py +1 -0
  128. klaude_code/ui/modes/exec/display.py +63 -0
  129. klaude_code/ui/modes/repl/__init__.py +51 -0
  130. klaude_code/ui/modes/repl/clipboard.py +152 -0
  131. klaude_code/ui/modes/repl/completers.py +429 -0
  132. klaude_code/ui/modes/repl/display.py +60 -0
  133. klaude_code/ui/modes/repl/event_handler.py +375 -0
  134. klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
  135. klaude_code/ui/modes/repl/key_bindings.py +170 -0
  136. klaude_code/ui/modes/repl/renderer.py +281 -0
  137. klaude_code/ui/renderers/__init__.py +0 -0
  138. klaude_code/ui/renderers/assistant.py +21 -0
  139. klaude_code/ui/renderers/common.py +8 -0
  140. klaude_code/ui/renderers/developer.py +158 -0
  141. klaude_code/ui/renderers/diffs.py +215 -0
  142. klaude_code/ui/renderers/errors.py +16 -0
  143. klaude_code/ui/renderers/metadata.py +190 -0
  144. klaude_code/ui/renderers/sub_agent.py +71 -0
  145. klaude_code/ui/renderers/thinking.py +39 -0
  146. klaude_code/ui/renderers/tools.py +551 -0
  147. klaude_code/ui/renderers/user_input.py +65 -0
  148. klaude_code/ui/rich/__init__.py +1 -0
  149. klaude_code/ui/rich/live.py +65 -0
  150. klaude_code/ui/rich/markdown.py +308 -0
  151. klaude_code/ui/rich/quote.py +34 -0
  152. klaude_code/ui/rich/searchable_text.py +71 -0
  153. klaude_code/ui/rich/status.py +240 -0
  154. klaude_code/ui/rich/theme.py +274 -0
  155. klaude_code/ui/terminal/__init__.py +1 -0
  156. klaude_code/ui/terminal/color.py +244 -0
  157. klaude_code/ui/terminal/control.py +147 -0
  158. klaude_code/ui/terminal/notifier.py +107 -0
  159. klaude_code/ui/terminal/progress_bar.py +87 -0
  160. klaude_code/ui/utils/__init__.py +1 -0
  161. klaude_code/ui/utils/common.py +108 -0
  162. klaude_code/ui/utils/debouncer.py +42 -0
  163. klaude_code/version.py +163 -0
  164. klaude_code-1.2.6.dist-info/METADATA +178 -0
  165. klaude_code-1.2.6.dist-info/RECORD +167 -0
  166. klaude_code-1.2.6.dist-info/WHEEL +4 -0
  167. 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