klaude-code 1.2.3__py3-none-any.whl → 1.2.5__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/runtime.py +5 -4
- klaude_code/command/__init__.py +2 -0
- klaude_code/command/export_cmd.py +3 -1
- klaude_code/command/status_cmd.py +111 -0
- klaude_code/core/executor.py +100 -232
- klaude_code/core/manager/__init__.py +19 -0
- klaude_code/core/manager/agent_manager.py +127 -0
- klaude_code/core/manager/llm_clients.py +42 -0
- klaude_code/core/manager/llm_clients_builder.py +49 -0
- klaude_code/core/manager/sub_agent_manager.py +86 -0
- klaude_code/core/tool/shell/bash_tool.py +4 -6
- klaude_code/core/tool/shell/command_safety.py +0 -267
- klaude_code/core/tool/tool_registry.py +2 -0
- klaude_code/llm/anthropic/input.py +4 -1
- klaude_code/llm/openai_compatible/input.py +4 -3
- klaude_code/llm/openrouter/input.py +4 -3
- klaude_code/llm/responses/input.py +1 -1
- klaude_code/protocol/commands.py +1 -0
- klaude_code/protocol/model.py +7 -0
- klaude_code/session/export.py +16 -18
- klaude_code/session/templates/export_session.html +221 -10
- klaude_code/ui/renderers/developer.py +56 -1
- {klaude_code-1.2.3.dist-info → klaude_code-1.2.5.dist-info}/METADATA +1 -1
- {klaude_code-1.2.3.dist-info → klaude_code-1.2.5.dist-info}/RECORD +26 -20
- {klaude_code-1.2.3.dist-info → klaude_code-1.2.5.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.3.dist-info → klaude_code-1.2.5.dist-info}/entry_points.txt +0 -0
klaude_code/protocol/commands.py
CHANGED
|
@@ -11,6 +11,7 @@ class CommandName(str, Enum):
|
|
|
11
11
|
CLEAR = "clear"
|
|
12
12
|
TERMINAL_SETUP = "terminal-setup"
|
|
13
13
|
EXPORT = "export"
|
|
14
|
+
STATUS = "status"
|
|
14
15
|
# PLAN and DOC are dynamically registered now, but kept here if needed for reference
|
|
15
16
|
# or we can remove them if no code explicitly imports them.
|
|
16
17
|
# PLAN = "plan"
|
klaude_code/protocol/model.py
CHANGED
|
@@ -45,6 +45,7 @@ class ToolResultUIExtraType(str, Enum):
|
|
|
45
45
|
SESSION_ID = "session_id"
|
|
46
46
|
MERMAID_LINK = "mermaid_link"
|
|
47
47
|
TRUNCATION = "truncation"
|
|
48
|
+
SESSION_STATUS = "session_status"
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
class ToolSideEffect(str, Enum):
|
|
@@ -62,6 +63,11 @@ class TruncationUIExtra(BaseModel):
|
|
|
62
63
|
truncated_length: int
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
class SessionStatusUIExtra(BaseModel):
|
|
67
|
+
usage: "Usage"
|
|
68
|
+
task_count: int
|
|
69
|
+
|
|
70
|
+
|
|
65
71
|
class ToolResultUIExtra(BaseModel):
|
|
66
72
|
type: ToolResultUIExtraType
|
|
67
73
|
diff_text: str | None = None
|
|
@@ -69,6 +75,7 @@ class ToolResultUIExtra(BaseModel):
|
|
|
69
75
|
session_id: str | None = None
|
|
70
76
|
mermaid_link: MermaidLinkUIExtra | None = None
|
|
71
77
|
truncation: TruncationUIExtra | None = None
|
|
78
|
+
session_status: SessionStatusUIExtra | None = None
|
|
72
79
|
|
|
73
80
|
|
|
74
81
|
class AtPatternParseResult(BaseModel):
|
klaude_code/session/export.py
CHANGED
|
@@ -159,62 +159,60 @@ def _format_cost(cost: float) -> str:
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
def _render_metadata_item(item: model.ResponseMetadataItem) -> str:
|
|
162
|
-
#
|
|
162
|
+
# Model Name [@ Provider]
|
|
163
|
+
parts: list[str] = []
|
|
164
|
+
|
|
163
165
|
model_parts = [f'<span class="metadata-model">{_escape_html(item.model_name)}</span>']
|
|
164
166
|
if item.provider:
|
|
165
167
|
provider = _escape_html(item.provider.lower().replace(" ", "-"))
|
|
166
168
|
model_parts.append(f'<span class="metadata-provider">@{provider}</span>')
|
|
167
169
|
|
|
168
|
-
|
|
170
|
+
parts.append("".join(model_parts))
|
|
169
171
|
|
|
170
|
-
#
|
|
171
|
-
stats_parts: list[str] = []
|
|
172
|
+
# Stats
|
|
172
173
|
if item.usage:
|
|
173
174
|
u = item.usage
|
|
174
175
|
# Input with cost
|
|
175
176
|
input_stat = f"input: {_format_token_count(u.input_tokens)}"
|
|
176
177
|
if u.input_cost is not None:
|
|
177
178
|
input_stat += f"({_format_cost(u.input_cost)})"
|
|
178
|
-
|
|
179
|
+
parts.append(f'<span class="metadata-stat">{input_stat}</span>')
|
|
179
180
|
|
|
180
181
|
# Cached with cost
|
|
181
182
|
if u.cached_tokens > 0:
|
|
182
183
|
cached_stat = f"cached: {_format_token_count(u.cached_tokens)}"
|
|
183
184
|
if u.cache_read_cost is not None:
|
|
184
185
|
cached_stat += f"({_format_cost(u.cache_read_cost)})"
|
|
185
|
-
|
|
186
|
+
parts.append(f'<span class="metadata-stat">{cached_stat}</span>')
|
|
186
187
|
|
|
187
188
|
# Output with cost
|
|
188
189
|
output_stat = f"output: {_format_token_count(u.output_tokens)}"
|
|
189
190
|
if u.output_cost is not None:
|
|
190
191
|
output_stat += f"({_format_cost(u.output_cost)})"
|
|
191
|
-
|
|
192
|
+
parts.append(f'<span class="metadata-stat">{output_stat}</span>')
|
|
192
193
|
|
|
193
194
|
if u.reasoning_tokens > 0:
|
|
194
|
-
|
|
195
|
+
parts.append(
|
|
195
196
|
f'<span class="metadata-stat">thinking: {_format_token_count(u.reasoning_tokens)}</span>'
|
|
196
197
|
)
|
|
197
198
|
if u.context_usage_percent is not None:
|
|
198
|
-
|
|
199
|
+
parts.append(f'<span class="metadata-stat">context: {u.context_usage_percent:.1f}%</span>')
|
|
199
200
|
if u.throughput_tps is not None:
|
|
200
|
-
|
|
201
|
+
parts.append(f'<span class="metadata-stat">tps: {u.throughput_tps:.1f}</span>')
|
|
201
202
|
|
|
202
203
|
if item.task_duration_s is not None:
|
|
203
|
-
|
|
204
|
+
parts.append(f'<span class="metadata-stat">time: {item.task_duration_s:.1f}s</span>')
|
|
204
205
|
|
|
205
206
|
# Total cost
|
|
206
207
|
if item.usage is not None and item.usage.total_cost is not None:
|
|
207
|
-
|
|
208
|
+
parts.append(f'<span class="metadata-stat">cost: {_format_cost(item.usage.total_cost)}</span>')
|
|
208
209
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
divider = '<span class="metadata-divider">/</span>'
|
|
212
|
-
stats_html = divider.join(stats_parts)
|
|
210
|
+
divider = '<span class="metadata-divider">/</span>'
|
|
211
|
+
joined_html = divider.join(parts)
|
|
213
212
|
|
|
214
213
|
return (
|
|
215
214
|
f'<div class="response-metadata">'
|
|
216
|
-
f'<div class="metadata-line">{
|
|
217
|
-
f'<div class="metadata-line">{stats_html}</div>'
|
|
215
|
+
f'<div class="metadata-line">{joined_html}</div>'
|
|
218
216
|
f"</div>"
|
|
219
217
|
)
|
|
220
218
|
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka/800.css"
|
|
21
21
|
rel="stylesheet"
|
|
22
22
|
/>
|
|
23
|
+
<link
|
|
24
|
+
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap"
|
|
25
|
+
rel="stylesheet"
|
|
26
|
+
/>
|
|
23
27
|
<style>
|
|
24
28
|
:root {
|
|
25
29
|
--bg-body: #ededed;
|
|
@@ -34,9 +38,10 @@
|
|
|
34
38
|
--error: #dc2626;
|
|
35
39
|
--bg-error: #ffebee;
|
|
36
40
|
--bg-code: #f3f3f3;
|
|
37
|
-
--
|
|
41
|
+
--fg-inline-code: #4f4fc7;
|
|
38
42
|
--font-mono: "Iosevka", "SF Mono", Menlo, monospace;
|
|
39
|
-
--font-markdown: "
|
|
43
|
+
--font-markdown-mono: "IBM Plex Mono", Menlo, monospace;
|
|
44
|
+
--font-markdown: "IBM Plex Mono", Menlo, monospace;
|
|
40
45
|
--font-weight-bold: 800;
|
|
41
46
|
--font-size-xs: 13px;
|
|
42
47
|
--font-size-sm: 14px;
|
|
@@ -60,7 +65,7 @@
|
|
|
60
65
|
body {
|
|
61
66
|
background-color: var(--bg-body);
|
|
62
67
|
color: var(--text);
|
|
63
|
-
font-family: var(--font-
|
|
68
|
+
font-family: var(--font-mono);
|
|
64
69
|
line-height: 1.6;
|
|
65
70
|
font-size: var(--font-size-lg);
|
|
66
71
|
-webkit-font-smoothing: antialiased;
|
|
@@ -276,7 +281,7 @@
|
|
|
276
281
|
background: var(--bg-card);
|
|
277
282
|
border: 1px solid var(--border);
|
|
278
283
|
border-radius: var(--radius-md);
|
|
279
|
-
padding:
|
|
284
|
+
padding: 20px;
|
|
280
285
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
|
|
281
286
|
font-size: var(--font-size-base);
|
|
282
287
|
}
|
|
@@ -361,7 +366,7 @@
|
|
|
361
366
|
background: var(--bg-body);
|
|
362
367
|
border: 1px dashed var(--border);
|
|
363
368
|
border-radius: var(--radius-sm);
|
|
364
|
-
padding:
|
|
369
|
+
padding: 20px;
|
|
365
370
|
}
|
|
366
371
|
.assistant-message.show-raw .assistant-rendered {
|
|
367
372
|
display: none;
|
|
@@ -583,7 +588,7 @@
|
|
|
583
588
|
background: var(--bg-body);
|
|
584
589
|
border: 1px dashed var(--border);
|
|
585
590
|
border-radius: var(--radius-sm);
|
|
586
|
-
padding:
|
|
591
|
+
padding: 20px;
|
|
587
592
|
}
|
|
588
593
|
|
|
589
594
|
.subagent-content.show-raw .subagent-rendered {
|
|
@@ -596,6 +601,7 @@
|
|
|
596
601
|
/* Markdown Elements */
|
|
597
602
|
.markdown-body {
|
|
598
603
|
font-family: var(--font-markdown);
|
|
604
|
+
line-height: 1.5;
|
|
599
605
|
}
|
|
600
606
|
.markdown-body hr {
|
|
601
607
|
height: 0;
|
|
@@ -612,9 +618,8 @@
|
|
|
612
618
|
border: 1px solid var(--border);
|
|
613
619
|
}
|
|
614
620
|
.markdown-body code {
|
|
615
|
-
font-family: var(--font-markdown);
|
|
616
|
-
|
|
617
|
-
background: var(--bg-code);
|
|
621
|
+
font-family: var(--font-markdown-mono);
|
|
622
|
+
color: var(--fg-inline-code);
|
|
618
623
|
padding: 2px 4px;
|
|
619
624
|
border-radius: var(--radius-sm);
|
|
620
625
|
}
|
|
@@ -908,9 +913,81 @@
|
|
|
908
913
|
font-size: var(--font-size-sm);
|
|
909
914
|
margin-top: 2px;
|
|
910
915
|
}
|
|
916
|
+
/* TOC Sidebar */
|
|
917
|
+
.toc-sidebar {
|
|
918
|
+
position: fixed;
|
|
919
|
+
top: 33vh;
|
|
920
|
+
left: 20px;
|
|
921
|
+
width: 220px;
|
|
922
|
+
bottom: 33vh;
|
|
923
|
+
overflow-y: auto;
|
|
924
|
+
padding-right: 12px;
|
|
925
|
+
/* Vertical padding to offset mask */
|
|
926
|
+
padding-top: 30px;
|
|
927
|
+
padding-bottom: 30px;
|
|
928
|
+
display: none;
|
|
929
|
+
scrollbar-width: none;
|
|
930
|
+
z-index: 100;
|
|
931
|
+
/* Linear mask for fading edges */
|
|
932
|
+
-webkit-mask-image: linear-gradient(
|
|
933
|
+
to bottom,
|
|
934
|
+
transparent 0%,
|
|
935
|
+
black 30px,
|
|
936
|
+
black calc(100% - 30px),
|
|
937
|
+
transparent 100%
|
|
938
|
+
);
|
|
939
|
+
mask-image: linear-gradient(
|
|
940
|
+
to bottom,
|
|
941
|
+
transparent 0%,
|
|
942
|
+
black 30px,
|
|
943
|
+
black calc(100% - 30px),
|
|
944
|
+
transparent 100%
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.toc-sidebar::-webkit-scrollbar {
|
|
949
|
+
display: none;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/* Show TOC on wide screens */
|
|
953
|
+
@media (min-width: 1400px) {
|
|
954
|
+
.toc-sidebar {
|
|
955
|
+
display: block;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.toc-item {
|
|
960
|
+
display: block;
|
|
961
|
+
padding: 3px 10px;
|
|
962
|
+
margin-bottom: 1px;
|
|
963
|
+
font-family: var(--font-mono);
|
|
964
|
+
font-size: 12px;
|
|
965
|
+
line-height: 1.3;
|
|
966
|
+
color: var(--text-dim);
|
|
967
|
+
text-decoration: none;
|
|
968
|
+
cursor: pointer;
|
|
969
|
+
transition: all 0.2s;
|
|
970
|
+
white-space: nowrap;
|
|
971
|
+
overflow: hidden;
|
|
972
|
+
text-overflow: ellipsis;
|
|
973
|
+
border-radius: 4px;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.toc-item:hover {
|
|
977
|
+
color: var(--text);
|
|
978
|
+
background: rgba(0, 0, 0, 0.03);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
.toc-item.active {
|
|
982
|
+
color: var(--text);
|
|
983
|
+
background: var(--bg-card);
|
|
984
|
+
font-weight: bold;
|
|
985
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
986
|
+
}
|
|
911
987
|
</style>
|
|
912
988
|
</head>
|
|
913
989
|
<body>
|
|
990
|
+
<div id="toc-sidebar" class="toc-sidebar"></div>
|
|
914
991
|
<div class="container">
|
|
915
992
|
<div class="header">
|
|
916
993
|
<h1>Klaude Code</h1>
|
|
@@ -941,7 +1018,9 @@
|
|
|
941
1018
|
<div
|
|
942
1019
|
class="details-content system-prompt-content"
|
|
943
1020
|
style="font-family: var(--font-mono); white-space: pre-wrap"
|
|
944
|
-
|
|
1021
|
+
>
|
|
1022
|
+
$system_prompt
|
|
1023
|
+
</div>
|
|
945
1024
|
</details>
|
|
946
1025
|
|
|
947
1026
|
<details class="collapsible-section">
|
|
@@ -1215,6 +1294,138 @@
|
|
|
1215
1294
|
behavior: "smooth",
|
|
1216
1295
|
});
|
|
1217
1296
|
});
|
|
1297
|
+
|
|
1298
|
+
// TOC Logic
|
|
1299
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
1300
|
+
const tocSidebar = document.getElementById("toc-sidebar");
|
|
1301
|
+
if (!tocSidebar) return;
|
|
1302
|
+
|
|
1303
|
+
const sections = [];
|
|
1304
|
+
let userCount = 0;
|
|
1305
|
+
let assistantCount = 0;
|
|
1306
|
+
|
|
1307
|
+
// 1. System Prompt
|
|
1308
|
+
const sysPrompt = document.querySelector(
|
|
1309
|
+
".collapsible-section summary"
|
|
1310
|
+
);
|
|
1311
|
+
if (sysPrompt && sysPrompt.textContent.includes("System Prompt")) {
|
|
1312
|
+
const details = sysPrompt.parentElement;
|
|
1313
|
+
details.id = details.id || "section-system-prompt";
|
|
1314
|
+
sections.push({
|
|
1315
|
+
id: details.id,
|
|
1316
|
+
label: "System Prompt",
|
|
1317
|
+
el: details,
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// 2. Messages and Tools
|
|
1322
|
+
const stream = document.querySelector(".message-stream");
|
|
1323
|
+
if (stream) {
|
|
1324
|
+
// Use a Walker to find top-level relevant items if structure is complex
|
|
1325
|
+
// But assuming flat children of message-stream based on CSS
|
|
1326
|
+
Array.from(stream.children).forEach((child) => {
|
|
1327
|
+
let label = null;
|
|
1328
|
+
let idPrefix = "section";
|
|
1329
|
+
|
|
1330
|
+
if (child.classList.contains("message-group")) {
|
|
1331
|
+
const roleLabel = child.querySelector(".role-label");
|
|
1332
|
+
if (roleLabel) {
|
|
1333
|
+
if (roleLabel.classList.contains("user")) {
|
|
1334
|
+
userCount++;
|
|
1335
|
+
label = `USER $${userCount}`;
|
|
1336
|
+
idPrefix = "user";
|
|
1337
|
+
} else if (roleLabel.classList.contains("assistant")) {
|
|
1338
|
+
assistantCount++;
|
|
1339
|
+
label = `ASSISTANT $${assistantCount}`;
|
|
1340
|
+
idPrefix = "assistant";
|
|
1341
|
+
} else {
|
|
1342
|
+
label = roleLabel.textContent.trim();
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
} else if (child.classList.contains("tool-call")) {
|
|
1346
|
+
const toolName = child.querySelector(".tool-name");
|
|
1347
|
+
if (toolName) {
|
|
1348
|
+
label = toolName.textContent.trim();
|
|
1349
|
+
idPrefix = "tool";
|
|
1350
|
+
} else {
|
|
1351
|
+
label = "Tool";
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (label) {
|
|
1356
|
+
child.id =
|
|
1357
|
+
child.id ||
|
|
1358
|
+
`$${idPrefix}-$${Math.random().toString(36).substr(2, 9)}`;
|
|
1359
|
+
sections.push({ id: child.id, label: label, el: child });
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Render TOC
|
|
1365
|
+
sections.forEach((section) => {
|
|
1366
|
+
const item = document.createElement("div");
|
|
1367
|
+
item.className = "toc-item";
|
|
1368
|
+
item.textContent = section.label;
|
|
1369
|
+
item.dataset.target = section.id;
|
|
1370
|
+
item.addEventListener("click", () => {
|
|
1371
|
+
section.el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1372
|
+
});
|
|
1373
|
+
tocSidebar.appendChild(item);
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
// Scroll Spy with throttling
|
|
1377
|
+
let ticking = false;
|
|
1378
|
+
const offset = 150; // Pixel offset for "active" area
|
|
1379
|
+
|
|
1380
|
+
function updateActiveSection() {
|
|
1381
|
+
let currentId = sections.length > 0 ? sections[0].id : null;
|
|
1382
|
+
const scrollY = window.scrollY;
|
|
1383
|
+
|
|
1384
|
+
// We look for the last section that has passed the top threshold (+ offset)
|
|
1385
|
+
for (let i = 0; i < sections.length; i++) {
|
|
1386
|
+
const el = sections[i].el;
|
|
1387
|
+
// If element top is above the "active line" (scrollY + offset)
|
|
1388
|
+
if (el.offsetTop <= scrollY + offset) {
|
|
1389
|
+
currentId = sections[i].id;
|
|
1390
|
+
} else {
|
|
1391
|
+
// Since sections are ordered by position, we can stop once we find one below the line
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Update UI
|
|
1397
|
+
document.querySelectorAll(".toc-item").forEach((item) => {
|
|
1398
|
+
const isActive = item.dataset.target === currentId;
|
|
1399
|
+
if (item.classList.contains("active") !== isActive) {
|
|
1400
|
+
item.classList.toggle("active", isActive);
|
|
1401
|
+
if (isActive) {
|
|
1402
|
+
// Auto-scroll sidebar to visible
|
|
1403
|
+
const sidebarRect = tocSidebar.getBoundingClientRect();
|
|
1404
|
+
const itemRect = item.getBoundingClientRect();
|
|
1405
|
+
// Check if item is out of view in sidebar (accounting for mask)
|
|
1406
|
+
if (
|
|
1407
|
+
itemRect.top < sidebarRect.top + 40 ||
|
|
1408
|
+
itemRect.bottom > sidebarRect.bottom - 40
|
|
1409
|
+
) {
|
|
1410
|
+
item.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
ticking = false;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
window.addEventListener("scroll", () => {
|
|
1420
|
+
if (!ticking) {
|
|
1421
|
+
window.requestAnimationFrame(updateActiveSection);
|
|
1422
|
+
ticking = true;
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
// Initial update
|
|
1427
|
+
updateActiveSection();
|
|
1428
|
+
});
|
|
1218
1429
|
</script>
|
|
1219
1430
|
</body>
|
|
1220
1431
|
</html>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from rich.console import Group, RenderableType
|
|
2
2
|
from rich.padding import Padding
|
|
3
|
+
from rich.table import Table
|
|
3
4
|
from rich.text import Text
|
|
4
5
|
|
|
5
|
-
from klaude_code.protocol import commands, events
|
|
6
|
+
from klaude_code.protocol import commands, events, model
|
|
6
7
|
from klaude_code.ui.renderers import diffs as r_diffs
|
|
7
8
|
from klaude_code.ui.renderers.common import create_grid
|
|
8
9
|
from klaude_code.ui.renderers.tools import render_path
|
|
@@ -97,7 +98,61 @@ def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
97
98
|
return r_diffs.render_diff_panel(e.item.content, show_file_name=True)
|
|
98
99
|
case commands.CommandName.HELP:
|
|
99
100
|
return Padding.indent(Text.from_markup(e.item.content or ""), level=2)
|
|
101
|
+
case commands.CommandName.STATUS:
|
|
102
|
+
return _render_status_output(e.item.command_output)
|
|
100
103
|
case _:
|
|
101
104
|
content = e.item.content or "(no content)"
|
|
102
105
|
style = ThemeKey.TOOL_RESULT if not e.item.command_output.is_error else ThemeKey.ERROR
|
|
103
106
|
return Padding.indent(Text(truncate_display(content), style=style), level=2)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _format_tokens(tokens: int) -> str:
|
|
110
|
+
"""Format token count with K/M suffix for readability."""
|
|
111
|
+
if tokens >= 1_000_000:
|
|
112
|
+
return f"{tokens / 1_000_000:.2f}M"
|
|
113
|
+
if tokens >= 1_000:
|
|
114
|
+
return f"{tokens / 1_000:.1f}K"
|
|
115
|
+
return str(tokens)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _format_cost(cost: float | None) -> str:
|
|
119
|
+
"""Format cost in USD."""
|
|
120
|
+
if cost is None:
|
|
121
|
+
return "-"
|
|
122
|
+
if cost < 0.01:
|
|
123
|
+
return f"${cost:.4f}"
|
|
124
|
+
return f"${cost:.2f}"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _render_status_output(command_output: model.CommandOutput) -> RenderableType:
|
|
128
|
+
"""Render session status as a two-column table with sections."""
|
|
129
|
+
if not command_output.ui_extra or not command_output.ui_extra.session_status:
|
|
130
|
+
return Text("(no status data)", style=ThemeKey.TOOL_RESULT)
|
|
131
|
+
|
|
132
|
+
status = command_output.ui_extra.session_status
|
|
133
|
+
usage = status.usage
|
|
134
|
+
|
|
135
|
+
table = Table.grid(padding=(0, 2))
|
|
136
|
+
table.add_column(style=ThemeKey.TOOL_RESULT, no_wrap=True)
|
|
137
|
+
table.add_column(style=ThemeKey.TOOL_RESULT, no_wrap=True)
|
|
138
|
+
# Token Usage section
|
|
139
|
+
table.add_row(Text("Token Usage", style="bold"), "")
|
|
140
|
+
table.add_row("Input Tokens", _format_tokens(usage.input_tokens))
|
|
141
|
+
if usage.cached_tokens > 0:
|
|
142
|
+
table.add_row("Cached Tokens", _format_tokens(usage.cached_tokens))
|
|
143
|
+
if usage.reasoning_tokens > 0:
|
|
144
|
+
table.add_row("Reasoning Tokens", _format_tokens(usage.reasoning_tokens))
|
|
145
|
+
table.add_row("Output Tokens", _format_tokens(usage.output_tokens))
|
|
146
|
+
table.add_row("Total Tokens", _format_tokens(usage.total_tokens))
|
|
147
|
+
|
|
148
|
+
# Cost section
|
|
149
|
+
if usage.total_cost is not None:
|
|
150
|
+
table.add_row("", "") # Empty line
|
|
151
|
+
table.add_row(Text("Cost", style="bold"), "")
|
|
152
|
+
table.add_row("Input Cost", _format_cost(usage.input_cost))
|
|
153
|
+
if usage.cache_read_cost is not None and usage.cache_read_cost > 0:
|
|
154
|
+
table.add_row("Cache Read Cost", _format_cost(usage.cache_read_cost))
|
|
155
|
+
table.add_row("Output Cost", _format_cost(usage.output_cost))
|
|
156
|
+
table.add_row("Total Cost", _format_cost(usage.total_cost))
|
|
157
|
+
|
|
158
|
+
return Padding.indent(table, level=2)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
klaude_code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
klaude_code/cli/__init__.py,sha256=YzlAoWAr5rx5oe6B_4zPxRFS4QaZauuy1AFwampP5fg,45
|
|
3
3
|
klaude_code/cli/main.py,sha256=vQt1jZVX4pxQUYceWQcXkRO3w0glOUIhazI7uDex2fE,9279
|
|
4
|
-
klaude_code/cli/runtime.py,sha256=
|
|
4
|
+
klaude_code/cli/runtime.py,sha256=It7JwXi4MfaRHV2CaBjDmR8M8VVJEXoljXMUc7pR85Y,12347
|
|
5
5
|
klaude_code/cli/session_cmd.py,sha256=cIBm3uUurke-TfBvQHz9mGW29LOAh22FIpXVyypnwDo,2549
|
|
6
|
-
klaude_code/command/__init__.py,sha256=
|
|
6
|
+
klaude_code/command/__init__.py,sha256=ApRFYqzmDxXe-jJ0tHg4qe4XsJ4QSICu8ICQcmwxQ1Y,1125
|
|
7
7
|
klaude_code/command/clear_cmd.py,sha256=ojsDdZnv_-bDYBPecpdY85DfGicst9Jf9j7esg2z2hE,674
|
|
8
8
|
klaude_code/command/command_abc.py,sha256=WWY9qi7SILttPl6fJNi9ThevsqpGFoQJ1ecgoosd_Ms,2547
|
|
9
9
|
klaude_code/command/diff_cmd.py,sha256=306PcTeKokLmchp6mIWeZ2rokINsU5-2f4iGrjEbkCE,5269
|
|
10
|
-
klaude_code/command/export_cmd.py,sha256=
|
|
10
|
+
klaude_code/command/export_cmd.py,sha256=lsnBwxjiHyxgU7TPmT038ZVsOVqNxV2l-6U9T-dxI1g,3498
|
|
11
11
|
klaude_code/command/help_cmd.py,sha256=VddWegAJS5OjbAZ35c9Pb0SSDZpPH5PqRyQ--sdseII,1709
|
|
12
12
|
klaude_code/command/model_cmd.py,sha256=EApytBR6gl1Po9Mi7YVvDWgV-0ivgw-RQTnQ__EIvFQ,1510
|
|
13
13
|
klaude_code/command/prompt-dev-docs-update.md,sha256=g1IWIWIa-3qlNOw5mBA4N9H1_nvYcw8AKo7XoQw_AZQ,1855
|
|
@@ -16,6 +16,7 @@ klaude_code/command/prompt-init.md,sha256=a4_FQ3gKizqs2vl9oEY5jtG6HNhv3f-1b5RSCF
|
|
|
16
16
|
klaude_code/command/prompt_command.py,sha256=cDdLAyGk3JFS-nRlNzTnT8T35QQ-FC6RT0fD5ELZGO4,2405
|
|
17
17
|
klaude_code/command/refresh_cmd.py,sha256=kgpbcnqf1n_ihMW99AfTXeZEkE2_MNwgQ9DbNfl2jqY,1288
|
|
18
18
|
klaude_code/command/registry.py,sha256=OZhQqoLfWjqojJauemc8L2rnwPhYoAh2Kjx3HSAz_ZE,3554
|
|
19
|
+
klaude_code/command/status_cmd.py,sha256=mVGxyaqaP2qgjdjQ5BhweUb-Cc0VpsK8-7Pab92kKJg,3961
|
|
19
20
|
klaude_code/command/terminal_setup_cmd.py,sha256=bQfMDZIzak8emkmuaY43_2YaS7t908sUJ_orYwccTtY,10957
|
|
20
21
|
klaude_code/config/__init__.py,sha256=9XVCYYqzJtCi46I94hbUmJ2yTFuZ-UlH-QTx7OpLAkQ,292
|
|
21
22
|
klaude_code/config/config.py,sha256=Vc9u7-40T81Rbx1OdMqSWZLh3vf9aj4wmBUnIOH7jAw,6526
|
|
@@ -24,7 +25,12 @@ klaude_code/config/select_model.py,sha256=el1jqXlNyYmHH_dvdcEkWVOYqIZ9y05_VJRlfZ
|
|
|
24
25
|
klaude_code/const/__init__.py,sha256=joTUQF1NEA-OHY3fuNIjNYYxOJlBW-MtSQ3YE3EAer4,3980
|
|
25
26
|
klaude_code/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
27
|
klaude_code/core/agent.py,sha256=XCzoo0fR5WVlJjSdOURB1FgvMbgRt4xrLydPORkbqOU,6193
|
|
27
|
-
klaude_code/core/executor.py,sha256=
|
|
28
|
+
klaude_code/core/executor.py,sha256=cuFxabyChIq8VqFr1ssbAy-VByHu0qJynJfEq2iHajI,18653
|
|
29
|
+
klaude_code/core/manager/__init__.py,sha256=6CswltCHXBUcezlW7xui2S1swDp8JTkS1YiEHmq4-no,658
|
|
30
|
+
klaude_code/core/manager/agent_manager.py,sha256=5InJ1EaPKbBSLZy7vLro2B8-DbbjPRHHc4D9SuiT9Rw,4908
|
|
31
|
+
klaude_code/core/manager/llm_clients.py,sha256=Fdkd7EijC9IDnEPU622cFLeaYGojov5Z2nPyR0xhj3Q,1335
|
|
32
|
+
klaude_code/core/manager/llm_clients_builder.py,sha256=NzZKWXv8xB4GA1Hd6whEYM746atMavnI_nJaq7K5G08,1710
|
|
33
|
+
klaude_code/core/manager/sub_agent_manager.py,sha256=X6jilMg4HSIck_m032AyRUarpuux6TVI15g6L8_3T9Q,3474
|
|
28
34
|
klaude_code/core/prompt.py,sha256=EhaM-rgjamaceYOWy4J6rXYqc0M7TfnORMBCMS1h1Yk,2868
|
|
29
35
|
klaude_code/core/prompts/prompt-claude-code.md,sha256=3OY5ShcYPp8f0XrIl-3pBVIDZkweWsLzDHy5ldnSOJc,8954
|
|
30
36
|
klaude_code/core/prompts/prompt-codex.md,sha256=jNi593_4L3EoMvjS0TwltF2b684gtDBsYHa9npxO34A,24239
|
|
@@ -56,8 +62,8 @@ klaude_code/core/tool/memory/skill_tool.md,sha256=UfjJK5EGAd3mf7ym5rIrRdPyV3kBTx
|
|
|
56
62
|
klaude_code/core/tool/memory/skill_tool.py,sha256=Ma0iLQya3AndatUHfEANTDtXzhTly0AkzckPuQ1JXtA,3209
|
|
57
63
|
klaude_code/core/tool/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
64
|
klaude_code/core/tool/shell/bash_tool.md,sha256=ILKpnRCBTkU2uSDEdZQjNYo1l6hsM4TO-3RD5zWC61c,3935
|
|
59
|
-
klaude_code/core/tool/shell/bash_tool.py,sha256=
|
|
60
|
-
klaude_code/core/tool/shell/command_safety.py,sha256=
|
|
65
|
+
klaude_code/core/tool/shell/bash_tool.py,sha256=qPB7W51LmFsRxWJoqKih1vNTGIOaXE4wfxnKPzBXs6g,4490
|
|
66
|
+
klaude_code/core/tool/shell/command_safety.py,sha256=r5nTltVj1Iil7dmnYByj4jp4du7AtPEaUa4J6XdrlTo,13209
|
|
61
67
|
klaude_code/core/tool/sub_agent_tool.py,sha256=CpQAG9P6ovtDBaQ_khXNoIfGerbHJVL7UnNRtcqtX5c,2763
|
|
62
68
|
klaude_code/core/tool/todo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
69
|
klaude_code/core/tool/todo/todo_write_tool.md,sha256=AJ2OkZGcccQYDXkydPAr5JI2SExBF8qJd0rXsHySLps,9711
|
|
@@ -66,7 +72,7 @@ klaude_code/core/tool/todo/update_plan_tool.md,sha256=OoEF4voHHd5J3VDv7tq3UCHdXA
|
|
|
66
72
|
klaude_code/core/tool/todo/update_plan_tool.py,sha256=4l8BO_f0ZZTbI8JNL9mIU_lPo1Qo7E1s93Kz9CR2DuA,3840
|
|
67
73
|
klaude_code/core/tool/tool_abc.py,sha256=3FlVZ8a6hC-_Ci23_cpLaap9nHinHgxSB1TsZL5ylUQ,731
|
|
68
74
|
klaude_code/core/tool/tool_context.py,sha256=vCA3oYN_YHnINoxejsdEUfM2QxlbXRaJ3D9_udc-8wk,3749
|
|
69
|
-
klaude_code/core/tool/tool_registry.py,sha256=
|
|
75
|
+
klaude_code/core/tool/tool_registry.py,sha256=2d-5zIsODztgJe7obS1OAZbrQSqAquULcoXPLLEr2Xs,2665
|
|
70
76
|
klaude_code/core/tool/tool_runner.py,sha256=IyVjMnGVMAjs8bJuCMBfFE1eQ39FSjnOz3Wb5HHQ0T4,10259
|
|
71
77
|
klaude_code/core/tool/truncation.py,sha256=V_p4rW3msyJgw_QSgLCx74KyuCC0_eEI0-R7jTfPM-8,6439
|
|
72
78
|
klaude_code/core/tool/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -78,36 +84,36 @@ klaude_code/core/turn.py,sha256=_Jx0huYlUXXTvgEIo_LFisJnFYssdyZePELSw7d_vHg,8391
|
|
|
78
84
|
klaude_code/llm/__init__.py,sha256=iHO9DXKO3z-8CRuwKbSgyaWyRUVAcwFKN2eO68Ud6Ms,544
|
|
79
85
|
klaude_code/llm/anthropic/__init__.py,sha256=PWETvaeNAAX3ue0ww1uRUIxTJG0RpWiutkn7MlwKxBs,67
|
|
80
86
|
klaude_code/llm/anthropic/client.py,sha256=yEsp3JSVj-18y-UiYT6zQUMRg8PYJ-Ao3bSPSBiJSJM,11166
|
|
81
|
-
klaude_code/llm/anthropic/input.py,sha256=
|
|
87
|
+
klaude_code/llm/anthropic/input.py,sha256=X7zNloQR0Zzk--2LD2tCmfZNxt42-0IZ-5Ul1dPhIlQ,7458
|
|
82
88
|
klaude_code/llm/client.py,sha256=Un5LA5Z622JQ1MYEfJi7gXj3ycX3ce0WvYPcOogM32Y,1527
|
|
83
89
|
klaude_code/llm/input_common.py,sha256=yLn-g9T_lC3AnPPilFwlKEA2-vnIgyOKlHFBYB00jm8,8972
|
|
84
90
|
klaude_code/llm/openai_compatible/__init__.py,sha256=ACGpnki7k53mMcCl591aw99pm9jZOZk0ghr7atOfNps,81
|
|
85
91
|
klaude_code/llm/openai_compatible/client.py,sha256=LqwkmKZdgDJq8_zlwsb35-QRhunP-ec0VFKMr-SM_uk,9215
|
|
86
|
-
klaude_code/llm/openai_compatible/input.py,sha256=
|
|
92
|
+
klaude_code/llm/openai_compatible/input.py,sha256=AispOfSlQPmFWPgDYs2j3Q_2zwpYyZDf6oQNG8Z5q1M,3590
|
|
87
93
|
klaude_code/llm/openai_compatible/tool_call_accumulator.py,sha256=kuw3ceDgenQz2Ccc9KYqBkDo6F1sDb5Aga6m41AIECA,4071
|
|
88
94
|
klaude_code/llm/openrouter/__init__.py,sha256=_As8lHjwj6vapQhLorZttTpukk5ZiCdhFdGT38_ASPo,69
|
|
89
95
|
klaude_code/llm/openrouter/client.py,sha256=oq8VPmehAdSK9wsnKCNDRmkicEbeZ4sjy7bPfuWBNPc,9101
|
|
90
|
-
klaude_code/llm/openrouter/input.py,sha256=
|
|
96
|
+
klaude_code/llm/openrouter/input.py,sha256=JrO3T8XMRumjwDoP4sc1UKTU9K_BFot63pi83sUN-IE,5901
|
|
91
97
|
klaude_code/llm/openrouter/reasoning_handler.py,sha256=TYIHdwMopi8DVqOpeN3vpyp-GcWOZgTeRnT5QvlK70U,8100
|
|
92
98
|
klaude_code/llm/registry.py,sha256=KBKSelBLlGooInpzfVztN6OZqqfc0WzmB-cT8lE5fw8,686
|
|
93
99
|
klaude_code/llm/responses/__init__.py,sha256=WsiyvnNiIytaYcaAqNiB8GI-5zcpjjeODPbMlteeFjA,67
|
|
94
100
|
klaude_code/llm/responses/client.py,sha256=laNTjJ7WJ8hooqZ55XPfAkrurKKryMJTx4xz0AzG_6g,10663
|
|
95
|
-
klaude_code/llm/responses/input.py,sha256=
|
|
101
|
+
klaude_code/llm/responses/input.py,sha256=hnTnOGpo1-N_vsi7C3O_PJoj-Gf1cBdt9Q1sal3vhYM,6079
|
|
96
102
|
klaude_code/llm/usage.py,sha256=zDW47J-sxHhRJqDZihXeW2eyxpZS6F39qLsB8K0cua0,4240
|
|
97
103
|
klaude_code/protocol/__init__.py,sha256=aGUgzhYqvhuT3Mk2vj7lrHGriH4h9TSbqV1RsRFAZjQ,194
|
|
98
|
-
klaude_code/protocol/commands.py,sha256=
|
|
104
|
+
klaude_code/protocol/commands.py,sha256=zoqyBSpFZI8q8LP3rlr1mR-ekCufx-DfPGbzqsgx7X8,544
|
|
99
105
|
klaude_code/protocol/events.py,sha256=Pw8lY7rJLwk-FjKWtX8C_-Op7p0KWLaI-PH1_U7Uoyg,3290
|
|
100
106
|
klaude_code/protocol/llm_param.py,sha256=8_B8e83iPQs62CyVicTKlEKWBoOmRMlOOWkKBK__R58,4337
|
|
101
|
-
klaude_code/protocol/model.py,sha256=
|
|
107
|
+
klaude_code/protocol/model.py,sha256=op7xqxF8S8L1xwtI4-DWnZgUVW-9q0OUlTc_-suzuY8,7755
|
|
102
108
|
klaude_code/protocol/op.py,sha256=rkPvDRsOgPyibMI1n0pDO7ghFWJBfDGas9BnACtmfO4,2654
|
|
103
109
|
klaude_code/protocol/op_handler.py,sha256=_lnv3-RxKkrTfGTNBlQ23gbHJBEtMLC8O48SYWDtPjE,843
|
|
104
110
|
klaude_code/protocol/sub_agent.py,sha256=rD4nDX3jBp1HIseQTjd83I4VJ_fFcJ9NzkKCWO6JGsk,13351
|
|
105
111
|
klaude_code/protocol/tools.py,sha256=hkjVirnQqGTJS46IWvVKXWR4usPPUgDZDnm34LzAVSc,348
|
|
106
112
|
klaude_code/session/__init__.py,sha256=oXcDA5w-gJCbzmlF8yuWy3ezIW9DgFBNUs-gJHUJ-Rc,121
|
|
107
|
-
klaude_code/session/export.py,sha256=
|
|
113
|
+
klaude_code/session/export.py,sha256=pm4CCV8TkciGoEeCLEFYrbxj2X8BQB_IGz5plMv8jzU,23508
|
|
108
114
|
klaude_code/session/selector.py,sha256=HTboyzih7V8zbZoSsArJ-GVC1EnXTGocHJwFmZfbeSg,2567
|
|
109
115
|
klaude_code/session/session.py,sha256=KLE3QUNvt8d7Zu_Mj2x6rdHygzsUtLsvMzQbsiIOrKg,19195
|
|
110
|
-
klaude_code/session/templates/export_session.html,sha256=
|
|
116
|
+
klaude_code/session/templates/export_session.html,sha256=F25Na7bMhAB-awpABRkTiNsUcyk6sRuBblVsGn7dH4g,41029
|
|
111
117
|
klaude_code/trace/__init__.py,sha256=B-S4qdCj8W88AaC_gVmhTaejH6eLYClBVh2Q6aGAVBk,184
|
|
112
118
|
klaude_code/trace/log.py,sha256=0K0uek2KWq0zkUZijcO-TBM6STLe-h_8IIW0lx7V7ZA,4865
|
|
113
119
|
klaude_code/ui/__init__.py,sha256=AL1Ro0u8ObNPMj4sci_U4zakG7IpnI3bie-C63E2QPI,2873
|
|
@@ -131,7 +137,7 @@ klaude_code/ui/modes/repl/renderer.py,sha256=o1QubSm9lej-bVZ1G3ie-5qRxgPMDUo42kC
|
|
|
131
137
|
klaude_code/ui/renderers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
132
138
|
klaude_code/ui/renderers/assistant.py,sha256=Dxy6v4pX28RyWhnrjBteY8_NvDIi_jQa-j0mWt-eqWY,569
|
|
133
139
|
klaude_code/ui/renderers/common.py,sha256=TPH7LCbeJGqB8ArTsVitqJHEyOxHU6nwnRtvF04nLJ4,184
|
|
134
|
-
klaude_code/ui/renderers/developer.py,sha256
|
|
140
|
+
klaude_code/ui/renderers/developer.py,sha256=-pyy3Fj3M-Agsv3YsqGa6mMru1IoEQa40n6YkLJ4fqs,6019
|
|
135
141
|
klaude_code/ui/renderers/diffs.py,sha256=P--aLjvZy4z77FDx6uM9LlIYVjYlyZwj0MncdJTO2AA,7691
|
|
136
142
|
klaude_code/ui/renderers/errors.py,sha256=c_fbnoNOnvuI3Bb24IujwV8Mpes-qWS_xCWfAcBvg6A,517
|
|
137
143
|
klaude_code/ui/renderers/metadata.py,sha256=KSF6z4LHM_eBtaSPTLSIMK7GI-1MU6FUHaT2Hv4z8tk,6985
|
|
@@ -155,7 +161,7 @@ klaude_code/ui/utils/__init__.py,sha256=YEsCLjbCPaPza-UXTPUMTJTrc9BmNBUP5CbFWlsh
|
|
|
155
161
|
klaude_code/ui/utils/common.py,sha256=xzw-Mgj0agxrf22QxpH7YzVIpkMXIRY6SgXWtLYF0yU,2881
|
|
156
162
|
klaude_code/ui/utils/debouncer.py,sha256=TFF1z7B7-FxONEigkYohhShDlqo4cOcqydE9zz7JBHc,1270
|
|
157
163
|
klaude_code/version.py,sha256=QDgEqSFhyTSiABQFQc9VfujMPQcdjgoCaYTQsr94x0Q,4752
|
|
158
|
-
klaude_code-1.2.
|
|
159
|
-
klaude_code-1.2.
|
|
160
|
-
klaude_code-1.2.
|
|
161
|
-
klaude_code-1.2.
|
|
164
|
+
klaude_code-1.2.5.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
165
|
+
klaude_code-1.2.5.dist-info/entry_points.txt,sha256=7CWKjolvs6dZiYHpelhA_FRJ-sVDh43eu3iWuOhKc_w,53
|
|
166
|
+
klaude_code-1.2.5.dist-info/METADATA,sha256=SGt02gNURHOMgSv4hczVgvyZf4jrI-rbfgGtwEX49qY,5140
|
|
167
|
+
klaude_code-1.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|