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.
- klaude_code/cli/main.py +9 -4
- klaude_code/cli/runtime.py +42 -43
- klaude_code/command/__init__.py +7 -5
- klaude_code/command/clear_cmd.py +6 -29
- klaude_code/command/command_abc.py +44 -8
- klaude_code/command/diff_cmd.py +33 -27
- klaude_code/command/export_cmd.py +18 -26
- klaude_code/command/help_cmd.py +10 -8
- klaude_code/command/model_cmd.py +11 -40
- klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
- klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
- klaude_code/command/prompt-init.md +2 -5
- klaude_code/command/prompt_command.py +6 -6
- klaude_code/command/refresh_cmd.py +4 -5
- klaude_code/command/registry.py +16 -19
- klaude_code/command/terminal_setup_cmd.py +12 -11
- klaude_code/config/__init__.py +4 -0
- klaude_code/config/config.py +25 -26
- klaude_code/config/list_model.py +8 -3
- klaude_code/config/select_model.py +1 -1
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/__init__.py +0 -3
- klaude_code/core/agent.py +25 -50
- klaude_code/core/executor.py +268 -101
- klaude_code/core/prompt.py +12 -12
- klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
- klaude_code/core/reminders.py +76 -95
- klaude_code/core/task.py +21 -14
- klaude_code/core/tool/__init__.py +45 -11
- klaude_code/core/tool/file/apply_patch.py +5 -1
- klaude_code/core/tool/file/apply_patch_tool.py +11 -13
- klaude_code/core/tool/file/edit_tool.py +27 -23
- klaude_code/core/tool/file/multi_edit_tool.py +15 -17
- klaude_code/core/tool/file/read_tool.py +41 -36
- klaude_code/core/tool/file/write_tool.py +13 -15
- klaude_code/core/tool/memory/memory_tool.py +85 -68
- klaude_code/core/tool/memory/skill_tool.py +10 -12
- klaude_code/core/tool/shell/bash_tool.py +24 -22
- klaude_code/core/tool/shell/command_safety.py +12 -1
- klaude_code/core/tool/sub_agent_tool.py +11 -12
- klaude_code/core/tool/todo/todo_write_tool.py +21 -28
- klaude_code/core/tool/todo/update_plan_tool.py +14 -24
- klaude_code/core/tool/tool_abc.py +3 -4
- klaude_code/core/tool/tool_context.py +7 -7
- klaude_code/core/tool/tool_registry.py +30 -47
- klaude_code/core/tool/tool_runner.py +35 -43
- klaude_code/core/tool/truncation.py +14 -20
- klaude_code/core/tool/web/mermaid_tool.py +12 -14
- klaude_code/core/tool/web/web_fetch_tool.py +15 -17
- klaude_code/core/turn.py +19 -7
- klaude_code/llm/__init__.py +3 -4
- klaude_code/llm/anthropic/client.py +30 -46
- klaude_code/llm/anthropic/input.py +4 -11
- klaude_code/llm/client.py +29 -8
- klaude_code/llm/input_common.py +66 -36
- klaude_code/llm/openai_compatible/client.py +42 -84
- klaude_code/llm/openai_compatible/input.py +11 -16
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
- klaude_code/llm/openrouter/client.py +40 -289
- klaude_code/llm/openrouter/input.py +13 -35
- klaude_code/llm/openrouter/reasoning_handler.py +209 -0
- klaude_code/llm/registry.py +5 -75
- klaude_code/llm/responses/client.py +34 -55
- klaude_code/llm/responses/input.py +24 -26
- klaude_code/llm/usage.py +109 -0
- klaude_code/protocol/__init__.py +4 -0
- klaude_code/protocol/events.py +3 -2
- klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
- klaude_code/protocol/model.py +49 -4
- klaude_code/protocol/op.py +18 -16
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/{core → protocol}/sub_agent.py +7 -0
- klaude_code/session/export.py +150 -70
- klaude_code/session/session.py +28 -14
- klaude_code/session/templates/export_session.html +180 -42
- klaude_code/trace/__init__.py +2 -2
- klaude_code/trace/log.py +11 -5
- klaude_code/ui/__init__.py +91 -8
- 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/modes/__init__.py +1 -0
- klaude_code/ui/modes/debug/__init__.py +1 -0
- klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
- klaude_code/ui/modes/exec/__init__.py +1 -0
- klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
- klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
- 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/{repl → modes/repl}/renderer.py +109 -132
- klaude_code/ui/renderers/assistant.py +21 -0
- klaude_code/ui/renderers/common.py +0 -16
- klaude_code/ui/renderers/developer.py +18 -18
- klaude_code/ui/renderers/diffs.py +36 -14
- klaude_code/ui/renderers/errors.py +1 -1
- klaude_code/ui/renderers/metadata.py +50 -27
- klaude_code/ui/renderers/sub_agent.py +43 -9
- klaude_code/ui/renderers/thinking.py +33 -1
- klaude_code/ui/renderers/tools.py +212 -20
- klaude_code/ui/renderers/user_input.py +19 -23
- klaude_code/ui/rich/__init__.py +1 -0
- klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
- klaude_code/ui/{renderers → rich}/status.py +29 -18
- klaude_code/ui/{base → rich}/theme.py +8 -2
- klaude_code/ui/terminal/__init__.py +1 -0
- klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
- klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
- klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
- klaude_code/ui/utils/__init__.py +1 -0
- klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
- klaude_code-1.2.3.dist-info/RECORD +161 -0
- klaude_code/core/clipboard_manifest.py +0 -124
- klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
- klaude_code/ui/base/__init__.py +0 -1
- klaude_code/ui/base/display_abc.py +0 -36
- klaude_code/ui/base/input_abc.py +0 -20
- klaude_code/ui/repl/display.py +0 -36
- klaude_code/ui/repl/event_handler.py +0 -247
- klaude_code/ui/repl/input.py +0 -773
- klaude_code/ui/rich_ext/__init__.py +0 -1
- klaude_code-1.2.1.dist-info/RECORD +0 -151
- /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
- /klaude_code/ui/{base → core}/stage_manager.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
- /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
- /klaude_code/ui/{base → utils}/debouncer.py +0 -0
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
- {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.
|
|
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(
|
klaude_code/cli/runtime.py
CHANGED
|
@@ -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
|
|
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.
|
|
17
|
-
from klaude_code.
|
|
18
|
-
from klaude_code.
|
|
19
|
-
from klaude_code.
|
|
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.
|
|
24
|
-
from klaude_code.ui.
|
|
25
|
-
from klaude_code.ui.
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
147
|
-
display = ui.ExecDisplay()
|
|
151
|
+
display = ui.create_exec_display(debug=init_config.debug, stream_json=init_config.stream_json)
|
|
148
152
|
else:
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
op.UserInputOperation(
|
|
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
|
-
|
|
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(
|
|
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
|
|
305
|
-
await components.executor.
|
|
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.
|
|
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()
|
klaude_code/command/__init__.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
"
|
|
38
|
-
"
|
|
39
|
+
"is_slash_command_name",
|
|
40
|
+
"has_interactive_command",
|
|
39
41
|
]
|
klaude_code/command/clear_cmd.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
7
|
-
from klaude_code.protocol
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
None
|
|
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
|
|
klaude_code/command/diff_cmd.py
CHANGED
|
@@ -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
|
|
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"],
|
|
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(
|
|
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,
|
|
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}",
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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()
|
klaude_code/command/help_cmd.py
CHANGED
|
@@ -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
|
|
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(
|
|
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])
|