klaude-code 1.2.18__py3-none-any.whl → 1.2.19__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 +42 -22
- klaude_code/cli/runtime.py +41 -2
- klaude_code/{version.py → cli/self_update.py} +110 -2
- klaude_code/command/export_online_cmd.py +8 -3
- klaude_code/command/registry.py +67 -22
- klaude_code/command/thinking_cmd.py +4 -3
- klaude_code/core/executor.py +21 -1
- klaude_code/core/prompts/prompt-sub-agent-explore.md +14 -2
- klaude_code/core/task.py +9 -7
- klaude_code/core/tool/file/read_tool.md +1 -1
- klaude_code/core/tool/file/read_tool.py +3 -2
- klaude_code/core/tool/memory/skill_loader.py +12 -10
- klaude_code/core/tool/tool_registry.py +1 -1
- klaude_code/llm/anthropic/client.py +25 -9
- klaude_code/llm/openai_compatible/client.py +5 -2
- klaude_code/llm/openrouter/client.py +7 -3
- klaude_code/llm/responses/client.py +6 -1
- klaude_code/session/session.py +33 -13
- klaude_code/session/templates/export_session.html +43 -41
- klaude_code/ui/modes/repl/completers.py +212 -71
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
- klaude_code/ui/modes/repl/renderer.py +2 -2
- klaude_code/ui/renderers/common.py +54 -0
- klaude_code/ui/renderers/developer.py +2 -3
- klaude_code/ui/renderers/errors.py +1 -1
- klaude_code/ui/renderers/metadata.py +10 -1
- klaude_code/ui/renderers/tools.py +3 -4
- klaude_code/ui/utils/common.py +0 -18
- {klaude_code-1.2.18.dist-info → klaude_code-1.2.19.dist-info}/METADATA +17 -2
- {klaude_code-1.2.18.dist-info → klaude_code-1.2.19.dist-info}/RECORD +32 -32
- {klaude_code-1.2.18.dist-info → klaude_code-1.2.19.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.18.dist-info → klaude_code-1.2.19.dist-info}/entry_points.txt +0 -0
|
@@ -209,8 +209,9 @@ class ReadTool(ToolABC):
|
|
|
209
209
|
return model.ToolResultItem(
|
|
210
210
|
status="error",
|
|
211
211
|
output=(
|
|
212
|
-
"<tool_use_error>PDF files are not supported by this tool
|
|
213
|
-
"
|
|
212
|
+
"<tool_use_error>PDF files are not supported by this tool.\n"
|
|
213
|
+
"If there's an available skill for PDF, use it.\n"
|
|
214
|
+
"Or use a Python script with `pdfplumber` to extract text/tables:\n\n"
|
|
214
215
|
"```python\n"
|
|
215
216
|
"# /// script\n"
|
|
216
217
|
'# dependencies = ["pdfplumber"]\n'
|
|
@@ -132,20 +132,22 @@ class SkillLoader:
|
|
|
132
132
|
for user_dir in self.USER_SKILLS_DIRS:
|
|
133
133
|
expanded_dir = user_dir.expanduser()
|
|
134
134
|
if expanded_dir.exists():
|
|
135
|
-
for
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
for pattern in ("SKILL.md", "skill.md"):
|
|
136
|
+
for skill_file in expanded_dir.rglob(pattern):
|
|
137
|
+
skill = self.load_skill(skill_file, location="user")
|
|
138
|
+
if skill:
|
|
139
|
+
skills.append(skill)
|
|
140
|
+
self.loaded_skills[skill.name] = skill
|
|
140
141
|
|
|
141
142
|
# Load project-level skills (override user skills if same name)
|
|
142
143
|
project_dir = self.PROJECT_SKILLS_DIR.resolve()
|
|
143
144
|
if project_dir.exists():
|
|
144
|
-
for
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
for pattern in ("SKILL.md", "skill.md"):
|
|
146
|
+
for skill_file in project_dir.rglob(pattern):
|
|
147
|
+
skill = self.load_skill(skill_file, location="project")
|
|
148
|
+
if skill:
|
|
149
|
+
skills.append(skill)
|
|
150
|
+
self.loaded_skills[skill.name] = skill
|
|
149
151
|
|
|
150
152
|
# Log discovery summary
|
|
151
153
|
if skills:
|
|
@@ -66,7 +66,7 @@ def load_agent_tools(
|
|
|
66
66
|
|
|
67
67
|
# Main agent tools
|
|
68
68
|
if "gpt-5" in model_name:
|
|
69
|
-
tool_names = [tools.BASH, tools.APPLY_PATCH, tools.UPDATE_PLAN]
|
|
69
|
+
tool_names = [tools.BASH, tools.READ, tools.APPLY_PATCH, tools.UPDATE_PLAN]
|
|
70
70
|
elif "gemini-3" in model_name:
|
|
71
71
|
tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE]
|
|
72
72
|
else:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import os
|
|
2
3
|
from collections.abc import AsyncGenerator
|
|
3
4
|
from typing import override
|
|
4
5
|
|
|
@@ -61,11 +62,20 @@ def build_payload(param: llm_param.LLMCallParameter) -> MessageCreateParamsStrea
|
|
|
61
62
|
class AnthropicClient(LLMClientABC):
|
|
62
63
|
def __init__(self, config: llm_param.LLMConfigParameter):
|
|
63
64
|
super().__init__(config)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
65
|
+
# Remove ANTHROPIC_AUTH_TOKEN env var to prevent anthropic SDK from adding
|
|
66
|
+
# Authorization: Bearer header that may conflict with third-party APIs
|
|
67
|
+
# (e.g., deepseek, moonshot) that use Authorization header for authentication.
|
|
68
|
+
# The API key will be sent via X-Api-Key header instead.
|
|
69
|
+
saved_auth_token = os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
|
|
70
|
+
try:
|
|
71
|
+
client = anthropic.AsyncAnthropic(
|
|
72
|
+
api_key=config.api_key,
|
|
73
|
+
base_url=config.base_url,
|
|
74
|
+
timeout=httpx.Timeout(300.0, connect=15.0, read=285.0),
|
|
75
|
+
)
|
|
76
|
+
finally:
|
|
77
|
+
if saved_auth_token is not None:
|
|
78
|
+
os.environ["ANTHROPIC_AUTH_TOKEN"] = saved_auth_token
|
|
69
79
|
self.client: anthropic.AsyncAnthropic = client
|
|
70
80
|
|
|
71
81
|
@classmethod
|
|
@@ -120,35 +130,38 @@ class AnthropicClient(LLMClientABC):
|
|
|
120
130
|
case BetaRawContentBlockDeltaEvent() as event:
|
|
121
131
|
match event.delta:
|
|
122
132
|
case BetaThinkingDelta() as delta:
|
|
123
|
-
|
|
133
|
+
if delta.thinking:
|
|
134
|
+
metadata_tracker.record_token()
|
|
124
135
|
accumulated_thinking.append(delta.thinking)
|
|
125
136
|
yield model.ReasoningTextDelta(
|
|
126
137
|
content=delta.thinking,
|
|
127
138
|
response_id=response_id,
|
|
128
139
|
)
|
|
129
140
|
case BetaSignatureDelta() as delta:
|
|
130
|
-
metadata_tracker.record_token()
|
|
131
141
|
yield model.ReasoningEncryptedItem(
|
|
132
142
|
encrypted_content=delta.signature,
|
|
133
143
|
response_id=response_id,
|
|
134
144
|
model=str(param.model),
|
|
135
145
|
)
|
|
136
146
|
case BetaTextDelta() as delta:
|
|
137
|
-
|
|
147
|
+
if delta.text:
|
|
148
|
+
metadata_tracker.record_token()
|
|
138
149
|
accumulated_content.append(delta.text)
|
|
139
150
|
yield model.AssistantMessageDelta(
|
|
140
151
|
content=delta.text,
|
|
141
152
|
response_id=response_id,
|
|
142
153
|
)
|
|
143
154
|
case BetaInputJSONDelta() as delta:
|
|
144
|
-
metadata_tracker.record_token()
|
|
145
155
|
if current_tool_inputs is not None:
|
|
156
|
+
if delta.partial_json:
|
|
157
|
+
metadata_tracker.record_token()
|
|
146
158
|
current_tool_inputs.append(delta.partial_json)
|
|
147
159
|
case _:
|
|
148
160
|
pass
|
|
149
161
|
case BetaRawContentBlockStartEvent() as event:
|
|
150
162
|
match event.content_block:
|
|
151
163
|
case BetaToolUseBlock() as block:
|
|
164
|
+
metadata_tracker.record_token()
|
|
152
165
|
yield model.ToolCallStartItem(
|
|
153
166
|
response_id=response_id,
|
|
154
167
|
call_id=block.id,
|
|
@@ -161,6 +174,7 @@ class AnthropicClient(LLMClientABC):
|
|
|
161
174
|
pass
|
|
162
175
|
case BetaRawContentBlockStopEvent() as event:
|
|
163
176
|
if len(accumulated_thinking) > 0:
|
|
177
|
+
metadata_tracker.record_token()
|
|
164
178
|
full_thinking = "".join(accumulated_thinking)
|
|
165
179
|
yield model.ReasoningTextItem(
|
|
166
180
|
content=full_thinking,
|
|
@@ -169,12 +183,14 @@ class AnthropicClient(LLMClientABC):
|
|
|
169
183
|
)
|
|
170
184
|
accumulated_thinking.clear()
|
|
171
185
|
if len(accumulated_content) > 0:
|
|
186
|
+
metadata_tracker.record_token()
|
|
172
187
|
yield model.AssistantMessageItem(
|
|
173
188
|
content="".join(accumulated_content),
|
|
174
189
|
response_id=response_id,
|
|
175
190
|
)
|
|
176
191
|
accumulated_content.clear()
|
|
177
192
|
if current_tool_name and current_tool_call_id:
|
|
193
|
+
metadata_tracker.record_token()
|
|
178
194
|
yield model.ToolCallItem(
|
|
179
195
|
name=current_tool_name,
|
|
180
196
|
call_id=current_tool_call_id,
|
|
@@ -23,7 +23,7 @@ def build_payload(param: llm_param.LLMCallParameter) -> tuple[CompletionCreatePa
|
|
|
23
23
|
|
|
24
24
|
extra_body: dict[str, object] = {}
|
|
25
25
|
|
|
26
|
-
if param.thinking:
|
|
26
|
+
if param.thinking and param.thinking.type == "enabled":
|
|
27
27
|
extra_body["thinking"] = {
|
|
28
28
|
"type": param.thinking.type,
|
|
29
29
|
"budget": param.thinking.budget_tokens,
|
|
@@ -182,7 +182,10 @@ class OpenAICompatibleClient(LLMClientABC):
|
|
|
182
182
|
yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
|
183
183
|
|
|
184
184
|
# Finalize
|
|
185
|
-
|
|
185
|
+
flushed_items = state.flush_all()
|
|
186
|
+
if flushed_items:
|
|
187
|
+
metadata_tracker.record_token()
|
|
188
|
+
for item in flushed_items:
|
|
186
189
|
yield item
|
|
187
190
|
|
|
188
191
|
metadata_tracker.set_response_id(state.response_id)
|
|
@@ -35,7 +35,7 @@ def build_payload(
|
|
|
35
35
|
extra_headers: dict[str, str] = {}
|
|
36
36
|
|
|
37
37
|
if param.thinking:
|
|
38
|
-
if param.thinking.budget_tokens is not None:
|
|
38
|
+
if param.thinking.type != "disabled" and param.thinking.budget_tokens is not None:
|
|
39
39
|
extra_body["reasoning"] = {
|
|
40
40
|
"max_tokens": param.thinking.budget_tokens,
|
|
41
41
|
"enable": True,
|
|
@@ -139,7 +139,8 @@ class OpenRouterClient(LLMClientABC):
|
|
|
139
139
|
for item in reasoning_details:
|
|
140
140
|
try:
|
|
141
141
|
reasoning_detail = ReasoningDetail.model_validate(item)
|
|
142
|
-
|
|
142
|
+
if reasoning_detail.text or reasoning_detail.summary:
|
|
143
|
+
metadata_tracker.record_token()
|
|
143
144
|
state.stage = "reasoning"
|
|
144
145
|
# Yield delta immediately for streaming
|
|
145
146
|
if reasoning_detail.text:
|
|
@@ -198,7 +199,10 @@ class OpenRouterClient(LLMClientABC):
|
|
|
198
199
|
yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
|
|
199
200
|
|
|
200
201
|
# Finalize
|
|
201
|
-
|
|
202
|
+
flushed_items = state.flush_all()
|
|
203
|
+
if flushed_items:
|
|
204
|
+
metadata_tracker.record_token()
|
|
205
|
+
for item in flushed_items:
|
|
202
206
|
yield item
|
|
203
207
|
|
|
204
208
|
metadata_tracker.set_response_id(state.response_id)
|
|
@@ -77,6 +77,7 @@ async def parse_responses_stream(
|
|
|
77
77
|
yield model.StartItem(response_id=response_id)
|
|
78
78
|
case responses.ResponseReasoningSummaryTextDeltaEvent() as event:
|
|
79
79
|
if event.delta:
|
|
80
|
+
metadata_tracker.record_token()
|
|
80
81
|
yield model.ReasoningTextDelta(
|
|
81
82
|
content=event.delta,
|
|
82
83
|
response_id=response_id,
|
|
@@ -89,10 +90,12 @@ async def parse_responses_stream(
|
|
|
89
90
|
model=str(param.model),
|
|
90
91
|
)
|
|
91
92
|
case responses.ResponseTextDeltaEvent() as event:
|
|
92
|
-
|
|
93
|
+
if event.delta:
|
|
94
|
+
metadata_tracker.record_token()
|
|
93
95
|
yield model.AssistantMessageDelta(content=event.delta, response_id=response_id)
|
|
94
96
|
case responses.ResponseOutputItemAddedEvent() as event:
|
|
95
97
|
if isinstance(event.item, responses.ResponseFunctionToolCall):
|
|
98
|
+
metadata_tracker.record_token()
|
|
96
99
|
yield model.ToolCallStartItem(
|
|
97
100
|
response_id=response_id,
|
|
98
101
|
call_id=event.item.call_id,
|
|
@@ -102,6 +105,7 @@ async def parse_responses_stream(
|
|
|
102
105
|
match event.item:
|
|
103
106
|
case responses.ResponseReasoningItem() as item:
|
|
104
107
|
if item.encrypted_content:
|
|
108
|
+
metadata_tracker.record_token()
|
|
105
109
|
yield model.ReasoningEncryptedItem(
|
|
106
110
|
id=item.id,
|
|
107
111
|
encrypted_content=item.encrypted_content,
|
|
@@ -109,6 +113,7 @@ async def parse_responses_stream(
|
|
|
109
113
|
model=str(param.model),
|
|
110
114
|
)
|
|
111
115
|
case responses.ResponseOutputMessage() as item:
|
|
116
|
+
metadata_tracker.record_token()
|
|
112
117
|
yield model.AssistantMessageItem(
|
|
113
118
|
content="\n".join(
|
|
114
119
|
[
|
klaude_code/session/session.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
|
|
8
8
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
9
9
|
|
|
10
|
-
from klaude_code.protocol import events, model, tools
|
|
10
|
+
from klaude_code.protocol import events, llm_param, model, tools
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Session(BaseModel):
|
|
@@ -22,6 +22,9 @@ class Session(BaseModel):
|
|
|
22
22
|
# Model name used for this session
|
|
23
23
|
# Used in list method SessionMetaBrief
|
|
24
24
|
model_name: str | None = None
|
|
25
|
+
|
|
26
|
+
model_config_name: str | None = None
|
|
27
|
+
model_thinking: llm_param.Thinking | None = None
|
|
25
28
|
# Timestamps (epoch seconds)
|
|
26
29
|
created_at: float = Field(default_factory=lambda: time.time())
|
|
27
30
|
updated_at: float = Field(default_factory=lambda: time.time())
|
|
@@ -103,14 +106,7 @@ class Session(BaseModel):
|
|
|
103
106
|
return self._messages_dir() / f"{prefix}-{self.id}.jsonl"
|
|
104
107
|
|
|
105
108
|
@classmethod
|
|
106
|
-
def
|
|
107
|
-
"""Create a new session without checking for existing files."""
|
|
108
|
-
return Session(id=id or uuid.uuid4().hex, work_dir=Path.cwd())
|
|
109
|
-
|
|
110
|
-
@classmethod
|
|
111
|
-
def load(cls, id: str) -> "Session":
|
|
112
|
-
"""Load an existing session or create a new one if not found."""
|
|
113
|
-
# Load session metadata
|
|
109
|
+
def _find_session_file(cls, id: str) -> Path | None:
|
|
114
110
|
sessions_dir = cls._sessions_dir()
|
|
115
111
|
session_candidates = sorted(
|
|
116
112
|
sessions_dir.glob(f"*-{id}.json"),
|
|
@@ -118,13 +114,24 @@ class Session(BaseModel):
|
|
|
118
114
|
reverse=True,
|
|
119
115
|
)
|
|
120
116
|
if not session_candidates:
|
|
121
|
-
|
|
117
|
+
return None
|
|
118
|
+
return session_candidates[0]
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def create(cls, id: str | None = None) -> "Session":
|
|
122
|
+
"""Create a new session without checking for existing files."""
|
|
123
|
+
return Session(id=id or uuid.uuid4().hex, work_dir=Path.cwd())
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def load_meta(cls, id: str) -> "Session":
|
|
127
|
+
"""Load session metadata only (without loading messages history)."""
|
|
128
|
+
|
|
129
|
+
session_path = cls._find_session_file(id)
|
|
130
|
+
if session_path is None:
|
|
122
131
|
return Session(id=id, work_dir=Path.cwd())
|
|
123
|
-
session_path = session_candidates[0]
|
|
124
132
|
|
|
125
133
|
raw = json.loads(session_path.read_text())
|
|
126
134
|
|
|
127
|
-
# Basic fields (conversation history is loaded separately)
|
|
128
135
|
work_dir_str = raw.get("work_dir", str(Path.cwd()))
|
|
129
136
|
|
|
130
137
|
sub_agent_state_raw = raw.get("sub_agent_state")
|
|
@@ -135,8 +142,12 @@ class Session(BaseModel):
|
|
|
135
142
|
created_at = float(raw.get("created_at", time.time()))
|
|
136
143
|
updated_at = float(raw.get("updated_at", created_at))
|
|
137
144
|
model_name = raw.get("model_name")
|
|
145
|
+
model_config_name = raw.get("model_config_name")
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
model_thinking_raw = raw.get("model_thinking")
|
|
148
|
+
model_thinking = llm_param.Thinking(**model_thinking_raw) if isinstance(model_thinking_raw, dict) else None # pyright: ignore[reportUnknownArgumentType]
|
|
149
|
+
|
|
150
|
+
return Session(
|
|
140
151
|
id=id,
|
|
141
152
|
work_dir=Path(work_dir_str),
|
|
142
153
|
sub_agent_state=sub_agent_state,
|
|
@@ -146,8 +157,15 @@ class Session(BaseModel):
|
|
|
146
157
|
created_at=created_at,
|
|
147
158
|
updated_at=updated_at,
|
|
148
159
|
model_name=model_name,
|
|
160
|
+
model_config_name=model_config_name,
|
|
161
|
+
model_thinking=model_thinking,
|
|
149
162
|
)
|
|
150
163
|
|
|
164
|
+
@classmethod
|
|
165
|
+
def load(cls, id: str) -> "Session":
|
|
166
|
+
"""Load an existing session or create a new one if not found."""
|
|
167
|
+
sess = cls.load_meta(id)
|
|
168
|
+
|
|
151
169
|
# Load conversation history from messages JSONL
|
|
152
170
|
messages_dir = cls._messages_dir()
|
|
153
171
|
# Expect a single messages file per session (prefixed filenames only)
|
|
@@ -204,6 +222,8 @@ class Session(BaseModel):
|
|
|
204
222
|
"updated_at": self.updated_at,
|
|
205
223
|
"messages_count": self.messages_count,
|
|
206
224
|
"model_name": self.model_name,
|
|
225
|
+
"model_config_name": self.model_config_name,
|
|
226
|
+
"model_thinking": self.model_thinking.model_dump() if self.model_thinking else None,
|
|
207
227
|
}
|
|
208
228
|
self._session_file().write_text(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
209
229
|
|
|
@@ -6,54 +6,50 @@
|
|
|
6
6
|
<title>Klaude Code - $first_user_message</title>
|
|
7
7
|
<link
|
|
8
8
|
rel="icon"
|
|
9
|
-
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%
|
|
9
|
+
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%230b5bd3%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><polyline points=%2216 18 22 12 16 6%22></polyline><polyline points=%228 6 2 12 8 18%22></polyline></svg>"
|
|
10
10
|
/>
|
|
11
11
|
<link
|
|
12
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/
|
|
12
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/geist/400.css"
|
|
13
13
|
rel="stylesheet"
|
|
14
14
|
/>
|
|
15
15
|
<link
|
|
16
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/
|
|
16
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/geist/800.css"
|
|
17
17
|
rel="stylesheet"
|
|
18
18
|
/>
|
|
19
19
|
<link
|
|
20
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/
|
|
20
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono/400.css"
|
|
21
21
|
rel="stylesheet"
|
|
22
22
|
/>
|
|
23
23
|
<link
|
|
24
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/
|
|
25
|
-
rel="stylesheet"
|
|
26
|
-
/>
|
|
27
|
-
<link
|
|
28
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/fira-code/400.css"
|
|
29
|
-
rel="stylesheet"
|
|
30
|
-
/>
|
|
31
|
-
<link
|
|
32
|
-
href="https://cdn.jsdelivr.net/npm/@fontsource/fira-code/700.css"
|
|
24
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono/800.css"
|
|
33
25
|
rel="stylesheet"
|
|
34
26
|
/>
|
|
35
27
|
<style>
|
|
36
28
|
:root {
|
|
37
|
-
--bg-body: #
|
|
38
|
-
--bg-container: #
|
|
39
|
-
--bg-card: #
|
|
40
|
-
--bg-
|
|
41
|
-
--bg-
|
|
42
|
-
--
|
|
43
|
-
--
|
|
44
|
-
--
|
|
45
|
-
--
|
|
46
|
-
--
|
|
47
|
-
--
|
|
48
|
-
--
|
|
49
|
-
--
|
|
50
|
-
--
|
|
51
|
-
--
|
|
52
|
-
--
|
|
53
|
-
--
|
|
54
|
-
--font-
|
|
55
|
-
--font-
|
|
56
|
-
--font-
|
|
29
|
+
--bg-body: #f0efeb;
|
|
30
|
+
--bg-container: #f3f2ee;
|
|
31
|
+
--bg-card: #f3f2ee;
|
|
32
|
+
--bg-overlay: rgba(248, 246, 240, 0.98);
|
|
33
|
+
--bg-error: #fdecec;
|
|
34
|
+
--bg-success: #eaf6ed;
|
|
35
|
+
--bg-code: #f7f7f4;
|
|
36
|
+
--border: #ded8cf;
|
|
37
|
+
--text: #151515;
|
|
38
|
+
--text-dim: #5f6b7a;
|
|
39
|
+
--accent: #0b5bd3;
|
|
40
|
+
--accent-dim: rgba(11, 91, 211, 0.10);
|
|
41
|
+
--accent-underline: rgba(11, 91, 211, 0.25);
|
|
42
|
+
--accent-hover: rgba(11, 91, 211, 0.06);
|
|
43
|
+
--success: #1a7f37;
|
|
44
|
+
--error: #c62828;
|
|
45
|
+
--fg-inline-code: #1f4fbf;
|
|
46
|
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
47
|
+
--font-markdown-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
48
|
+
--font-markdown: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
49
|
+
--font-weight-bold: 800;
|
|
50
|
+
--font-size-xs: 12px;
|
|
51
|
+
--font-size-sm: 13px;
|
|
52
|
+
--font-size-base: 14px;
|
|
57
53
|
--font-size-lg: 16px;
|
|
58
54
|
--spacing-xs: 4px;
|
|
59
55
|
--spacing-sm: 8px;
|
|
@@ -351,7 +347,7 @@
|
|
|
351
347
|
left: 0;
|
|
352
348
|
width: 100vw;
|
|
353
349
|
height: 100vh;
|
|
354
|
-
background:
|
|
350
|
+
background: var(--bg-overlay);
|
|
355
351
|
z-index: 1000;
|
|
356
352
|
display: flex;
|
|
357
353
|
flex-direction: column;
|
|
@@ -708,12 +704,12 @@
|
|
|
708
704
|
.markdown-body a {
|
|
709
705
|
color: var(--accent);
|
|
710
706
|
text-decoration: none;
|
|
711
|
-
border-bottom: 1px solid
|
|
707
|
+
border-bottom: 1px solid var(--accent-underline);
|
|
712
708
|
transition: border-color 0.2s, background-color 0.2s;
|
|
713
709
|
}
|
|
714
710
|
.markdown-body a:hover {
|
|
715
711
|
border-bottom-color: var(--accent);
|
|
716
|
-
background-color:
|
|
712
|
+
background-color: var(--accent-hover);
|
|
717
713
|
border-radius: 2px;
|
|
718
714
|
}
|
|
719
715
|
|
|
@@ -824,13 +820,13 @@
|
|
|
824
820
|
white-space: pre;
|
|
825
821
|
}
|
|
826
822
|
.diff-plus {
|
|
827
|
-
color:
|
|
828
|
-
background:
|
|
823
|
+
color: var(--success);
|
|
824
|
+
background: var(--bg-success);
|
|
829
825
|
display: block;
|
|
830
826
|
}
|
|
831
827
|
.diff-minus {
|
|
832
|
-
color:
|
|
833
|
-
background:
|
|
828
|
+
color: var(--error);
|
|
829
|
+
background: var(--bg-error);
|
|
834
830
|
display: block;
|
|
835
831
|
}
|
|
836
832
|
.diff-ctx {
|
|
@@ -1278,10 +1274,16 @@
|
|
|
1278
1274
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
1279
1275
|
<script type="module">
|
|
1280
1276
|
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
|
|
1277
|
+
const markdownMonoFont = getComputedStyle(document.documentElement)
|
|
1278
|
+
.getPropertyValue("--font-markdown-mono")
|
|
1279
|
+
.trim();
|
|
1280
|
+
|
|
1281
1281
|
mermaid.initialize({
|
|
1282
1282
|
startOnLoad: true,
|
|
1283
1283
|
theme: "neutral",
|
|
1284
|
-
fontFamily:
|
|
1284
|
+
fontFamily:
|
|
1285
|
+
markdownMonoFont ||
|
|
1286
|
+
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
|
|
1285
1287
|
});
|
|
1286
1288
|
</script>
|
|
1287
1289
|
<script>
|