klaude-code 2.0.2__py3-none-any.whl → 2.1.1__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/app/__init__.py +12 -0
- klaude_code/app/runtime.py +215 -0
- klaude_code/cli/auth_cmd.py +2 -2
- klaude_code/cli/config_cmd.py +2 -2
- klaude_code/cli/cost_cmd.py +1 -1
- klaude_code/cli/debug.py +12 -36
- klaude_code/cli/list_model.py +3 -3
- klaude_code/cli/main.py +17 -60
- klaude_code/cli/self_update.py +2 -187
- klaude_code/cli/session_cmd.py +2 -2
- klaude_code/config/config.py +1 -1
- klaude_code/config/select_model.py +1 -1
- klaude_code/const.py +9 -1
- klaude_code/core/agent.py +9 -62
- klaude_code/core/agent_profile.py +291 -0
- klaude_code/core/executor.py +335 -230
- klaude_code/core/manager/llm_clients_builder.py +1 -1
- klaude_code/core/manager/sub_agent_manager.py +16 -29
- klaude_code/core/reminders.py +84 -103
- klaude_code/core/task.py +12 -20
- klaude_code/core/tool/__init__.py +5 -19
- klaude_code/core/tool/context.py +84 -0
- klaude_code/core/tool/file/apply_patch_tool.py +18 -21
- klaude_code/core/tool/file/edit_tool.py +39 -42
- klaude_code/core/tool/file/read_tool.py +14 -9
- klaude_code/core/tool/file/write_tool.py +12 -13
- klaude_code/core/tool/report_back_tool.py +4 -1
- klaude_code/core/tool/shell/bash_tool.py +6 -11
- klaude_code/core/tool/sub_agent_tool.py +8 -7
- klaude_code/core/tool/todo/todo_write_tool.py +3 -9
- klaude_code/core/tool/todo/update_plan_tool.py +3 -5
- klaude_code/core/tool/tool_abc.py +2 -1
- klaude_code/core/tool/tool_registry.py +2 -33
- klaude_code/core/tool/tool_runner.py +13 -10
- klaude_code/core/tool/web/mermaid_tool.py +3 -1
- klaude_code/core/tool/web/web_fetch_tool.py +5 -3
- klaude_code/core/tool/web/web_search_tool.py +5 -3
- klaude_code/core/turn.py +87 -30
- klaude_code/llm/anthropic/client.py +1 -1
- klaude_code/llm/bedrock/client.py +1 -1
- klaude_code/llm/claude/client.py +1 -1
- klaude_code/llm/codex/client.py +1 -1
- klaude_code/llm/google/client.py +1 -1
- klaude_code/llm/openai_compatible/client.py +1 -1
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +1 -1
- klaude_code/llm/openrouter/client.py +1 -1
- klaude_code/llm/openrouter/reasoning.py +1 -1
- klaude_code/llm/responses/client.py +1 -1
- klaude_code/protocol/commands.py +1 -0
- klaude_code/protocol/events/__init__.py +57 -0
- klaude_code/protocol/events/base.py +18 -0
- klaude_code/protocol/events/chat.py +20 -0
- klaude_code/protocol/events/lifecycle.py +22 -0
- klaude_code/protocol/events/metadata.py +15 -0
- klaude_code/protocol/events/streaming.py +43 -0
- klaude_code/protocol/events/system.py +53 -0
- klaude_code/protocol/events/tools.py +27 -0
- klaude_code/protocol/op.py +5 -0
- klaude_code/protocol/tools.py +0 -1
- klaude_code/session/session.py +6 -7
- klaude_code/skill/assets/create-plan/SKILL.md +76 -0
- klaude_code/skill/loader.py +32 -88
- klaude_code/skill/manager.py +38 -0
- klaude_code/skill/system_skills.py +1 -1
- klaude_code/tui/__init__.py +8 -0
- klaude_code/{command → tui/command}/__init__.py +3 -0
- klaude_code/{command → tui/command}/clear_cmd.py +2 -1
- klaude_code/tui/command/copy_cmd.py +53 -0
- klaude_code/{command → tui/command}/debug_cmd.py +3 -2
- klaude_code/{command → tui/command}/export_cmd.py +2 -1
- klaude_code/{command → tui/command}/export_online_cmd.py +2 -1
- klaude_code/{command → tui/command}/fork_session_cmd.py +4 -3
- klaude_code/{command → tui/command}/help_cmd.py +2 -1
- klaude_code/{command → tui/command}/model_cmd.py +4 -3
- klaude_code/{command → tui/command}/model_select.py +2 -2
- klaude_code/{command → tui/command}/prompt_command.py +4 -3
- klaude_code/{command → tui/command}/refresh_cmd.py +3 -1
- klaude_code/{command → tui/command}/registry.py +6 -5
- klaude_code/{command → tui/command}/release_notes_cmd.py +2 -1
- klaude_code/{command → tui/command}/resume_cmd.py +4 -3
- klaude_code/{command → tui/command}/status_cmd.py +2 -1
- klaude_code/{command → tui/command}/terminal_setup_cmd.py +2 -1
- klaude_code/{command → tui/command}/thinking_cmd.py +3 -2
- klaude_code/tui/commands.py +164 -0
- klaude_code/{ui/renderers → tui/components}/assistant.py +3 -3
- klaude_code/{ui/renderers → tui/components}/bash_syntax.py +2 -2
- klaude_code/{ui/renderers → tui/components}/common.py +1 -1
- klaude_code/{ui/renderers → tui/components}/developer.py +4 -4
- klaude_code/{ui/renderers → tui/components}/diffs.py +2 -2
- klaude_code/{ui/renderers → tui/components}/errors.py +2 -2
- klaude_code/{ui/renderers → tui/components}/metadata.py +7 -7
- klaude_code/{ui → tui/components}/rich/markdown.py +9 -23
- klaude_code/{ui → tui/components}/rich/status.py +2 -2
- klaude_code/{ui → tui/components}/rich/theme.py +3 -1
- klaude_code/{ui/renderers → tui/components}/sub_agent.py +23 -43
- klaude_code/{ui/renderers → tui/components}/thinking.py +3 -3
- klaude_code/{ui/renderers → tui/components}/tools.py +13 -17
- klaude_code/{ui/renderers → tui/components}/user_input.py +3 -20
- klaude_code/tui/display.py +85 -0
- klaude_code/{ui/modes/repl → tui/input}/__init__.py +1 -1
- klaude_code/{ui/modes/repl → tui/input}/completers.py +1 -1
- klaude_code/{ui/modes/repl/input_prompt_toolkit.py → tui/input/prompt_toolkit.py} +6 -6
- klaude_code/tui/machine.py +608 -0
- klaude_code/tui/renderer.py +707 -0
- klaude_code/tui/runner.py +321 -0
- klaude_code/tui/terminal/__init__.py +56 -0
- klaude_code/{ui → tui}/terminal/color.py +1 -1
- klaude_code/{ui → tui}/terminal/control.py +1 -1
- klaude_code/{ui → tui}/terminal/notifier.py +1 -1
- klaude_code/ui/__init__.py +6 -50
- klaude_code/ui/core/display.py +3 -3
- klaude_code/ui/core/input.py +2 -1
- klaude_code/ui/{modes/debug/display.py → debug_mode.py} +1 -1
- klaude_code/ui/{modes/exec/display.py → exec_mode.py} +0 -2
- klaude_code/ui/terminal/__init__.py +6 -54
- klaude_code/ui/terminal/title.py +31 -0
- klaude_code/update.py +163 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/METADATA +1 -1
- klaude_code-2.1.1.dist-info/RECORD +233 -0
- klaude_code/cli/runtime.py +0 -518
- klaude_code/core/prompt.py +0 -108
- klaude_code/core/tool/skill/skill_tool.md +0 -24
- klaude_code/core/tool/skill/skill_tool.py +0 -87
- klaude_code/core/tool/tool_context.py +0 -148
- klaude_code/protocol/events.py +0 -195
- klaude_code/skill/assets/dev-docs/SKILL.md +0 -108
- klaude_code/trace/__init__.py +0 -21
- klaude_code/ui/core/stage_manager.py +0 -48
- klaude_code/ui/modes/__init__.py +0 -1
- klaude_code/ui/modes/debug/__init__.py +0 -1
- klaude_code/ui/modes/exec/__init__.py +0 -1
- klaude_code/ui/modes/repl/display.py +0 -61
- klaude_code/ui/modes/repl/event_handler.py +0 -629
- klaude_code/ui/modes/repl/renderer.py +0 -464
- klaude_code/ui/renderers/__init__.py +0 -0
- klaude_code/ui/utils/__init__.py +0 -1
- klaude_code-2.0.2.dist-info/RECORD +0 -227
- /klaude_code/{trace/log.py → log.py} +0 -0
- /klaude_code/{command → tui/command}/command_abc.py +0 -0
- /klaude_code/{command → tui/command}/prompt-commit.md +0 -0
- /klaude_code/{command → tui/command}/prompt-init.md +0 -0
- /klaude_code/{core/tool/skill → tui/components}/__init__.py +0 -0
- /klaude_code/{ui/renderers → tui/components}/mermaid_viewer.py +0 -0
- /klaude_code/{ui → tui/components}/rich/__init__.py +0 -0
- /klaude_code/{ui → tui/components}/rich/cjk_wrap.py +0 -0
- /klaude_code/{ui → tui/components}/rich/code_panel.py +0 -0
- /klaude_code/{ui → tui/components}/rich/live.py +0 -0
- /klaude_code/{ui → tui/components}/rich/quote.py +0 -0
- /klaude_code/{ui → tui/components}/rich/searchable_text.py +0 -0
- /klaude_code/{ui/modes/repl → tui/input}/clipboard.py +0 -0
- /klaude_code/{ui/modes/repl → tui/input}/key_bindings.py +0 -0
- /klaude_code/{ui → tui}/terminal/image.py +0 -0
- /klaude_code/{ui → tui}/terminal/progress_bar.py +0 -0
- /klaude_code/{ui → tui}/terminal/selector.py +0 -0
- /klaude_code/ui/{utils/common.py → common.py} +0 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/WHEEL +0 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/entry_points.txt +0 -0
klaude_code/session/session.py
CHANGED
|
@@ -289,7 +289,7 @@ class Session(BaseModel):
|
|
|
289
289
|
return True
|
|
290
290
|
return isinstance(prev_item, (message.UserMessage, message.ToolResultMessage, message.DeveloperMessage))
|
|
291
291
|
|
|
292
|
-
def get_history_item(self) -> Iterable[events.
|
|
292
|
+
def get_history_item(self) -> Iterable[events.ReplayEventUnion]:
|
|
293
293
|
seen_sub_agent_sessions: set[str] = set()
|
|
294
294
|
prev_item: message.HistoryEvent | None = None
|
|
295
295
|
last_assistant_content: str = ""
|
|
@@ -314,8 +314,8 @@ class Session(BaseModel):
|
|
|
314
314
|
response_id=am.response_id,
|
|
315
315
|
session_id=self.id,
|
|
316
316
|
)
|
|
317
|
-
yield events.
|
|
318
|
-
thinking_text=thinking_text,
|
|
317
|
+
yield events.ResponseCompleteEvent(
|
|
318
|
+
thinking_text=thinking_text or None,
|
|
319
319
|
content=content,
|
|
320
320
|
response_id=am.response_id,
|
|
321
321
|
session_id=self.id,
|
|
@@ -350,8 +350,6 @@ class Session(BaseModel):
|
|
|
350
350
|
is_last_in_turn=is_last_in_turn,
|
|
351
351
|
)
|
|
352
352
|
yield from self._iter_sub_agent_history(tr, seen_sub_agent_sessions)
|
|
353
|
-
if tr.status == "aborted":
|
|
354
|
-
yield events.InterruptEvent(session_id=self.id)
|
|
355
353
|
case message.UserMessage() as um:
|
|
356
354
|
images = [part for part in um.parts if isinstance(part, message.ImageURLPart)]
|
|
357
355
|
yield events.UserMessageEvent(
|
|
@@ -360,7 +358,8 @@ class Session(BaseModel):
|
|
|
360
358
|
images=images or None,
|
|
361
359
|
)
|
|
362
360
|
case model.TaskMetadataItem() as mt:
|
|
363
|
-
|
|
361
|
+
if self.sub_agent_state is None:
|
|
362
|
+
yield events.TaskMetadataEvent(session_id=self.id, metadata=mt)
|
|
364
363
|
case message.DeveloperMessage() as dm:
|
|
365
364
|
yield events.DeveloperMessageEvent(session_id=self.id, item=dm)
|
|
366
365
|
case message.StreamErrorItem() as se:
|
|
@@ -377,7 +376,7 @@ class Session(BaseModel):
|
|
|
377
376
|
|
|
378
377
|
def _iter_sub_agent_history(
|
|
379
378
|
self, tool_result: message.ToolResultMessage, seen_sub_agent_sessions: set[str]
|
|
380
|
-
) -> Iterable[events.
|
|
379
|
+
) -> Iterable[events.ReplayEventUnion]:
|
|
381
380
|
ui_extra = tool_result.ui_extra
|
|
382
381
|
if not isinstance(ui_extra, model.SessionIdUIExtra):
|
|
383
382
|
return
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-plan
|
|
3
|
+
description: Create a concise plan. Use when a user explicitly asks for a plan related to a coding task.
|
|
4
|
+
metadata:
|
|
5
|
+
short-description: Create a plan
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Create Plan
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Turn a user prompt into a **single, actionable plan** delivered in the final assistant message.
|
|
13
|
+
|
|
14
|
+
## Minimal workflow
|
|
15
|
+
|
|
16
|
+
1. **Scan context quickly**
|
|
17
|
+
- Read `README.md` and any obvious docs (`docs/`, `CONTRIBUTING.md`, `ARCHITECTURE.md`).
|
|
18
|
+
- Skim relevant files (the ones most likely touched).
|
|
19
|
+
- Identify constraints (language, frameworks, CI/test commands, deployment shape).
|
|
20
|
+
|
|
21
|
+
2. **Ask follow-ups only if blocking**
|
|
22
|
+
- Ask **at most 1–2 questions**.
|
|
23
|
+
- Only ask if you cannot responsibly plan without the answer; prefer multiple-choice.
|
|
24
|
+
- If unsure but not blocked, make a reasonable assumption and proceed.
|
|
25
|
+
|
|
26
|
+
3. **Create a plan using the template below**
|
|
27
|
+
- Start with **1 short paragraph** describing the intent and approach.
|
|
28
|
+
- Clearly call out what is **in scope** and what is **not in scope** in short.
|
|
29
|
+
- Then provide a **small checklist** of action items (default 6–10 items).
|
|
30
|
+
- Each checklist item should be a concrete action and, when helpful, mention files/commands.
|
|
31
|
+
- **Make items atomic and ordered**: discovery → changes → tests → rollout.
|
|
32
|
+
- **Verb-first**: “Add…”, “Refactor…”, “Verify…”, “Ship…”.
|
|
33
|
+
- Include at least one item for **tests/validation** and one for **edge cases/risk** when applicable.
|
|
34
|
+
- If there are unknowns, include a tiny **Open questions** section (max 3).
|
|
35
|
+
|
|
36
|
+
4. **Write the plan to `plan.md` in the current working directory**
|
|
37
|
+
- Use the Write tool to save the plan to `./plan.md`
|
|
38
|
+
- If `plan.md` already exists, overwrite it with the new plan
|
|
39
|
+
|
|
40
|
+
5. **Do not preface the plan with meta explanations; output only the plan as per template**
|
|
41
|
+
|
|
42
|
+
## Plan template (follow exactly)
|
|
43
|
+
|
|
44
|
+
```markdown
|
|
45
|
+
# Plan
|
|
46
|
+
|
|
47
|
+
<1–3 sentences: what we’re doing, why, and the high-level approach.>
|
|
48
|
+
|
|
49
|
+
## Scope
|
|
50
|
+
- In:
|
|
51
|
+
- Out:
|
|
52
|
+
|
|
53
|
+
## Action items
|
|
54
|
+
[ ] <Step 1>
|
|
55
|
+
[ ] <Step 2>
|
|
56
|
+
[ ] <Step 3>
|
|
57
|
+
[ ] <Step 4>
|
|
58
|
+
[ ] <Step 5>
|
|
59
|
+
[ ] <Step 6>
|
|
60
|
+
|
|
61
|
+
## Open questions
|
|
62
|
+
- <Question 1>
|
|
63
|
+
- <Question 2>
|
|
64
|
+
- <Question 3>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Checklist item guidance
|
|
68
|
+
Good checklist items:
|
|
69
|
+
- Point to likely files/modules: src/..., app/..., services/...
|
|
70
|
+
- Name concrete validation: “Run npm test”, “Add unit tests for X”
|
|
71
|
+
- Include safe rollout when relevant: feature flag, migration plan, rollback note
|
|
72
|
+
|
|
73
|
+
Avoid:
|
|
74
|
+
- Vague steps (“handle backend”, “do auth”)
|
|
75
|
+
- Too many micro-steps
|
|
76
|
+
- Writing code snippets (keep the plan implementation-agnostic)
|
klaude_code/skill/loader.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import re
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
from typing import ClassVar
|
|
5
4
|
|
|
6
5
|
import yaml
|
|
7
6
|
|
|
8
|
-
from klaude_code.
|
|
7
|
+
from klaude_code.log import log_debug
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@dataclass
|
|
@@ -14,12 +13,12 @@ class Skill:
|
|
|
14
13
|
|
|
15
14
|
name: str # Skill identifier (lowercase-hyphen)
|
|
16
15
|
description: str # What the skill does and when to use it
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
location: str # Skill source: 'system', 'user', or 'project'
|
|
17
|
+
skill_path: Path
|
|
18
|
+
base_dir: Path
|
|
19
19
|
license: str | None = None
|
|
20
20
|
allowed_tools: list[str] | None = None
|
|
21
21
|
metadata: dict[str, str] | None = None
|
|
22
|
-
skill_path: Path | None = None
|
|
23
22
|
|
|
24
23
|
@property
|
|
25
24
|
def short_description(self) -> str:
|
|
@@ -31,17 +30,6 @@ class Skill:
|
|
|
31
30
|
return self.metadata["short-description"]
|
|
32
31
|
return self.description
|
|
33
32
|
|
|
34
|
-
def to_prompt(self) -> str:
|
|
35
|
-
"""Convert skill to prompt format for agent consumption"""
|
|
36
|
-
return f"""# Skill: {self.name}
|
|
37
|
-
|
|
38
|
-
{self.description}
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
{self.content}
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
33
|
|
|
46
34
|
class SkillLoader:
|
|
47
35
|
"""Load and manage Claude Skills from SKILL.md files"""
|
|
@@ -79,7 +67,6 @@ class SkillLoader:
|
|
|
79
67
|
|
|
80
68
|
# Parse YAML frontmatter
|
|
81
69
|
frontmatter: dict[str, object] = {}
|
|
82
|
-
markdown_content = content
|
|
83
70
|
|
|
84
71
|
if content.startswith("---"):
|
|
85
72
|
parts = content.split("---", 2)
|
|
@@ -87,7 +74,6 @@ class SkillLoader:
|
|
|
87
74
|
loaded: object = yaml.safe_load(parts[1])
|
|
88
75
|
if isinstance(loaded, dict):
|
|
89
76
|
frontmatter = dict(loaded) # type: ignore[arg-type]
|
|
90
|
-
markdown_content = parts[2].strip()
|
|
91
77
|
|
|
92
78
|
# Extract skill metadata
|
|
93
79
|
name = str(frontmatter.get("name", ""))
|
|
@@ -96,10 +82,6 @@ class SkillLoader:
|
|
|
96
82
|
if not name or not description:
|
|
97
83
|
return None
|
|
98
84
|
|
|
99
|
-
# Process relative paths in content
|
|
100
|
-
skill_dir = skill_path.parent
|
|
101
|
-
processed_content = self._process_skill_paths(markdown_content, skill_dir)
|
|
102
|
-
|
|
103
85
|
# Create Skill object
|
|
104
86
|
license_val = frontmatter.get("license")
|
|
105
87
|
allowed_tools_val = frontmatter.get("allowed-tools")
|
|
@@ -118,12 +100,12 @@ class SkillLoader:
|
|
|
118
100
|
skill = Skill(
|
|
119
101
|
name=name,
|
|
120
102
|
description=description,
|
|
121
|
-
content=processed_content,
|
|
122
103
|
location=location,
|
|
123
104
|
license=str(license_val) if license_val is not None else None,
|
|
124
105
|
allowed_tools=allowed_tools,
|
|
125
106
|
metadata=metadata,
|
|
126
|
-
skill_path=skill_path,
|
|
107
|
+
skill_path=skill_path.resolve(),
|
|
108
|
+
base_dir=skill_path.parent.resolve(),
|
|
127
109
|
)
|
|
128
110
|
|
|
129
111
|
return skill
|
|
@@ -144,6 +126,15 @@ class SkillLoader:
|
|
|
144
126
|
List of successfully loaded Skill objects
|
|
145
127
|
"""
|
|
146
128
|
skills: list[Skill] = []
|
|
129
|
+
priority = {"system": 0, "user": 1, "project": 2}
|
|
130
|
+
|
|
131
|
+
def register(skill: Skill) -> None:
|
|
132
|
+
existing = self.loaded_skills.get(skill.name)
|
|
133
|
+
if existing is None:
|
|
134
|
+
self.loaded_skills[skill.name] = skill
|
|
135
|
+
return
|
|
136
|
+
if priority.get(skill.location, -1) >= priority.get(existing.location, -1):
|
|
137
|
+
self.loaded_skills[skill.name] = skill
|
|
147
138
|
|
|
148
139
|
# Load system-level skills first (lowest priority, can be overridden)
|
|
149
140
|
system_dir = self.SYSTEM_SKILLS_DIR.expanduser()
|
|
@@ -152,7 +143,7 @@ class SkillLoader:
|
|
|
152
143
|
skill = self.load_skill(skill_file, location="system")
|
|
153
144
|
if skill:
|
|
154
145
|
skills.append(skill)
|
|
155
|
-
|
|
146
|
+
register(skill)
|
|
156
147
|
|
|
157
148
|
# Load user-level skills (override system skills if same name)
|
|
158
149
|
for user_dir in self.USER_SKILLS_DIRS:
|
|
@@ -165,7 +156,7 @@ class SkillLoader:
|
|
|
165
156
|
skill = self.load_skill(skill_file, location="user")
|
|
166
157
|
if skill:
|
|
167
158
|
skills.append(skill)
|
|
168
|
-
|
|
159
|
+
register(skill)
|
|
169
160
|
|
|
170
161
|
# Load project-level skills (override user skills if same name)
|
|
171
162
|
project_dir = self.PROJECT_SKILLS_DIR.resolve()
|
|
@@ -174,13 +165,14 @@ class SkillLoader:
|
|
|
174
165
|
skill = self.load_skill(skill_file, location="project")
|
|
175
166
|
if skill:
|
|
176
167
|
skills.append(skill)
|
|
177
|
-
|
|
168
|
+
register(skill)
|
|
178
169
|
|
|
179
170
|
# Log discovery summary
|
|
180
|
-
if
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
if self.loaded_skills:
|
|
172
|
+
selected = list(self.loaded_skills.values())
|
|
173
|
+
system_count = sum(1 for s in selected if s.location == "system")
|
|
174
|
+
user_count = sum(1 for s in selected if s.location == "user")
|
|
175
|
+
project_count = sum(1 for s in selected if s.location == "project")
|
|
184
176
|
parts: list[str] = []
|
|
185
177
|
if system_count > 0:
|
|
186
178
|
parts.append(f"{system_count} system")
|
|
@@ -188,7 +180,7 @@ class SkillLoader:
|
|
|
188
180
|
parts.append(f"{user_count} user")
|
|
189
181
|
if project_count > 0:
|
|
190
182
|
parts.append(f"{project_count} project")
|
|
191
|
-
log_debug(f"
|
|
183
|
+
log_debug(f"Loaded {len(self.loaded_skills)} Claude Skills ({', '.join(parts)})")
|
|
192
184
|
|
|
193
185
|
return skills
|
|
194
186
|
|
|
@@ -224,62 +216,14 @@ class SkillLoader:
|
|
|
224
216
|
XML string with all skill metadata
|
|
225
217
|
"""
|
|
226
218
|
xml_parts: list[str] = []
|
|
227
|
-
|
|
228
|
-
|
|
219
|
+
# Prefer showing higher-priority skills first (project > user > system).
|
|
220
|
+
location_order = {"project": 0, "user": 1, "system": 2}
|
|
221
|
+
for skill in sorted(self.loaded_skills.values(), key=lambda s: location_order.get(s.location, 3)):
|
|
222
|
+
xml_parts.append(
|
|
223
|
+
f"""<skill>
|
|
229
224
|
<name>{skill.name}</name>
|
|
230
225
|
<description>{skill.description}</description>
|
|
231
|
-
<location>{skill.
|
|
232
|
-
</skill>"""
|
|
226
|
+
<location>{skill.skill_path}</location>
|
|
227
|
+
</skill>"""
|
|
228
|
+
)
|
|
233
229
|
return "\n".join(xml_parts)
|
|
234
|
-
|
|
235
|
-
def _process_skill_paths(self, content: str, skill_dir: Path) -> str:
|
|
236
|
-
"""Convert relative paths to absolute paths for Level 3+
|
|
237
|
-
|
|
238
|
-
Supports:
|
|
239
|
-
- scripts/, examples/, templates/, reference/ directories
|
|
240
|
-
- Markdown document references
|
|
241
|
-
- Markdown links [text](path)
|
|
242
|
-
|
|
243
|
-
Args:
|
|
244
|
-
content: Original skill content
|
|
245
|
-
skill_dir: Directory containing the SKILL.md file
|
|
246
|
-
|
|
247
|
-
Returns:
|
|
248
|
-
Content with absolute paths
|
|
249
|
-
"""
|
|
250
|
-
# Pattern 1: Directory-based paths (scripts/, examples/, etc.)
|
|
251
|
-
# e.g., "python scripts/generate.py" -> "python /abs/path/to/scripts/generate.py"
|
|
252
|
-
dir_pattern = r"\b(scripts|examples|templates|reference)/([^\s\)]+)"
|
|
253
|
-
|
|
254
|
-
def replace_dir_path(match: re.Match[str]) -> str:
|
|
255
|
-
directory = match.group(1)
|
|
256
|
-
filename = match.group(2)
|
|
257
|
-
abs_path = skill_dir / directory / filename
|
|
258
|
-
return str(abs_path)
|
|
259
|
-
|
|
260
|
-
content = re.sub(dir_pattern, replace_dir_path, content)
|
|
261
|
-
|
|
262
|
-
# Pattern 2: Markdown links [text](./path or path)
|
|
263
|
-
# e.g., "[Guide](./docs/guide.md)" -> "[Guide](`/abs/path/to/docs/guide.md`) (use the Read tool to access)"
|
|
264
|
-
link_pattern = r"\[([^\]]+)\]\((\./)?([^\)]+\.md)\)"
|
|
265
|
-
|
|
266
|
-
def replace_link(match: re.Match[str]) -> str:
|
|
267
|
-
text = match.group(1)
|
|
268
|
-
filename = match.group(3)
|
|
269
|
-
abs_path = skill_dir / filename
|
|
270
|
-
return f"[{text}](`{abs_path}`) (use the Read tool to access)"
|
|
271
|
-
|
|
272
|
-
content = re.sub(link_pattern, replace_link, content)
|
|
273
|
-
|
|
274
|
-
# Pattern 3: Standalone markdown references
|
|
275
|
-
# e.g., "see reference.md" -> "see `/abs/path/to/reference.md` (use the Read tool to access)"
|
|
276
|
-
standalone_pattern = r"(?<!\])\b(\w+\.md)\b(?!\))"
|
|
277
|
-
|
|
278
|
-
def replace_standalone(match: re.Match[str]) -> str:
|
|
279
|
-
filename = match.group(1)
|
|
280
|
-
abs_path = skill_dir / filename
|
|
281
|
-
return f"`{abs_path}` (use the Read tool to access)"
|
|
282
|
-
|
|
283
|
-
content = re.sub(standalone_pattern, replace_standalone, content)
|
|
284
|
-
|
|
285
|
-
return content
|
klaude_code/skill/manager.py
CHANGED
|
@@ -68,3 +68,41 @@ def list_skill_names() -> list[str]:
|
|
|
68
68
|
List of skill names
|
|
69
69
|
"""
|
|
70
70
|
return _ensure_initialized().list_skills()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def format_available_skills_for_system_prompt() -> str:
|
|
74
|
+
"""Format skills metadata for inclusion in the system prompt.
|
|
75
|
+
|
|
76
|
+
This follows the progressive-disclosure approach:
|
|
77
|
+
- Keep only name/description + file location in the always-on system prompt
|
|
78
|
+
- Load the full SKILL.md content on demand via the Read tool when needed
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
loader = _ensure_initialized()
|
|
83
|
+
skills_xml = loader.get_skills_xml().strip()
|
|
84
|
+
if not skills_xml:
|
|
85
|
+
return ""
|
|
86
|
+
|
|
87
|
+
return f"""\
|
|
88
|
+
# Skills
|
|
89
|
+
|
|
90
|
+
Skills are optional task-specific instructions stored as `SKILL.md` files.
|
|
91
|
+
|
|
92
|
+
How to use skills:
|
|
93
|
+
- Use the metadata in <available_skills> to decide whether a skill applies.
|
|
94
|
+
- When the task matches a skill's description, use the `Read` tool to load the `SKILL.md` at the given <location>.
|
|
95
|
+
- If the user explicitly activates a skill by starting their message with `$skill-name`, prioritize that skill.
|
|
96
|
+
|
|
97
|
+
Important:
|
|
98
|
+
- Only use skills listed in <available_skills> below.
|
|
99
|
+
- Keep context small: do NOT load skill files unless needed.
|
|
100
|
+
|
|
101
|
+
The list below is metadata only (name/description/location). The full instructions live in the referenced file.
|
|
102
|
+
|
|
103
|
+
<available_skills>
|
|
104
|
+
{skills_xml}
|
|
105
|
+
</available_skills>"""
|
|
106
|
+
except Exception:
|
|
107
|
+
# Skills are an optional enhancement; do not fail prompt construction if discovery breaks.
|
|
108
|
+
return ""
|
|
@@ -11,7 +11,7 @@ from contextlib import contextmanager
|
|
|
11
11
|
from importlib import resources
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from klaude_code.
|
|
14
|
+
from klaude_code.log import log_debug
|
|
15
15
|
|
|
16
16
|
# Marker file name for tracking installed skills version
|
|
17
17
|
SYSTEM_SKILLS_MARKER_FILENAME = ".klaude-system-skills.marker"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Terminal (TUI) frontend for klaude-code.
|
|
2
|
+
|
|
3
|
+
This package contains all terminal-specific UI code (Rich rendering,
|
|
4
|
+
prompt-toolkit input, and terminal integrations).
|
|
5
|
+
|
|
6
|
+
The tui layer may depend on `klaude_code.ui`, but `klaude_code.ui` must not
|
|
7
|
+
depend on `klaude_code.tui`.
|
|
8
|
+
"""
|
|
@@ -30,6 +30,7 @@ def ensure_commands_loaded() -> None:
|
|
|
30
30
|
|
|
31
31
|
# Import and register commands in display order
|
|
32
32
|
from .clear_cmd import ClearCommand
|
|
33
|
+
from .copy_cmd import CopyCommand
|
|
33
34
|
from .debug_cmd import DebugCommand
|
|
34
35
|
from .export_cmd import ExportCommand
|
|
35
36
|
from .export_online_cmd import ExportOnlineCommand
|
|
@@ -49,6 +50,7 @@ def ensure_commands_loaded() -> None:
|
|
|
49
50
|
register(RefreshTerminalCommand())
|
|
50
51
|
register(ThinkingCommand())
|
|
51
52
|
register(ModelCommand())
|
|
53
|
+
register(CopyCommand())
|
|
52
54
|
register(ForkSessionCommand())
|
|
53
55
|
register(ResumeCommand())
|
|
54
56
|
load_prompt_commands()
|
|
@@ -66,6 +68,7 @@ def ensure_commands_loaded() -> None:
|
|
|
66
68
|
def __getattr__(name: str) -> object:
|
|
67
69
|
_commands_map = {
|
|
68
70
|
"ClearCommand": "clear_cmd",
|
|
71
|
+
"CopyCommand": "copy_cmd",
|
|
69
72
|
"DebugCommand": "debug_cmd",
|
|
70
73
|
"ExportCommand": "export_cmd",
|
|
71
74
|
"ExportOnlineCommand": "export_online_cmd",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
2
1
|
from klaude_code.protocol import commands, message, op
|
|
3
2
|
|
|
3
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
4
|
+
|
|
4
5
|
|
|
5
6
|
class ClearCommand(CommandABC):
|
|
6
7
|
"""Clear current session and start a new conversation"""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from klaude_code.protocol import commands, events, message, model
|
|
2
|
+
from klaude_code.tui.input.clipboard import copy_to_clipboard
|
|
3
|
+
|
|
4
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CopyCommand(CommandABC):
|
|
8
|
+
"""Copy the last assistant message to system clipboard."""
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def name(self) -> commands.CommandName:
|
|
12
|
+
return commands.CommandName.COPY
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def summary(self) -> str:
|
|
16
|
+
return "Copy last assistant message to clipboard"
|
|
17
|
+
|
|
18
|
+
async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
|
|
19
|
+
del user_input # unused
|
|
20
|
+
|
|
21
|
+
last = _get_last_assistant_text(agent.session.conversation_history)
|
|
22
|
+
if not last:
|
|
23
|
+
return _developer_message(agent, "(no assistant message to copy)", self.name)
|
|
24
|
+
|
|
25
|
+
copy_to_clipboard(last)
|
|
26
|
+
return _developer_message(agent, "Copied last assistant message to clipboard.", self.name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_last_assistant_text(history: list[message.HistoryEvent]) -> str:
|
|
30
|
+
for item in reversed(history):
|
|
31
|
+
if not isinstance(item, message.AssistantMessage):
|
|
32
|
+
continue
|
|
33
|
+
content = message.join_text_parts(item.parts)
|
|
34
|
+
images = [part for part in item.parts if isinstance(part, message.ImageFilePart)]
|
|
35
|
+
formatted = message.format_saved_images(images, content)
|
|
36
|
+
return formatted.strip()
|
|
37
|
+
return ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _developer_message(agent: Agent, content: str, command_name: commands.CommandName) -> CommandResult:
|
|
41
|
+
return CommandResult(
|
|
42
|
+
events=[
|
|
43
|
+
events.DeveloperMessageEvent(
|
|
44
|
+
session_id=agent.session.id,
|
|
45
|
+
item=message.DeveloperMessage(
|
|
46
|
+
parts=message.text_parts_from_str(content),
|
|
47
|
+
ui_extra=model.build_command_output_extra(command_name),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
],
|
|
51
|
+
persist_user_input=False,
|
|
52
|
+
persist_events=False,
|
|
53
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from klaude_code.
|
|
1
|
+
from klaude_code.log import DebugType, get_current_log_file, is_debug_enabled, set_debug_logging
|
|
2
2
|
from klaude_code.protocol import commands, events, message, model
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def _format_status() -> str:
|
|
@@ -2,9 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
6
5
|
from klaude_code.protocol import commands, message, op
|
|
7
6
|
|
|
7
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
class ExportCommand(CommandABC):
|
|
10
11
|
"""Export the current session into a standalone HTML transcript."""
|
|
@@ -9,10 +9,11 @@ from pathlib import Path
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
from rich.text import Text
|
|
11
11
|
|
|
12
|
-
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
13
12
|
from klaude_code.protocol import commands, events, message, model
|
|
14
13
|
from klaude_code.session.export import build_export_html
|
|
15
14
|
|
|
15
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
16
|
+
|
|
16
17
|
|
|
17
18
|
class ExportOnlineCommand(CommandABC):
|
|
18
19
|
"""Export and deploy the current session to surge.sh as a static webpage."""
|
|
@@ -5,10 +5,11 @@ from typing import Literal
|
|
|
5
5
|
|
|
6
6
|
from prompt_toolkit.styles import Style
|
|
7
7
|
|
|
8
|
-
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
9
8
|
from klaude_code.protocol import commands, events, message, model
|
|
10
|
-
from klaude_code.
|
|
11
|
-
from klaude_code.
|
|
9
|
+
from klaude_code.tui.input.clipboard import copy_to_clipboard
|
|
10
|
+
from klaude_code.tui.terminal.selector import SelectItem, select_one
|
|
11
|
+
|
|
12
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
12
13
|
|
|
13
14
|
FORK_SELECT_STYLE = Style(
|
|
14
15
|
[
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
2
1
|
from klaude_code.protocol import commands, events, message, model
|
|
3
2
|
|
|
3
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
4
|
+
|
|
4
5
|
|
|
5
6
|
class HelpCommand(CommandABC):
|
|
6
7
|
"""Display help information for all available slash commands."""
|
|
@@ -2,10 +2,11 @@ import asyncio
|
|
|
2
2
|
|
|
3
3
|
from prompt_toolkit.styles import Style
|
|
4
4
|
|
|
5
|
-
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
6
|
-
from klaude_code.command.model_select import select_model_interactive
|
|
7
5
|
from klaude_code.protocol import commands, events, message, model, op
|
|
8
|
-
from klaude_code.
|
|
6
|
+
from klaude_code.tui.terminal.selector import SelectItem, select_one
|
|
7
|
+
|
|
8
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
9
|
+
from .model_select import select_model_interactive
|
|
9
10
|
|
|
10
11
|
SELECT_STYLE = Style(
|
|
11
12
|
[
|
|
@@ -4,7 +4,7 @@ import sys
|
|
|
4
4
|
|
|
5
5
|
from klaude_code.config.config import load_config
|
|
6
6
|
from klaude_code.config.select_model import match_model_from_config
|
|
7
|
-
from klaude_code.
|
|
7
|
+
from klaude_code.log import log
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def select_model_interactive(preferred: str | None = None) -> str | None:
|
|
@@ -38,7 +38,7 @@ def select_model_interactive(preferred: str | None = None) -> str | None:
|
|
|
38
38
|
# Interactive selection
|
|
39
39
|
from prompt_toolkit.styles import Style
|
|
40
40
|
|
|
41
|
-
from klaude_code.
|
|
41
|
+
from klaude_code.tui.terminal.selector import build_model_select_items, select_one
|
|
42
42
|
|
|
43
43
|
config = load_config()
|
|
44
44
|
names = [m.model_name for m in result.filtered_models]
|
|
@@ -2,9 +2,10 @@ from importlib.resources import files
|
|
|
2
2
|
|
|
3
3
|
import yaml
|
|
4
4
|
|
|
5
|
-
from klaude_code.
|
|
5
|
+
from klaude_code.log import log_debug
|
|
6
6
|
from klaude_code.protocol import commands, message, op
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class PromptCommand(CommandABC):
|
|
@@ -30,7 +31,7 @@ class PromptCommand(CommandABC):
|
|
|
30
31
|
return
|
|
31
32
|
|
|
32
33
|
try:
|
|
33
|
-
raw_text = files("klaude_code.command").joinpath(self.template_name).read_text(encoding="utf-8")
|
|
34
|
+
raw_text = files("klaude_code.tui.command").joinpath(self.template_name).read_text(encoding="utf-8")
|
|
34
35
|
|
|
35
36
|
if raw_text.startswith("---"):
|
|
36
37
|
parts = raw_text.split("---", 2)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
2
1
|
from klaude_code.protocol import commands, events, message
|
|
3
2
|
|
|
3
|
+
from .command_abc import Agent, CommandABC, CommandResult
|
|
4
|
+
|
|
4
5
|
|
|
5
6
|
class RefreshTerminalCommand(CommandABC):
|
|
6
7
|
"""Refresh terminal display"""
|
|
@@ -26,6 +27,7 @@ class RefreshTerminalCommand(CommandABC):
|
|
|
26
27
|
return CommandResult(
|
|
27
28
|
events=[
|
|
28
29
|
events.WelcomeEvent(
|
|
30
|
+
session_id=agent.session.id,
|
|
29
31
|
work_dir=str(agent.session.work_dir),
|
|
30
32
|
llm_config=agent.get_llm_client().get_llm_config(),
|
|
31
33
|
),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from importlib.resources import files
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
|
-
from klaude_code.
|
|
5
|
-
from klaude_code.command.prompt_command import PromptCommand
|
|
4
|
+
from klaude_code.log import log_debug
|
|
6
5
|
from klaude_code.protocol import commands, events, message, model, op
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
from .command_abc import Agent, CommandResult
|
|
8
|
+
from .prompt_command import PromptCommand
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from .command_abc import CommandABC
|
|
@@ -82,7 +83,7 @@ def register(cmd: "CommandABC") -> None:
|
|
|
82
83
|
def load_prompt_commands():
|
|
83
84
|
"""Dynamically load prompt-based commands from the command directory."""
|
|
84
85
|
try:
|
|
85
|
-
command_files = files("klaude_code.command").iterdir()
|
|
86
|
+
command_files = files("klaude_code.tui.command").iterdir()
|
|
86
87
|
for file_path in command_files:
|
|
87
88
|
name = file_path.name
|
|
88
89
|
if (name.startswith("prompt_") or name.startswith("prompt-")) and name.endswith(".md"):
|
|
@@ -94,7 +95,7 @@ def load_prompt_commands():
|
|
|
94
95
|
|
|
95
96
|
def _ensure_commands_loaded() -> None:
|
|
96
97
|
"""Ensure all commands are loaded (lazy initialization)."""
|
|
97
|
-
from klaude_code.command import ensure_commands_loaded
|
|
98
|
+
from klaude_code.tui.command import ensure_commands_loaded
|
|
98
99
|
|
|
99
100
|
ensure_commands_loaded()
|
|
100
101
|
|