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.
Files changed (32) hide show
  1. klaude_code/cli/main.py +42 -22
  2. klaude_code/cli/runtime.py +41 -2
  3. klaude_code/{version.py → cli/self_update.py} +110 -2
  4. klaude_code/command/export_online_cmd.py +8 -3
  5. klaude_code/command/registry.py +67 -22
  6. klaude_code/command/thinking_cmd.py +4 -3
  7. klaude_code/core/executor.py +21 -1
  8. klaude_code/core/prompts/prompt-sub-agent-explore.md +14 -2
  9. klaude_code/core/task.py +9 -7
  10. klaude_code/core/tool/file/read_tool.md +1 -1
  11. klaude_code/core/tool/file/read_tool.py +3 -2
  12. klaude_code/core/tool/memory/skill_loader.py +12 -10
  13. klaude_code/core/tool/tool_registry.py +1 -1
  14. klaude_code/llm/anthropic/client.py +25 -9
  15. klaude_code/llm/openai_compatible/client.py +5 -2
  16. klaude_code/llm/openrouter/client.py +7 -3
  17. klaude_code/llm/responses/client.py +6 -1
  18. klaude_code/session/session.py +33 -13
  19. klaude_code/session/templates/export_session.html +43 -41
  20. klaude_code/ui/modes/repl/completers.py +212 -71
  21. klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
  22. klaude_code/ui/modes/repl/renderer.py +2 -2
  23. klaude_code/ui/renderers/common.py +54 -0
  24. klaude_code/ui/renderers/developer.py +2 -3
  25. klaude_code/ui/renderers/errors.py +1 -1
  26. klaude_code/ui/renderers/metadata.py +10 -1
  27. klaude_code/ui/renderers/tools.py +3 -4
  28. klaude_code/ui/utils/common.py +0 -18
  29. {klaude_code-1.2.18.dist-info → klaude_code-1.2.19.dist-info}/METADATA +17 -2
  30. {klaude_code-1.2.18.dist-info → klaude_code-1.2.19.dist-info}/RECORD +32 -32
  31. {klaude_code-1.2.18.dist-info → klaude_code-1.2.19.dist-info}/WHEEL +0 -0
  32. {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
- "Please use a Python script with `pdfplumber` to extract text/tables:\n\n"
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 skill_file in expanded_dir.rglob("SKILL.md"):
136
- skill = self.load_skill(skill_file, location="user")
137
- if skill:
138
- skills.append(skill)
139
- self.loaded_skills[skill.name] = skill
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 skill_file in project_dir.rglob("SKILL.md"):
145
- skill = self.load_skill(skill_file, location="project")
146
- if skill:
147
- skills.append(skill)
148
- self.loaded_skills[skill.name] = skill
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
- client = anthropic.AsyncAnthropic(
65
- api_key=config.api_key,
66
- base_url=config.base_url,
67
- timeout=httpx.Timeout(300.0, connect=15.0, read=285.0),
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
- metadata_tracker.record_token()
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
- metadata_tracker.record_token()
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
- for item in state.flush_all():
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
- metadata_tracker.record_token()
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
- for item in state.flush_all():
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
- metadata_tracker.record_token()
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
  [
@@ -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 create(cls, id: str | None = None) -> "Session":
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
- # No existing session; create a new one
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
- sess = Session(
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%230851b2%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>"
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/source-sans-3/400.css"
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/source-sans-3/400-italic.css"
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/source-sans-3/700.css"
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/source-sans-3/700-italic.css"
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: #eae9e5;
38
- --bg-container: #edece9;
39
- --bg-card: #efeeeb;
40
- --bg-error: #ffebee;
41
- --bg-code: #f2f1ed;
42
- --border: #c8c8c8;
43
- --text: #111111;
44
- --text-dim: #64748b;
45
- --accent: #0851b2;
46
- --accent-dim: rgba(8, 145, 178, 0.08);
47
- --success: #15803d;
48
- --error: #dc2626;
49
- --fg-inline-code: #4f4fc7;
50
- --font-mono: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
51
- --font-markdown-mono: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
52
- --font-markdown: "Source Sans 3", system-ui, sans-serif;
53
- --font-weight-bold: 700;
54
- --font-size-xs: 13px;
55
- --font-size-sm: 14px;
56
- --font-size-base: 15px;
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: rgba(255, 255, 255, 0.98);
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 rgba(8, 81, 178, 0.2);
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: rgba(8, 81, 178, 0.05);
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: #166534;
828
- background: #e7f5e9;
823
+ color: var(--success);
824
+ background: var(--bg-success);
829
825
  display: block;
830
826
  }
831
827
  .diff-minus {
832
- color: #991b1b;
833
- background: #ffebee;
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: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
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>