klaude-code 1.2.1__py3-none-any.whl → 1.2.3__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 (140) hide show
  1. klaude_code/cli/main.py +9 -4
  2. klaude_code/cli/runtime.py +42 -43
  3. klaude_code/command/__init__.py +7 -5
  4. klaude_code/command/clear_cmd.py +6 -29
  5. klaude_code/command/command_abc.py +44 -8
  6. klaude_code/command/diff_cmd.py +33 -27
  7. klaude_code/command/export_cmd.py +18 -26
  8. klaude_code/command/help_cmd.py +10 -8
  9. klaude_code/command/model_cmd.py +11 -40
  10. klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
  11. klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
  12. klaude_code/command/prompt-init.md +2 -5
  13. klaude_code/command/prompt_command.py +6 -6
  14. klaude_code/command/refresh_cmd.py +4 -5
  15. klaude_code/command/registry.py +16 -19
  16. klaude_code/command/terminal_setup_cmd.py +12 -11
  17. klaude_code/config/__init__.py +4 -0
  18. klaude_code/config/config.py +25 -26
  19. klaude_code/config/list_model.py +8 -3
  20. klaude_code/config/select_model.py +1 -1
  21. klaude_code/const/__init__.py +1 -1
  22. klaude_code/core/__init__.py +0 -3
  23. klaude_code/core/agent.py +25 -50
  24. klaude_code/core/executor.py +268 -101
  25. klaude_code/core/prompt.py +12 -12
  26. klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
  27. klaude_code/core/reminders.py +76 -95
  28. klaude_code/core/task.py +21 -14
  29. klaude_code/core/tool/__init__.py +45 -11
  30. klaude_code/core/tool/file/apply_patch.py +5 -1
  31. klaude_code/core/tool/file/apply_patch_tool.py +11 -13
  32. klaude_code/core/tool/file/edit_tool.py +27 -23
  33. klaude_code/core/tool/file/multi_edit_tool.py +15 -17
  34. klaude_code/core/tool/file/read_tool.py +41 -36
  35. klaude_code/core/tool/file/write_tool.py +13 -15
  36. klaude_code/core/tool/memory/memory_tool.py +85 -68
  37. klaude_code/core/tool/memory/skill_tool.py +10 -12
  38. klaude_code/core/tool/shell/bash_tool.py +24 -22
  39. klaude_code/core/tool/shell/command_safety.py +12 -1
  40. klaude_code/core/tool/sub_agent_tool.py +11 -12
  41. klaude_code/core/tool/todo/todo_write_tool.py +21 -28
  42. klaude_code/core/tool/todo/update_plan_tool.py +14 -24
  43. klaude_code/core/tool/tool_abc.py +3 -4
  44. klaude_code/core/tool/tool_context.py +7 -7
  45. klaude_code/core/tool/tool_registry.py +30 -47
  46. klaude_code/core/tool/tool_runner.py +35 -43
  47. klaude_code/core/tool/truncation.py +14 -20
  48. klaude_code/core/tool/web/mermaid_tool.py +12 -14
  49. klaude_code/core/tool/web/web_fetch_tool.py +15 -17
  50. klaude_code/core/turn.py +19 -7
  51. klaude_code/llm/__init__.py +3 -4
  52. klaude_code/llm/anthropic/client.py +30 -46
  53. klaude_code/llm/anthropic/input.py +4 -11
  54. klaude_code/llm/client.py +29 -8
  55. klaude_code/llm/input_common.py +66 -36
  56. klaude_code/llm/openai_compatible/client.py +42 -84
  57. klaude_code/llm/openai_compatible/input.py +11 -16
  58. klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
  59. klaude_code/llm/openrouter/client.py +40 -289
  60. klaude_code/llm/openrouter/input.py +13 -35
  61. klaude_code/llm/openrouter/reasoning_handler.py +209 -0
  62. klaude_code/llm/registry.py +5 -75
  63. klaude_code/llm/responses/client.py +34 -55
  64. klaude_code/llm/responses/input.py +24 -26
  65. klaude_code/llm/usage.py +109 -0
  66. klaude_code/protocol/__init__.py +4 -0
  67. klaude_code/protocol/events.py +3 -2
  68. klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
  69. klaude_code/protocol/model.py +49 -4
  70. klaude_code/protocol/op.py +18 -16
  71. klaude_code/protocol/op_handler.py +28 -0
  72. klaude_code/{core → protocol}/sub_agent.py +7 -0
  73. klaude_code/session/export.py +150 -70
  74. klaude_code/session/session.py +28 -14
  75. klaude_code/session/templates/export_session.html +180 -42
  76. klaude_code/trace/__init__.py +2 -2
  77. klaude_code/trace/log.py +11 -5
  78. klaude_code/ui/__init__.py +91 -8
  79. klaude_code/ui/core/__init__.py +1 -0
  80. klaude_code/ui/core/display.py +103 -0
  81. klaude_code/ui/core/input.py +71 -0
  82. klaude_code/ui/modes/__init__.py +1 -0
  83. klaude_code/ui/modes/debug/__init__.py +1 -0
  84. klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
  85. klaude_code/ui/modes/exec/__init__.py +1 -0
  86. klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
  87. klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
  88. klaude_code/ui/modes/repl/clipboard.py +152 -0
  89. klaude_code/ui/modes/repl/completers.py +429 -0
  90. klaude_code/ui/modes/repl/display.py +60 -0
  91. klaude_code/ui/modes/repl/event_handler.py +375 -0
  92. klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
  93. klaude_code/ui/modes/repl/key_bindings.py +170 -0
  94. klaude_code/ui/{repl → modes/repl}/renderer.py +109 -132
  95. klaude_code/ui/renderers/assistant.py +21 -0
  96. klaude_code/ui/renderers/common.py +0 -16
  97. klaude_code/ui/renderers/developer.py +18 -18
  98. klaude_code/ui/renderers/diffs.py +36 -14
  99. klaude_code/ui/renderers/errors.py +1 -1
  100. klaude_code/ui/renderers/metadata.py +50 -27
  101. klaude_code/ui/renderers/sub_agent.py +43 -9
  102. klaude_code/ui/renderers/thinking.py +33 -1
  103. klaude_code/ui/renderers/tools.py +212 -20
  104. klaude_code/ui/renderers/user_input.py +19 -23
  105. klaude_code/ui/rich/__init__.py +1 -0
  106. klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
  107. klaude_code/ui/{renderers → rich}/status.py +29 -18
  108. klaude_code/ui/{base → rich}/theme.py +8 -2
  109. klaude_code/ui/terminal/__init__.py +1 -0
  110. klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
  111. klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
  112. klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
  113. klaude_code/ui/utils/__init__.py +1 -0
  114. klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
  115. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
  116. klaude_code-1.2.3.dist-info/RECORD +161 -0
  117. klaude_code/core/clipboard_manifest.py +0 -124
  118. klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
  119. klaude_code/ui/base/__init__.py +0 -1
  120. klaude_code/ui/base/display_abc.py +0 -36
  121. klaude_code/ui/base/input_abc.py +0 -20
  122. klaude_code/ui/repl/display.py +0 -36
  123. klaude_code/ui/repl/event_handler.py +0 -247
  124. klaude_code/ui/repl/input.py +0 -773
  125. klaude_code/ui/rich_ext/__init__.py +0 -1
  126. klaude_code-1.2.1.dist-info/RECORD +0 -151
  127. /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
  128. /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
  129. /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
  130. /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
  131. /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
  132. /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
  133. /klaude_code/ui/{base → core}/stage_manager.py +0 -0
  134. /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
  135. /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
  136. /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
  137. /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
  138. /klaude_code/ui/{base → utils}/debouncer.py +0 -0
  139. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
  140. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/entry_points.txt +0 -0
klaude_code/cli/main.py CHANGED
@@ -9,12 +9,10 @@ import typer
9
9
 
10
10
  from klaude_code.cli.runtime import DEBUG_FILTER_HELP, AppInitConfig, resolve_debug_settings, run_exec, run_interactive
11
11
  from klaude_code.cli.session_cmd import register_session_commands
12
- from klaude_code.config import config_path, load_config
13
- from klaude_code.config.list_model import display_models_and_providers
14
- from klaude_code.config.select_model import select_model_from_config
12
+ from klaude_code.config import config_path, display_models_and_providers, load_config, select_model_from_config
15
13
  from klaude_code.session import Session, resume_select_session
16
14
  from klaude_code.trace import log
17
- from klaude_code.ui.base.terminal_color import is_light_terminal_background
15
+ from klaude_code.ui.terminal.color import is_light_terminal_background
18
16
 
19
17
 
20
18
  def set_terminal_title(title: str) -> None:
@@ -64,6 +62,7 @@ def list_models() -> None:
64
62
 
65
63
 
66
64
  @app.command("config")
65
+ @app.command("conf", hidden=True)
67
66
  def edit_config() -> None:
68
67
  """Open the configuration file in $EDITOR or default system editor"""
69
68
  editor = os.environ.get("EDITOR")
@@ -150,6 +149,11 @@ def exec_command(
150
149
  "--vanilla",
151
150
  help="Vanilla mode exposes the model's raw API behavior: it provides only minimal tools (Bash, Read & Edit) and omits system prompts and reminders.",
152
151
  ),
152
+ stream_json: bool = typer.Option(
153
+ False,
154
+ "--stream-json",
155
+ help="Stream all events as JSON lines to stdout.",
156
+ ),
153
157
  ) -> None:
154
158
  """Execute non-interactively with provided input."""
155
159
 
@@ -195,6 +199,7 @@ def exec_command(
195
199
  vanilla=vanilla,
196
200
  is_exec_mode=True,
197
201
  debug_filters=debug_filters,
202
+ stream_json=stream_json,
198
203
  )
199
204
 
200
205
  asyncio.run(
@@ -8,26 +8,20 @@ import typer
8
8
  from rich.text import Text
9
9
 
10
10
  from klaude_code import ui
11
- from klaude_code.command.registry import is_interactive_command
12
- from klaude_code.config import load_config
13
- from klaude_code.config.config import Config
11
+ from klaude_code.command import has_interactive_command
12
+ from klaude_code.config import Config, load_config
14
13
  from klaude_code.core.agent import DefaultModelProfileProvider, VanillaModelProfileProvider
15
- from klaude_code.core.executor import Executor
16
- from klaude_code.core.sub_agent import iter_sub_agent_profiles
17
- from klaude_code.core.tool.memory.skill_loader import SkillLoader
18
- from klaude_code.core.tool.memory.skill_tool import SkillTool
19
- from klaude_code.llm import LLMClients
20
- from klaude_code.protocol import op
21
- from klaude_code.protocol.events import EndEvent, Event
14
+ from klaude_code.core.executor import Executor, LLMClients
15
+ from klaude_code.core.tool import SkillLoader, SkillTool
16
+ from klaude_code.protocol import events, op
17
+ from klaude_code.protocol.model import UserInputPayload
18
+ from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
22
19
  from klaude_code.trace import DebugType, log, set_debug_logging
23
- from klaude_code.ui.base.progress_bar import OSC94States, emit_osc94
24
- from klaude_code.ui.base.terminal_color import is_light_terminal_background
25
- from klaude_code.ui.base.terminal_control import (
26
- install_sigint_double_press_exit,
27
- start_esc_interrupt_monitor,
28
- )
29
- from klaude_code.ui.repl import build_repl_status_snapshot
30
- from klaude_code.ui.repl.input import REPLStatusSnapshot
20
+ from klaude_code.ui.modes.repl import build_repl_status_snapshot
21
+ from klaude_code.ui.modes.repl.input_prompt_toolkit import REPLStatusSnapshot
22
+ from klaude_code.ui.terminal.color import is_light_terminal_background
23
+ from klaude_code.ui.terminal.control import install_sigint_double_press_exit, start_esc_interrupt_monitor
24
+ from klaude_code.ui.terminal.progress_bar import OSC94States, emit_osc94
31
25
  from klaude_code.version import get_update_message
32
26
 
33
27
 
@@ -52,7 +46,12 @@ def _parse_debug_filters(raw: str | None) -> set[DebugType] | None:
52
46
  filters.add(DebugType(normalized))
53
47
  except ValueError: # pragma: no cover - user input validation
54
48
  valid_options = ", ".join(dt.value for dt in DebugType)
55
- log((f"Invalid debug filter '{normalized}'. Valid options: {valid_options}", "red"))
49
+ log(
50
+ (
51
+ f"Invalid debug filter '{normalized}'. Valid options: {valid_options}",
52
+ "red",
53
+ )
54
+ )
56
55
  raise typer.Exit(2) from None
57
56
  return filters or None
58
57
 
@@ -72,6 +71,7 @@ class AppInitConfig:
72
71
  vanilla: bool
73
72
  is_exec_mode: bool = False
74
73
  debug_filters: set[DebugType] | None = None
74
+ stream_json: bool = False
75
75
 
76
76
 
77
77
  @dataclass
@@ -81,7 +81,7 @@ class AppComponents:
81
81
  config: Config
82
82
  executor: Executor
83
83
  executor_task: asyncio.Task[None]
84
- event_queue: asyncio.Queue[Event]
84
+ event_queue: asyncio.Queue[events.Event]
85
85
  display: ui.DisplayABC
86
86
  display_task: asyncio.Task[None]
87
87
  theme: str | None
@@ -110,7 +110,12 @@ async def initialize_app_components(init_config: AppInitConfig) -> AppComponents
110
110
  )
111
111
  except ValueError as exc:
112
112
  if init_config.model:
113
- log((f"Error: model '{init_config.model}' is not defined in the config", "red"))
113
+ log(
114
+ (
115
+ f"Error: model '{init_config.model}' is not defined in the config",
116
+ "red",
117
+ )
118
+ )
114
119
  log(("Hint: run `klaude list` to view available models", "yellow"))
115
120
  else:
116
121
  log((f"Error: failed to load the default model configuration: {exc}", "red"))
@@ -119,7 +124,7 @@ async def initialize_app_components(init_config: AppInitConfig) -> AppComponents
119
124
  model_profile_provider = VanillaModelProfileProvider() if init_config.vanilla else DefaultModelProfileProvider()
120
125
 
121
126
  # Create event queue for communication between executor and UI
122
- event_queue: asyncio.Queue[Event] = asyncio.Queue()
127
+ event_queue: asyncio.Queue[events.Event] = asyncio.Queue()
123
128
 
124
129
  # Create executor with the LLM client
125
130
  executor = Executor(
@@ -140,15 +145,12 @@ async def initialize_app_components(init_config: AppInitConfig) -> AppComponents
140
145
  elif detected is False:
141
146
  theme = "dark"
142
147
 
143
- # Set up UI components
148
+ # Set up UI components using factory functions
144
149
  display: ui.DisplayABC
145
150
  if init_config.is_exec_mode:
146
- # Use ExecDisplay for exec mode - only shows task results
147
- display = ui.ExecDisplay()
151
+ display = ui.create_exec_display(debug=init_config.debug, stream_json=init_config.stream_json)
148
152
  else:
149
- # Use REPLDisplay for interactive mode
150
- repl_display = ui.REPLDisplay(theme=theme)
151
- display = repl_display if not init_config.debug else ui.DebugEventDisplay(repl_display)
153
+ display = ui.create_default_display(debug=init_config.debug, theme=theme)
152
154
 
153
155
  # Start UI display task
154
156
  display_task = asyncio.create_task(display.consume_event_loop(event_queue))
@@ -172,7 +174,7 @@ async def cleanup_app_components(components: AppComponents) -> None:
172
174
  components.executor_task.cancel()
173
175
 
174
176
  # Signal UI to stop
175
- await components.event_queue.put(EndEvent())
177
+ await components.event_queue.put(events.EndEvent())
176
178
  await components.display_task
177
179
  finally:
178
180
  # Always attempt to clear Ghostty progress bar and restore cursor visibility
@@ -214,15 +216,13 @@ async def run_exec(init_config: AppInitConfig, input_content: str) -> None:
214
216
  session_id = uuid.uuid4().hex
215
217
 
216
218
  # Init Agent
217
- init_id = await components.executor.submit(op.InitAgentOperation(session_id=session_id))
218
- await components.executor.wait_for_completion(init_id)
219
+ await components.executor.submit_and_wait(op.InitAgentOperation(session_id=session_id))
219
220
  await components.event_queue.join()
220
221
 
221
222
  # Submit the input content directly
222
- submission_id = await components.executor.submit(
223
- op.UserInputOperation(content=input_content, session_id=session_id)
223
+ await components.executor.submit_and_wait(
224
+ op.UserInputOperation(input=UserInputPayload(text=input_content), session_id=session_id)
224
225
  )
225
- await components.executor.wait_for_completion(submission_id)
226
226
 
227
227
  except KeyboardInterrupt:
228
228
  await _handle_keyboard_interrupt(components.executor)
@@ -284,25 +284,24 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
284
284
 
285
285
  try:
286
286
  # Init Agent
287
- init_id = await components.executor.submit(op.InitAgentOperation(session_id=session_id))
288
- await components.executor.wait_for_completion(init_id)
287
+ await components.executor.submit_and_wait(op.InitAgentOperation(session_id=session_id))
289
288
  await components.event_queue.join()
290
289
  # Input
291
290
  await input_provider.start()
292
291
  async for user_input in input_provider.iter_inputs():
293
292
  # Handle special commands
294
- if user_input.strip().lower() in {"exit", ":q", "quit"}:
293
+ if user_input.text.strip().lower() in {"exit", ":q", "quit"}:
295
294
  break
296
- elif user_input.strip() == "":
295
+ elif user_input.text.strip() == "":
297
296
  continue
298
- # Submit user input operation
297
+ # Submit user input operation - directly use the payload from iter_inputs
299
298
  submission_id = await components.executor.submit(
300
- op.UserInputOperation(content=user_input, session_id=session_id)
299
+ op.UserInputOperation(input=user_input, session_id=session_id)
301
300
  )
302
301
  # If it's an interactive command (e.g., /model), avoid starting the ESC monitor
303
302
  # to prevent TTY conflicts with interactive prompts (questionary/prompt_toolkit).
304
- if is_interactive_command(user_input):
305
- await components.executor.wait_for_completion(submission_id)
303
+ if has_interactive_command(user_input.text):
304
+ await components.executor.wait_for(submission_id)
306
305
  else:
307
306
  # Esc monitor for long-running, interruptible operations
308
307
  async def _on_esc_interrupt() -> None:
@@ -311,7 +310,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
311
310
  stop_event, esc_task = start_esc_interrupt_monitor(_on_esc_interrupt)
312
311
  # Wait for this specific task to complete before accepting next input
313
312
  try:
314
- await components.executor.wait_for_completion(submission_id)
313
+ await components.executor.wait_for(submission_id)
315
314
  finally:
316
315
  # Stop ESC monitor and wait for it to finish cleaning up TTY
317
316
  stop_event.set()
@@ -1,5 +1,5 @@
1
1
  from .clear_cmd import ClearCommand
2
- from .command_abc import CommandABC, CommandResult
2
+ from .command_abc import CommandABC, CommandResult, InputAction, InputActionType
3
3
  from .diff_cmd import DiffCommand
4
4
  from .export_cmd import ExportCommand
5
5
  from .help_cmd import HelpCommand
@@ -10,9 +10,9 @@ from .model_cmd import ModelCommand
10
10
  from .refresh_cmd import RefreshTerminalCommand
11
11
  from .registry import (
12
12
  dispatch_command,
13
- get_command_names,
14
13
  get_commands,
15
- is_interactive_command,
14
+ has_interactive_command,
15
+ is_slash_command_name,
16
16
  load_prompt_commands,
17
17
  register_command,
18
18
  )
@@ -32,8 +32,10 @@ __all__ = [
32
32
  "register_command",
33
33
  "CommandABC",
34
34
  "CommandResult",
35
+ "InputAction",
36
+ "InputActionType",
35
37
  "dispatch_command",
36
38
  "get_commands",
37
- "get_command_names",
38
- "is_interactive_command",
39
+ "is_slash_command_name",
40
+ "has_interactive_command",
39
41
  ]
@@ -1,10 +1,7 @@
1
- from klaude_code.command.command_abc import CommandABC, CommandResult
1
+ from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
2
2
  from klaude_code.command.registry import register_command
3
- from klaude_code.core import Agent
4
- from klaude_code.protocol.commands import CommandName
5
- from klaude_code.protocol.events import DeveloperMessageEvent
6
- from klaude_code.protocol.model import CommandOutput, DeveloperMessageItem
7
- from klaude_code.session.session import Session
3
+ from klaude_code.core.agent import Agent
4
+ from klaude_code.protocol import commands
8
5
 
9
6
 
10
7
  @register_command
@@ -12,32 +9,12 @@ class ClearCommand(CommandABC):
12
9
  """Clear current session and start a new conversation"""
13
10
 
14
11
  @property
15
- def name(self) -> CommandName:
16
- return CommandName.CLEAR
12
+ def name(self) -> commands.CommandName:
13
+ return commands.CommandName.CLEAR
17
14
 
18
15
  @property
19
16
  def summary(self) -> str:
20
17
  return "Clear conversation history and free up context"
21
18
 
22
19
  async def run(self, raw: str, agent: Agent) -> CommandResult:
23
- # Create a new session instance to replace the current one
24
- new_session = Session(work_dir=agent.session.work_dir)
25
- new_session.model_name = agent.session.model_name
26
-
27
- # Replace the agent's session with the new one
28
- agent.session = new_session
29
-
30
- # Save the new session
31
- agent.session.save()
32
-
33
- return CommandResult(
34
- events=[
35
- DeveloperMessageEvent(
36
- session_id=agent.session.id,
37
- item=DeveloperMessageItem(
38
- content="started new conversation",
39
- command_output=CommandOutput(command_name=self.name),
40
- ),
41
- ),
42
- ]
43
- )
20
+ return CommandResult(actions=[InputAction.clear()])
@@ -1,19 +1,55 @@
1
1
  from abc import ABC, abstractmethod
2
+ from enum import Enum
2
3
 
3
4
  from pydantic import BaseModel
4
5
 
5
- from klaude_code.core import Agent
6
- from klaude_code.protocol.commands import CommandName
7
- from klaude_code.protocol.events import DeveloperMessageEvent, ReplayHistoryEvent, WelcomeEvent
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)
8
43
 
9
44
 
10
45
  class CommandResult(BaseModel):
11
46
  """Result of a command execution."""
12
47
 
13
- agent_input: str | None = None # Input to be submitted to agent, or None if no input needed
14
- events: list[DeveloperMessageEvent | WelcomeEvent | ReplayHistoryEvent] | None = (
15
- None # List of UI events to display immediately
16
- )
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
17
53
 
18
54
 
19
55
  class CommandABC(ABC):
@@ -21,7 +57,7 @@ class CommandABC(ABC):
21
57
 
22
58
  @property
23
59
  @abstractmethod
24
- def name(self) -> CommandName | str:
60
+ def name(self) -> commands.CommandName | str:
25
61
  """Command name without the leading slash."""
26
62
  pass
27
63
 
@@ -3,10 +3,8 @@ from pathlib import Path
3
3
 
4
4
  from klaude_code.command.command_abc import CommandABC, CommandResult
5
5
  from klaude_code.command.registry import register_command
6
- from klaude_code.core import Agent
7
- from klaude_code.protocol.commands import CommandName
8
- from klaude_code.protocol.events import DeveloperMessageEvent
9
- from klaude_code.protocol.model import CommandOutput, DeveloperMessageItem
6
+ from klaude_code.core.agent import Agent
7
+ from klaude_code.protocol import commands, events, model
10
8
 
11
9
 
12
10
  @register_command
@@ -14,8 +12,8 @@ class DiffCommand(CommandABC):
14
12
  """Show git diff for the current repository."""
15
13
 
16
14
  @property
17
- def name(self) -> CommandName:
18
- return CommandName.DIFF
15
+ def name(self) -> commands.CommandName:
16
+ return commands.CommandName.DIFF
19
17
 
20
18
  @property
21
19
  def summary(self) -> str:
@@ -34,28 +32,32 @@ class DiffCommand(CommandABC):
34
32
 
35
33
  if git_check.returncode != 0:
36
34
  # Not in a git repository
37
- event = DeveloperMessageEvent(
35
+ event = events.DeveloperMessageEvent(
38
36
  session_id=agent.session.id,
39
- item=DeveloperMessageItem(
37
+ item=model.DeveloperMessageItem(
40
38
  content="No in a git repo",
41
- command_output=CommandOutput(command_name=self.name, is_error=True),
39
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
42
40
  ),
43
41
  )
44
42
  return CommandResult(events=[event])
45
43
 
46
44
  # Run git diff in current directory
47
45
  result = subprocess.run(
48
- ["git", "diff", "HEAD"], cwd=Path.cwd(), capture_output=True, text=True, timeout=10.0
46
+ ["git", "diff", "HEAD"],
47
+ cwd=Path.cwd(),
48
+ capture_output=True,
49
+ text=True,
50
+ timeout=10.0,
49
51
  )
50
52
 
51
53
  if result.returncode != 0:
52
54
  # Git command failed
53
55
  error_msg = result.stderr.strip() or "git diff command failed"
54
- event = DeveloperMessageEvent(
56
+ event = events.DeveloperMessageEvent(
55
57
  session_id=agent.session.id,
56
- item=DeveloperMessageItem(
58
+ item=model.DeveloperMessageItem(
57
59
  content=f"Error: {error_msg}",
58
- command_output=CommandOutput(command_name=self.name, is_error=True),
60
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
59
61
  ),
60
62
  )
61
63
  return CommandResult(events=[event])
@@ -88,45 +90,49 @@ class DiffCommand(CommandABC):
88
90
 
89
91
  if not output_parts:
90
92
  # No changes and no untracked files
91
- event = DeveloperMessageEvent(
93
+ event = events.DeveloperMessageEvent(
92
94
  session_id=agent.session.id,
93
- item=DeveloperMessageItem(content="", command_output=CommandOutput(command_name=self.name)),
95
+ item=model.DeveloperMessageItem(
96
+ content="", command_output=model.CommandOutput(command_name=self.name)
97
+ ),
94
98
  )
95
99
  return CommandResult(events=[event])
96
100
 
97
101
  # Has changes or untracked files
98
102
  combined_output = "\n\n".join(output_parts)
99
- event = DeveloperMessageEvent(
103
+ event = events.DeveloperMessageEvent(
100
104
  session_id=agent.session.id,
101
- item=DeveloperMessageItem(
102
- content=combined_output, command_output=CommandOutput(command_name=self.name)
105
+ item=model.DeveloperMessageItem(
106
+ content=combined_output,
107
+ command_output=model.CommandOutput(command_name=self.name),
103
108
  ),
104
109
  )
105
110
  return CommandResult(events=[event])
106
111
 
107
112
  except subprocess.TimeoutExpired:
108
- event = DeveloperMessageEvent(
113
+ event = events.DeveloperMessageEvent(
109
114
  session_id=agent.session.id,
110
- item=DeveloperMessageItem(
115
+ item=model.DeveloperMessageItem(
111
116
  content="Error: git diff command timeout",
112
- command_output=CommandOutput(command_name=self.name, is_error=True),
117
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
113
118
  ),
114
119
  )
115
120
  return CommandResult(events=[event])
116
121
  except FileNotFoundError:
117
- event = DeveloperMessageEvent(
122
+ event = events.DeveloperMessageEvent(
118
123
  session_id=agent.session.id,
119
- item=DeveloperMessageItem(
124
+ item=model.DeveloperMessageItem(
120
125
  content="Error: git command not found",
121
- command_output=CommandOutput(command_name=self.name, is_error=True),
126
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
122
127
  ),
123
128
  )
124
129
  return CommandResult(events=[event])
125
130
  except Exception as e:
126
- event = DeveloperMessageEvent(
131
+ event = events.DeveloperMessageEvent(
127
132
  session_id=agent.session.id,
128
- item=DeveloperMessageItem(
129
- content=f"Error:{e}", command_output=CommandOutput(command_name=self.name, is_error=True)
133
+ item=model.DeveloperMessageItem(
134
+ content=f"Error:{e}",
135
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
130
136
  ),
131
137
  )
132
138
  return CommandResult(events=[event])
@@ -5,10 +5,8 @@ from pathlib import Path
5
5
 
6
6
  from klaude_code.command.command_abc import CommandABC, CommandResult
7
7
  from klaude_code.command.registry import register_command
8
- from klaude_code.core import Agent
9
- from klaude_code.protocol.commands import CommandName
10
- from klaude_code.protocol.events import DeveloperMessageEvent
11
- from klaude_code.protocol.model import CommandOutput, DeveloperMessageItem
8
+ from klaude_code.core.agent import Agent
9
+ from klaude_code.protocol import commands, events, model
12
10
  from klaude_code.session.export import build_export_html, get_default_export_path
13
11
 
14
12
 
@@ -17,8 +15,8 @@ class ExportCommand(CommandABC):
17
15
  """Export the current session into a standalone HTML transcript."""
18
16
 
19
17
  @property
20
- def name(self) -> CommandName:
21
- return CommandName.EXPORT
18
+ def name(self) -> commands.CommandName:
19
+ return commands.CommandName.EXPORT
22
20
 
23
21
  @property
24
22
  def summary(self) -> str:
@@ -39,29 +37,23 @@ class ExportCommand(CommandABC):
39
37
  output_path.parent.mkdir(parents=True, exist_ok=True)
40
38
  output_path.write_text(html_doc, encoding="utf-8")
41
39
  self._open_file(output_path)
42
- return CommandResult(
43
- events=[
44
- DeveloperMessageEvent(
45
- session_id=agent.session.id,
46
- item=DeveloperMessageItem(
47
- content=f"Session exported and opened: {output_path}",
48
- command_output=CommandOutput(command_name=self.name),
49
- ),
50
- )
51
- ]
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
+ ),
52
46
  )
47
+ return CommandResult(events=[event])
53
48
  except Exception as exc: # pragma: no cover - safeguard for unexpected errors
54
- return CommandResult(
55
- events=[
56
- DeveloperMessageEvent(
57
- session_id=agent.session.id,
58
- item=DeveloperMessageItem(
59
- content=f"Failed to export session: {exc}",
60
- command_output=CommandOutput(command_name=self.name, is_error=True),
61
- ),
62
- )
63
- ]
49
+ event = events.DeveloperMessageEvent(
50
+ session_id=agent.session.id,
51
+ item=model.DeveloperMessageItem(
52
+ content=f"Failed to export session: {exc}",
53
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
54
+ ),
64
55
  )
56
+ return CommandResult(events=[event])
65
57
 
66
58
  def _resolve_output_path(self, raw: str, agent: Agent) -> Path:
67
59
  trimmed = raw.strip()
@@ -1,9 +1,7 @@
1
1
  from klaude_code.command.command_abc import CommandABC, CommandResult
2
2
  from klaude_code.command.registry import register_command
3
- from klaude_code.core import Agent
4
- from klaude_code.protocol.commands import CommandName
5
- from klaude_code.protocol.events import DeveloperMessageEvent
6
- from klaude_code.protocol.model import CommandOutput, DeveloperMessageItem
3
+ from klaude_code.core.agent import Agent
4
+ from klaude_code.protocol import commands, events, model
7
5
 
8
6
 
9
7
  @register_command
@@ -11,8 +9,8 @@ class HelpCommand(CommandABC):
11
9
  """Display help information for all available slash commands."""
12
10
 
13
11
  @property
14
- def name(self) -> CommandName:
15
- return CommandName.HELP
12
+ def name(self) -> commands.CommandName:
13
+ return commands.CommandName.HELP
16
14
 
17
15
  @property
18
16
  def summary(self) -> str:
@@ -25,6 +23,7 @@ Usage:
25
23
  [b]@[/b] to mention file
26
24
  [b]esc[/b] to interrupt agent task
27
25
  [b]shift-enter[/b] or [b]ctrl-j[/b] for new line
26
+ [b]ctrl-v[/b] for pasting image
28
27
  [b]--continue[/b] or [b]--resume[/b] to continue an old session
29
28
  [b]--select-model[/b] to switch model
30
29
 
@@ -41,9 +40,12 @@ Available slash commands:"""
41
40
  additional_instructions = " \\[additional instructions]" if cmd_obj.support_addition_params else ""
42
41
  lines.append(f" [b]/{cmd_name}[/b]{additional_instructions} — {cmd_obj.summary}")
43
42
 
44
- event = DeveloperMessageEvent(
43
+ event = events.DeveloperMessageEvent(
45
44
  session_id=agent.session.id,
46
- item=DeveloperMessageItem(content="\n".join(lines), command_output=CommandOutput(command_name=self.name)),
45
+ item=model.DeveloperMessageItem(
46
+ content="\n".join(lines),
47
+ command_output=model.CommandOutput(command_name=self.name),
48
+ ),
47
49
  )
48
50
 
49
51
  return CommandResult(events=[event])