klaude-code 1.7.0__py3-none-any.whl → 1.7.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/cli/main.py +10 -0
- klaude_code/cli/runtime.py +2 -2
- klaude_code/command/fork_session_cmd.py +7 -0
- klaude_code/config/assets/builtin_config.yaml +24 -0
- klaude_code/config/config.py +5 -0
- klaude_code/const.py +17 -2
- klaude_code/core/executor.py +16 -3
- klaude_code/core/task.py +5 -3
- klaude_code/core/tool/shell/command_safety.py +3 -5
- klaude_code/protocol/events.py +1 -0
- klaude_code/session/export.py +14 -2
- klaude_code/session/session.py +49 -2
- klaude_code/session/store.py +3 -0
- klaude_code/session/templates/export_session.html +210 -18
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +6 -46
- klaude_code/ui/modes/repl/renderer.py +5 -1
- klaude_code/ui/renderers/developer.py +1 -1
- klaude_code/ui/renderers/sub_agent.py +1 -1
- {klaude_code-1.7.0.dist-info → klaude_code-1.7.1.dist-info}/METADATA +50 -6
- {klaude_code-1.7.0.dist-info → klaude_code-1.7.1.dist-info}/RECORD +22 -22
- {klaude_code-1.7.0.dist-info → klaude_code-1.7.1.dist-info}/WHEEL +0 -0
- {klaude_code-1.7.0.dist-info → klaude_code-1.7.1.dist-info}/entry_points.txt +0 -0
klaude_code/cli/main.py
CHANGED
|
@@ -95,10 +95,20 @@ def read_input_content(cli_argument: str) -> str | None:
|
|
|
95
95
|
return content
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
ENV_HELP = """\
|
|
99
|
+
Environment Variables:
|
|
100
|
+
|
|
101
|
+
KLAUDE_READ_GLOBAL_LINE_CAP Max lines to read (default: 2000)
|
|
102
|
+
|
|
103
|
+
KLAUDE_READ_MAX_CHARS Max total chars to read (default: 50000)
|
|
104
|
+
"""
|
|
105
|
+
|
|
98
106
|
app = typer.Typer(
|
|
99
107
|
add_completion=False,
|
|
100
108
|
pretty_exceptions_enable=False,
|
|
101
109
|
no_args_is_help=False,
|
|
110
|
+
rich_markup_mode="rich",
|
|
111
|
+
epilog=ENV_HELP,
|
|
102
112
|
)
|
|
103
113
|
|
|
104
114
|
# Register subcommands from modules
|
klaude_code/cli/runtime.py
CHANGED
|
@@ -379,7 +379,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
379
379
|
model_name=model_name,
|
|
380
380
|
save_as_default=False,
|
|
381
381
|
defer_thinking_selection=True,
|
|
382
|
-
emit_welcome_event=
|
|
382
|
+
emit_welcome_event=True,
|
|
383
383
|
emit_switch_message=False,
|
|
384
384
|
)
|
|
385
385
|
)
|
|
@@ -398,7 +398,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
398
398
|
op.ChangeThinkingOperation(
|
|
399
399
|
session_id=sid,
|
|
400
400
|
thinking=thinking,
|
|
401
|
-
emit_welcome_event=
|
|
401
|
+
emit_welcome_event=True,
|
|
402
402
|
emit_switch_message=False,
|
|
403
403
|
)
|
|
404
404
|
)
|
|
@@ -7,6 +7,7 @@ from prompt_toolkit.styles import Style
|
|
|
7
7
|
|
|
8
8
|
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
9
9
|
from klaude_code.protocol import commands, events, model
|
|
10
|
+
from klaude_code.ui.modes.repl.clipboard import copy_to_clipboard
|
|
10
11
|
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
11
12
|
|
|
12
13
|
FORK_SELECT_STYLE = Style(
|
|
@@ -215,6 +216,9 @@ class ForkSessionCommand(CommandABC):
|
|
|
215
216
|
new_session = agent.session.fork()
|
|
216
217
|
await new_session.wait_for_flush()
|
|
217
218
|
|
|
219
|
+
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
220
|
+
copy_to_clipboard(resume_cmd)
|
|
221
|
+
|
|
218
222
|
event = events.DeveloperMessageEvent(
|
|
219
223
|
session_id=agent.session.id,
|
|
220
224
|
item=model.DeveloperMessageItem(
|
|
@@ -247,6 +251,9 @@ class ForkSessionCommand(CommandABC):
|
|
|
247
251
|
# Build result message
|
|
248
252
|
fork_description = "entire conversation" if selected is None else f"up to message index {selected}"
|
|
249
253
|
|
|
254
|
+
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
255
|
+
copy_to_clipboard(resume_cmd)
|
|
256
|
+
|
|
250
257
|
event = events.DeveloperMessageEvent(
|
|
251
258
|
session_id=agent.session.id,
|
|
252
259
|
item=model.DeveloperMessageItem(
|
|
@@ -87,6 +87,30 @@ provider_list:
|
|
|
87
87
|
input: 1.75
|
|
88
88
|
output: 14.0
|
|
89
89
|
cache_read: 0.17
|
|
90
|
+
- model_name: gpt-5.2-medium
|
|
91
|
+
model_params:
|
|
92
|
+
model: openai/gpt-5.2
|
|
93
|
+
max_tokens: 128000
|
|
94
|
+
context_limit: 400000
|
|
95
|
+
verbosity: high
|
|
96
|
+
thinking:
|
|
97
|
+
reasoning_effort: medium
|
|
98
|
+
cost:
|
|
99
|
+
input: 1.75
|
|
100
|
+
output: 14.0
|
|
101
|
+
cache_read: 0.17
|
|
102
|
+
- model_name: gpt-5.2-low
|
|
103
|
+
model_params:
|
|
104
|
+
model: openai/gpt-5.2
|
|
105
|
+
max_tokens: 128000
|
|
106
|
+
context_limit: 400000
|
|
107
|
+
verbosity: low
|
|
108
|
+
thinking:
|
|
109
|
+
reasoning_effort: low
|
|
110
|
+
cost:
|
|
111
|
+
input: 1.75
|
|
112
|
+
output: 14.0
|
|
113
|
+
cache_read: 0.17
|
|
90
114
|
- model_name: gpt-5.2-fast
|
|
91
115
|
model_params:
|
|
92
116
|
model: openai/gpt-5.2
|
klaude_code/config/config.py
CHANGED
klaude_code/const.py
CHANGED
|
@@ -4,8 +4,21 @@ This module consolidates all magic numbers and configuration values
|
|
|
4
4
|
that were previously scattered across the codebase.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import os
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
10
|
+
|
|
11
|
+
def _get_int_env(name: str, default: int) -> int:
|
|
12
|
+
"""Get an integer value from environment variable, or return default."""
|
|
13
|
+
val = os.environ.get(name)
|
|
14
|
+
if val is None:
|
|
15
|
+
return default
|
|
16
|
+
try:
|
|
17
|
+
return int(val)
|
|
18
|
+
except ValueError:
|
|
19
|
+
return default
|
|
20
|
+
|
|
21
|
+
|
|
9
22
|
# =============================================================================
|
|
10
23
|
# Agent Configuration
|
|
11
24
|
# =============================================================================
|
|
@@ -47,10 +60,12 @@ TODO_REMINDER_TOOL_CALL_THRESHOLD = 10
|
|
|
47
60
|
READ_CHAR_LIMIT_PER_LINE = 2000
|
|
48
61
|
|
|
49
62
|
# Maximum number of lines to read from a file
|
|
50
|
-
|
|
63
|
+
# Can be overridden via KLAUDE_READ_GLOBAL_LINE_CAP environment variable
|
|
64
|
+
READ_GLOBAL_LINE_CAP = _get_int_env("KLAUDE_READ_GLOBAL_LINE_CAP", 2000)
|
|
51
65
|
|
|
52
66
|
# Maximum total characters to read (truncates beyond this limit)
|
|
53
|
-
|
|
67
|
+
# Can be overridden via KLAUDE_READ_MAX_CHARS environment variable
|
|
68
|
+
READ_MAX_CHARS = _get_int_env("KLAUDE_READ_MAX_CHARS", 50000)
|
|
54
69
|
|
|
55
70
|
# Maximum image file size in bytes (4MB)
|
|
56
71
|
READ_MAX_IMAGE_BYTES = 4 * 1024 * 1024
|
klaude_code/core/executor.py
CHANGED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import subprocess
|
|
12
|
+
import sys
|
|
12
13
|
from collections.abc import Callable
|
|
13
14
|
from dataclasses import dataclass
|
|
14
15
|
from pathlib import Path
|
|
@@ -427,14 +428,26 @@ class ExecutorContext:
|
|
|
427
428
|
return build_export_html(agent.session, system_prompt, tool_schemas, model_name)
|
|
428
429
|
|
|
429
430
|
def _open_file(self, path: Path) -> None:
|
|
431
|
+
# Select platform-appropriate command
|
|
432
|
+
if sys.platform == "darwin":
|
|
433
|
+
cmd = "open"
|
|
434
|
+
elif sys.platform == "win32":
|
|
435
|
+
cmd = "start"
|
|
436
|
+
else:
|
|
437
|
+
cmd = "xdg-open"
|
|
438
|
+
|
|
430
439
|
try:
|
|
431
440
|
# Detach stdin to prevent interference with prompt_toolkit's terminal state
|
|
432
|
-
|
|
441
|
+
if sys.platform == "win32":
|
|
442
|
+
# Windows 'start' requires shell=True
|
|
443
|
+
subprocess.run(f'start "" "{path}"', shell=True, stdin=subprocess.DEVNULL, check=True)
|
|
444
|
+
else:
|
|
445
|
+
subprocess.run([cmd, str(path)], stdin=subprocess.DEVNULL, check=True)
|
|
433
446
|
except FileNotFoundError as exc: # pragma: no cover
|
|
434
|
-
msg = "`
|
|
447
|
+
msg = f"`{cmd}` command not found; please open the HTML manually."
|
|
435
448
|
raise RuntimeError(msg) from exc
|
|
436
449
|
except subprocess.CalledProcessError as exc: # pragma: no cover
|
|
437
|
-
msg = f"Failed to open HTML with `
|
|
450
|
+
msg = f"Failed to open HTML with `{cmd}`: {exc}"
|
|
438
451
|
raise RuntimeError(msg) from exc
|
|
439
452
|
|
|
440
453
|
async def handle_interrupt(self, operation: op.InterruptOperation) -> None:
|
klaude_code/core/task.py
CHANGED
|
@@ -220,7 +220,9 @@ class TaskExecutor:
|
|
|
220
220
|
error_msg = f"Retrying {attempt + 1}/{const.MAX_FAILED_TURN_RETRIES} in {delay:.1f}s"
|
|
221
221
|
if last_error_message:
|
|
222
222
|
error_msg = f"{error_msg} - {last_error_message}"
|
|
223
|
-
yield events.ErrorEvent(
|
|
223
|
+
yield events.ErrorEvent(
|
|
224
|
+
error_message=error_msg, can_retry=True, session_id=session_ctx.session_id
|
|
225
|
+
)
|
|
224
226
|
await asyncio.sleep(delay)
|
|
225
227
|
finally:
|
|
226
228
|
self._current_turn = None
|
|
@@ -234,7 +236,7 @@ class TaskExecutor:
|
|
|
234
236
|
final_error = f"Turn failed after {const.MAX_FAILED_TURN_RETRIES} retries."
|
|
235
237
|
if last_error_message:
|
|
236
238
|
final_error = f"{last_error_message}\n{final_error}"
|
|
237
|
-
yield events.ErrorEvent(error_message=final_error, can_retry=False)
|
|
239
|
+
yield events.ErrorEvent(error_message=final_error, can_retry=False, session_id=session_ctx.session_id)
|
|
238
240
|
return
|
|
239
241
|
|
|
240
242
|
if turn is None or turn.task_finished:
|
|
@@ -244,7 +246,7 @@ class TaskExecutor:
|
|
|
244
246
|
error_msg = "Sub-agent returned empty result, retrying..."
|
|
245
247
|
else:
|
|
246
248
|
error_msg = "Agent returned empty result, retrying..."
|
|
247
|
-
yield events.ErrorEvent(error_message=error_msg, can_retry=True)
|
|
249
|
+
yield events.ErrorEvent(error_message=error_msg, can_retry=True, session_id=session_ctx.session_id)
|
|
248
250
|
continue
|
|
249
251
|
break
|
|
250
252
|
|
|
@@ -275,12 +275,10 @@ def _is_safe_argv(argv: list[str]) -> SafetyCheckResult:
|
|
|
275
275
|
"tag",
|
|
276
276
|
"clone",
|
|
277
277
|
"worktree",
|
|
278
|
+
"push",
|
|
279
|
+
"pull",
|
|
280
|
+
"remote",
|
|
278
281
|
}
|
|
279
|
-
# Block remote operations
|
|
280
|
-
blocked_git_cmds = {"push", "pull", "remote"}
|
|
281
|
-
|
|
282
|
-
if sub in blocked_git_cmds:
|
|
283
|
-
return SafetyCheckResult(False, f"git: Remote operation '{sub}' not allowed")
|
|
284
282
|
if sub not in allowed_git_cmds:
|
|
285
283
|
return SafetyCheckResult(False, f"git: Subcommand '{sub}' not in allow list")
|
|
286
284
|
return SafetyCheckResult(True)
|
klaude_code/protocol/events.py
CHANGED
klaude_code/session/export.py
CHANGED
|
@@ -308,13 +308,17 @@ def _try_render_todo_args(arguments: str, tool_name: str) -> str | None:
|
|
|
308
308
|
return None
|
|
309
309
|
|
|
310
310
|
|
|
311
|
-
def _render_sub_agent_result(content: str) -> str:
|
|
311
|
+
def _render_sub_agent_result(content: str, description: str | None = None) -> str:
|
|
312
312
|
# Try to format as JSON for better readability
|
|
313
313
|
try:
|
|
314
314
|
parsed = json.loads(content)
|
|
315
315
|
formatted = "```json\n" + json.dumps(parsed, ensure_ascii=False, indent=2) + "\n```"
|
|
316
316
|
except (json.JSONDecodeError, TypeError):
|
|
317
317
|
formatted = content
|
|
318
|
+
|
|
319
|
+
if description:
|
|
320
|
+
formatted = f"# {description}\n\n{formatted}"
|
|
321
|
+
|
|
318
322
|
encoded = _escape_html(formatted)
|
|
319
323
|
return (
|
|
320
324
|
f'<div class="sub-agent-result-container">'
|
|
@@ -628,7 +632,15 @@ def _format_tool_call(tool_call: model.ToolCallItem, result: model.ToolResultIte
|
|
|
628
632
|
|
|
629
633
|
if result.output and not should_hide_text:
|
|
630
634
|
if is_sub_agent_tool(tool_call.name):
|
|
631
|
-
|
|
635
|
+
description = None
|
|
636
|
+
try:
|
|
637
|
+
args = json.loads(tool_call.arguments)
|
|
638
|
+
if isinstance(args, dict):
|
|
639
|
+
typed_args = cast(dict[str, Any], args)
|
|
640
|
+
description = cast(str | None, typed_args.get("description"))
|
|
641
|
+
except (json.JSONDecodeError, TypeError):
|
|
642
|
+
pass
|
|
643
|
+
items_to_render.append(_render_sub_agent_result(result.output, description))
|
|
632
644
|
else:
|
|
633
645
|
items_to_render.append(_render_text_block(result.output))
|
|
634
646
|
|
klaude_code/session/session.py
CHANGED
|
@@ -62,6 +62,7 @@ class Session(BaseModel):
|
|
|
62
62
|
need_todo_not_used_cooldown_counter: int = Field(exclude=True, default=0)
|
|
63
63
|
|
|
64
64
|
_messages_count_cache: int | None = PrivateAttr(default=None)
|
|
65
|
+
_user_messages_cache: list[str] | None = PrivateAttr(default=None)
|
|
65
66
|
_store: JsonlSessionStore = PrivateAttr(default_factory=get_default_store)
|
|
66
67
|
|
|
67
68
|
@property
|
|
@@ -78,6 +79,20 @@ class Session(BaseModel):
|
|
|
78
79
|
def _invalidate_messages_count_cache(self) -> None:
|
|
79
80
|
self._messages_count_cache = None
|
|
80
81
|
|
|
82
|
+
@property
|
|
83
|
+
def user_messages(self) -> list[str]:
|
|
84
|
+
"""All user message contents in this session.
|
|
85
|
+
|
|
86
|
+
This is used for session selection UI and search, and is also persisted
|
|
87
|
+
in meta.json to avoid scanning events.jsonl for every session.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
if self._user_messages_cache is None:
|
|
91
|
+
self._user_messages_cache = [
|
|
92
|
+
it.content for it in self.conversation_history if isinstance(it, model.UserMessageItem) and it.content
|
|
93
|
+
]
|
|
94
|
+
return self._user_messages_cache
|
|
95
|
+
|
|
81
96
|
@staticmethod
|
|
82
97
|
def _project_key() -> str:
|
|
83
98
|
return _project_key_from_cwd()
|
|
@@ -178,6 +193,18 @@ class Session(BaseModel):
|
|
|
178
193
|
self.conversation_history.extend(items)
|
|
179
194
|
self._invalidate_messages_count_cache()
|
|
180
195
|
|
|
196
|
+
new_user_messages = [
|
|
197
|
+
it.content for it in items if isinstance(it, model.UserMessageItem) and it.content
|
|
198
|
+
]
|
|
199
|
+
if new_user_messages:
|
|
200
|
+
if self._user_messages_cache is None:
|
|
201
|
+
# Build from full history once to ensure correctness when resuming older sessions.
|
|
202
|
+
self._user_messages_cache = [
|
|
203
|
+
it.content for it in self.conversation_history if isinstance(it, model.UserMessageItem) and it.content
|
|
204
|
+
]
|
|
205
|
+
else:
|
|
206
|
+
self._user_messages_cache.extend(new_user_messages)
|
|
207
|
+
|
|
181
208
|
if self.created_at <= 0:
|
|
182
209
|
self.created_at = time.time()
|
|
183
210
|
self.updated_at = time.time()
|
|
@@ -188,6 +215,7 @@ class Session(BaseModel):
|
|
|
188
215
|
sub_agent_state=self.sub_agent_state,
|
|
189
216
|
file_tracker=self.file_tracker,
|
|
190
217
|
todos=list(self.todos),
|
|
218
|
+
user_messages=self.user_messages,
|
|
191
219
|
created_at=self.created_at,
|
|
192
220
|
updated_at=self.updated_at,
|
|
193
221
|
messages_count=self.messages_count,
|
|
@@ -311,7 +339,7 @@ class Session(BaseModel):
|
|
|
311
339
|
case model.DeveloperMessageItem() as dm:
|
|
312
340
|
yield events.DeveloperMessageEvent(session_id=self.id, item=dm)
|
|
313
341
|
case model.StreamErrorItem() as se:
|
|
314
|
-
yield events.ErrorEvent(error_message=se.error, can_retry=False)
|
|
342
|
+
yield events.ErrorEvent(error_message=se.error, can_retry=False, session_id=self.id)
|
|
315
343
|
case _:
|
|
316
344
|
continue
|
|
317
345
|
prev_item = it
|
|
@@ -378,6 +406,17 @@ class Session(BaseModel):
|
|
|
378
406
|
pass
|
|
379
407
|
return messages
|
|
380
408
|
|
|
409
|
+
def _maybe_backfill_user_messages(*, meta_path: Path, meta: dict[str, Any], user_messages: list[str]) -> None:
|
|
410
|
+
if isinstance(meta.get("user_messages"), list):
|
|
411
|
+
return
|
|
412
|
+
meta["user_messages"] = user_messages
|
|
413
|
+
try:
|
|
414
|
+
tmp_path = meta_path.with_suffix(".json.tmp")
|
|
415
|
+
tmp_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
416
|
+
tmp_path.replace(meta_path)
|
|
417
|
+
except OSError:
|
|
418
|
+
return
|
|
419
|
+
|
|
381
420
|
items: list[Session.SessionMetaBrief] = []
|
|
382
421
|
for meta_path in store.iter_meta_files():
|
|
383
422
|
data = _read_json_dict(meta_path)
|
|
@@ -390,7 +429,15 @@ class Session(BaseModel):
|
|
|
390
429
|
created = float(data.get("created_at", meta_path.stat().st_mtime))
|
|
391
430
|
updated = float(data.get("updated_at", meta_path.stat().st_mtime))
|
|
392
431
|
work_dir = str(data.get("work_dir", ""))
|
|
393
|
-
|
|
432
|
+
|
|
433
|
+
user_messages_raw = data.get("user_messages")
|
|
434
|
+
if isinstance(user_messages_raw, list) and all(
|
|
435
|
+
isinstance(m, str) for m in cast(list[object], user_messages_raw)
|
|
436
|
+
):
|
|
437
|
+
user_messages = cast(list[str], user_messages_raw)
|
|
438
|
+
else:
|
|
439
|
+
user_messages = _get_user_messages(sid)
|
|
440
|
+
_maybe_backfill_user_messages(meta_path=meta_path, meta=data, user_messages=user_messages)
|
|
394
441
|
messages_count = int(data.get("messages_count", -1))
|
|
395
442
|
model_name = data.get("model_name") if isinstance(data.get("model_name"), str) else None
|
|
396
443
|
|
klaude_code/session/store.py
CHANGED
|
@@ -193,6 +193,7 @@ def build_meta_snapshot(
|
|
|
193
193
|
sub_agent_state: model.SubAgentState | None,
|
|
194
194
|
file_tracker: dict[str, model.FileStatus],
|
|
195
195
|
todos: list[model.TodoItem],
|
|
196
|
+
user_messages: list[str],
|
|
196
197
|
created_at: float,
|
|
197
198
|
updated_at: float,
|
|
198
199
|
messages_count: int,
|
|
@@ -206,6 +207,8 @@ def build_meta_snapshot(
|
|
|
206
207
|
"sub_agent_state": sub_agent_state.model_dump(mode="json") if sub_agent_state else None,
|
|
207
208
|
"file_tracker": {path: status.model_dump(mode="json") for path, status in file_tracker.items()},
|
|
208
209
|
"todos": [todo.model_dump(mode="json", exclude_defaults=True) for todo in todos],
|
|
210
|
+
# Cache user messages to avoid scanning events.jsonl during session listing.
|
|
211
|
+
"user_messages": list(user_messages),
|
|
209
212
|
"created_at": created_at,
|
|
210
213
|
"updated_at": updated_at,
|
|
211
214
|
"messages_count": messages_count,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title
|
|
6
|
+
<title>$first_user_message - Klaude Code</title>
|
|
7
7
|
<link
|
|
8
8
|
rel="icon"
|
|
9
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>"
|
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
href="https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono/700.css"
|
|
17
17
|
rel="stylesheet"
|
|
18
18
|
/>
|
|
19
|
+
<link
|
|
20
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/inter@5.1.0/400.min.css"
|
|
21
|
+
rel="stylesheet"
|
|
22
|
+
/>
|
|
23
|
+
<link
|
|
24
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/inter@5.1.0/400-italic.min.css"
|
|
25
|
+
rel="stylesheet"
|
|
26
|
+
/>
|
|
27
|
+
<link
|
|
28
|
+
href="https://cdn.jsdelivr.net/npm/@fontsource/inter@5.1.0/700.min.css"
|
|
29
|
+
rel="stylesheet"
|
|
30
|
+
/>
|
|
19
31
|
<style>
|
|
20
32
|
@font-face {
|
|
21
33
|
font-family: 'TX-02';
|
|
@@ -44,9 +56,10 @@
|
|
|
44
56
|
--success: #1a7f37;
|
|
45
57
|
--error: #c62828;
|
|
46
58
|
--fg-inline-code: #1f4fbf;
|
|
59
|
+
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
47
60
|
--font-mono: "TX-02", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
48
61
|
--font-markdown-mono: "TX-02", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
|
49
|
-
--font-markdown:
|
|
62
|
+
--font-markdown: var(--font-sans);
|
|
50
63
|
--font-weight-bold: 700;
|
|
51
64
|
--font-size-xs: 12px;
|
|
52
65
|
--font-size-sm: 13px;
|
|
@@ -70,7 +83,7 @@
|
|
|
70
83
|
body {
|
|
71
84
|
background-color: var(--bg-body);
|
|
72
85
|
color: var(--text);
|
|
73
|
-
font-family: var(--font-
|
|
86
|
+
font-family: var(--font-sans);
|
|
74
87
|
line-height: 1.6;
|
|
75
88
|
font-size: var(--font-size-lg);
|
|
76
89
|
-webkit-font-smoothing: antialiased;
|
|
@@ -95,6 +108,7 @@
|
|
|
95
108
|
margin-bottom: 16px;
|
|
96
109
|
color: var(--text);
|
|
97
110
|
display: inline-block;
|
|
111
|
+
font-family: var(--font-mono);
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
.meta-grid {
|
|
@@ -114,6 +128,7 @@
|
|
|
114
128
|
font-weight: var(--font-weight-bold);
|
|
115
129
|
text-transform: uppercase;
|
|
116
130
|
color: var(--text-dim);
|
|
131
|
+
font-family: var(--font-mono);
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
.meta-value {
|
|
@@ -533,6 +548,7 @@
|
|
|
533
548
|
|
|
534
549
|
.tool-name {
|
|
535
550
|
font-weight: var(--font-weight-bold);
|
|
551
|
+
font-family: var(--font-sans);
|
|
536
552
|
color: var(--accent);
|
|
537
553
|
}
|
|
538
554
|
.tool-id {
|
|
@@ -677,6 +693,66 @@
|
|
|
677
693
|
display: block;
|
|
678
694
|
}
|
|
679
695
|
|
|
696
|
+
.sub-agent-rendered h1 {
|
|
697
|
+
font-size: 1.5em;
|
|
698
|
+
border-bottom: 1px solid var(--border);
|
|
699
|
+
padding-bottom: 0.3em;
|
|
700
|
+
margin-bottom: 16px;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/* Collapsible Markdown Headings */
|
|
704
|
+
.markdown-body details {
|
|
705
|
+
margin-top: 1em;
|
|
706
|
+
margin-bottom: 1em;
|
|
707
|
+
}
|
|
708
|
+
.markdown-body details > summary {
|
|
709
|
+
cursor: pointer;
|
|
710
|
+
list-style: none; /* Modern browsers */
|
|
711
|
+
position: relative;
|
|
712
|
+
padding-left: 1.5em; /* Space for triangle */
|
|
713
|
+
display: flex;
|
|
714
|
+
align-items: baseline;
|
|
715
|
+
gap: 8px;
|
|
716
|
+
margin-top: 24px;
|
|
717
|
+
margin-bottom: 16px;
|
|
718
|
+
}
|
|
719
|
+
.markdown-body details > summary::-webkit-details-marker {
|
|
720
|
+
display: none;
|
|
721
|
+
}
|
|
722
|
+
/* Triangle indicator */
|
|
723
|
+
.markdown-body details > summary::before {
|
|
724
|
+
content: "▶";
|
|
725
|
+
position: absolute;
|
|
726
|
+
left: 0;
|
|
727
|
+
/* Center vertically relative to line-height */
|
|
728
|
+
top: 0.2em;
|
|
729
|
+
font-size: 0.8em;
|
|
730
|
+
color: var(--text-dim);
|
|
731
|
+
transition: transform 0.2s;
|
|
732
|
+
line-height: inherit;
|
|
733
|
+
flex-shrink: 0;
|
|
734
|
+
}
|
|
735
|
+
.markdown-body details[open] > summary::before {
|
|
736
|
+
transform: rotate(90deg);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/* Indent content to align with triangle */
|
|
740
|
+
.markdown-body details > *:not(summary) {
|
|
741
|
+
margin-left: 1.5em;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/* Reset margins for headings inside summary to avoid double spacing */
|
|
745
|
+
.markdown-body details > summary > h2,
|
|
746
|
+
.markdown-body details > summary > h3,
|
|
747
|
+
.markdown-body details > summary > h4,
|
|
748
|
+
.markdown-body details > summary > h5,
|
|
749
|
+
.markdown-body details > summary > h6 {
|
|
750
|
+
margin-top: 0;
|
|
751
|
+
margin-bottom: 0;
|
|
752
|
+
flex-grow: 1;
|
|
753
|
+
width: 100%;
|
|
754
|
+
}
|
|
755
|
+
|
|
680
756
|
/* Markdown Elements */
|
|
681
757
|
.markdown-body {
|
|
682
758
|
font-family: var(--font-markdown);
|
|
@@ -702,6 +778,37 @@
|
|
|
702
778
|
line-height: 1.25;
|
|
703
779
|
}
|
|
704
780
|
|
|
781
|
+
.markdown-body h1 {
|
|
782
|
+
font-size: 1.75em;
|
|
783
|
+
background: #E8E6E1;
|
|
784
|
+
padding: 6px 12px;
|
|
785
|
+
border-radius: var(--radius-md);
|
|
786
|
+
display: inline-block;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.markdown-body h2 {
|
|
790
|
+
font-size: 1.35em;
|
|
791
|
+
padding-bottom: 0.2em;
|
|
792
|
+
border-bottom: 1px solid var(--border);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.markdown-body h3 {
|
|
796
|
+
font-size: 1.15em;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.markdown-body h4 {
|
|
800
|
+
font-size: 1em;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.markdown-body h5 {
|
|
804
|
+
font-size: 0.9em;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.markdown-body h6 {
|
|
808
|
+
font-size: 0.85em;
|
|
809
|
+
color: var(--text-dim);
|
|
810
|
+
}
|
|
811
|
+
|
|
705
812
|
.markdown-body a {
|
|
706
813
|
color: var(--accent);
|
|
707
814
|
text-decoration: none;
|
|
@@ -1143,10 +1250,10 @@
|
|
|
1143
1250
|
/* TOC Sidebar */
|
|
1144
1251
|
.toc-sidebar {
|
|
1145
1252
|
position: fixed;
|
|
1146
|
-
top:
|
|
1253
|
+
top: 20vh;
|
|
1147
1254
|
left: 20px;
|
|
1148
1255
|
width: 220px;
|
|
1149
|
-
bottom:
|
|
1256
|
+
bottom: 20vh;
|
|
1150
1257
|
overflow-y: auto;
|
|
1151
1258
|
padding-right: 12px;
|
|
1152
1259
|
/* Vertical padding to offset mask */
|
|
@@ -1219,12 +1326,6 @@
|
|
|
1219
1326
|
<div class="header">
|
|
1220
1327
|
<h1>Klaude Code</h1>
|
|
1221
1328
|
<div class="meta-grid">
|
|
1222
|
-
<div class="meta-item">
|
|
1223
|
-
<span class="meta-label">First Message</span>
|
|
1224
|
-
<span class="meta-value" title="$first_user_message"
|
|
1225
|
-
>$first_user_message</span
|
|
1226
|
-
>
|
|
1227
|
-
</div>
|
|
1228
1329
|
<div class="meta-item">
|
|
1229
1330
|
<span class="meta-label">Model</span>
|
|
1230
1331
|
<span class="meta-value">$model_name</span>
|
|
@@ -1308,9 +1409,12 @@
|
|
|
1308
1409
|
mermaid.initialize({
|
|
1309
1410
|
startOnLoad: true,
|
|
1310
1411
|
theme: "neutral",
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1412
|
+
themeVariables: {
|
|
1413
|
+
fontFamily:
|
|
1414
|
+
markdownMonoFont ||
|
|
1415
|
+
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace",
|
|
1416
|
+
},
|
|
1417
|
+
securityLevel: "loose",
|
|
1314
1418
|
});
|
|
1315
1419
|
</script>
|
|
1316
1420
|
<script>
|
|
@@ -1319,6 +1423,65 @@
|
|
|
1319
1423
|
el.textContent = el.textContent.trim();
|
|
1320
1424
|
});
|
|
1321
1425
|
|
|
1426
|
+
// Process markdown for collapsible sections
|
|
1427
|
+
// foldH2: if true, H2 headers start collapsed; otherwise all headers start open
|
|
1428
|
+
function structureMarkdown(root, foldH2 = false) {
|
|
1429
|
+
// Find all headings
|
|
1430
|
+
const headers = Array.from(root.querySelectorAll("h1, h2, h3, h4, h5, h6"));
|
|
1431
|
+
if (!headers.length) return;
|
|
1432
|
+
|
|
1433
|
+
const stack = [{ element: root, level: 0 }];
|
|
1434
|
+
const children = Array.from(root.childNodes);
|
|
1435
|
+
|
|
1436
|
+
root.innerHTML = '';
|
|
1437
|
+
|
|
1438
|
+
children.forEach(node => {
|
|
1439
|
+
let level = 100; // Content level
|
|
1440
|
+
if (node.tagName && /^H[1-6]$$/.test(node.tagName)) {
|
|
1441
|
+
level = parseInt(node.tagName.substring(1));
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
if (level === 1) {
|
|
1445
|
+
// H1: not collapsible, just append directly
|
|
1446
|
+
while (stack.length > 1) {
|
|
1447
|
+
stack.pop();
|
|
1448
|
+
}
|
|
1449
|
+
stack[0].element.appendChild(node);
|
|
1450
|
+
|
|
1451
|
+
} else if (level >= 2 && level < 100) {
|
|
1452
|
+
// H2-H6: collapsible
|
|
1453
|
+
// Close any open sections that are deeper or same level
|
|
1454
|
+
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
|
|
1455
|
+
stack.pop();
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// Create new section
|
|
1459
|
+
const details = document.createElement('details');
|
|
1460
|
+
const summary = document.createElement('summary');
|
|
1461
|
+
|
|
1462
|
+
// Move header into summary
|
|
1463
|
+
summary.appendChild(node);
|
|
1464
|
+
details.appendChild(summary);
|
|
1465
|
+
|
|
1466
|
+
// Default open state: fold H2 only if foldH2 is true
|
|
1467
|
+
if (!(foldH2 && level === 2)) {
|
|
1468
|
+
details.setAttribute('open', '');
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// Append this details to the current parent in stack
|
|
1472
|
+
stack[stack.length - 1].element.appendChild(details);
|
|
1473
|
+
|
|
1474
|
+
// Push to stack as the new current container
|
|
1475
|
+
stack.push({ element: details, level: level });
|
|
1476
|
+
|
|
1477
|
+
} else {
|
|
1478
|
+
// Content node (p, ul, text, etc)
|
|
1479
|
+
// Append to current container
|
|
1480
|
+
stack[stack.length - 1].element.appendChild(node);
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1322
1485
|
// Markdown rendering and Syntax Highlighting
|
|
1323
1486
|
document.querySelectorAll(".markdown-content").forEach((el) => {
|
|
1324
1487
|
const raw = el.dataset.raw;
|
|
@@ -1326,12 +1489,17 @@
|
|
|
1326
1489
|
// 1. Render Markdown
|
|
1327
1490
|
el.innerHTML = window.marked.parse(raw);
|
|
1328
1491
|
|
|
1329
|
-
// 2. Apply Syntax Highlighting
|
|
1492
|
+
// 2. Apply Syntax Highlighting
|
|
1330
1493
|
if (window.hljs) {
|
|
1331
1494
|
el.querySelectorAll("pre code").forEach((block) => {
|
|
1332
1495
|
hljs.highlightElement(block);
|
|
1333
1496
|
});
|
|
1334
1497
|
}
|
|
1498
|
+
|
|
1499
|
+
// 3. Make headings collapsible
|
|
1500
|
+
// Sub-agent results: fold H2 by default; others: all open
|
|
1501
|
+
const foldH2 = el.classList.contains("sub-agent-rendered");
|
|
1502
|
+
structureMarkdown(el, foldH2);
|
|
1335
1503
|
}
|
|
1336
1504
|
});
|
|
1337
1505
|
|
|
@@ -1655,11 +1823,11 @@
|
|
|
1655
1823
|
if (roleLabel) {
|
|
1656
1824
|
if (roleLabel.classList.contains("user")) {
|
|
1657
1825
|
userCount++;
|
|
1658
|
-
label = `USER
|
|
1826
|
+
label = `USER $$$${userCount}`;
|
|
1659
1827
|
idPrefix = "user";
|
|
1660
1828
|
} else if (roleLabel.classList.contains("assistant")) {
|
|
1661
1829
|
assistantCount++;
|
|
1662
|
-
label = `ASSISTANT
|
|
1830
|
+
label = `ASSISTANT $$$${assistantCount}`;
|
|
1663
1831
|
idPrefix = "assistant";
|
|
1664
1832
|
} else {
|
|
1665
1833
|
label = roleLabel.textContent.trim();
|
|
@@ -1670,6 +1838,30 @@
|
|
|
1670
1838
|
if (toolName) {
|
|
1671
1839
|
label = toolName.textContent.trim();
|
|
1672
1840
|
idPrefix = "tool";
|
|
1841
|
+
|
|
1842
|
+
// Try to extract description for Sub Agents
|
|
1843
|
+
try {
|
|
1844
|
+
const argsContent = child.querySelector(".tool-args-content");
|
|
1845
|
+
if (argsContent) {
|
|
1846
|
+
const argsText = argsContent.textContent.trim();
|
|
1847
|
+
if (argsText.startsWith("{")) {
|
|
1848
|
+
const args = JSON.parse(argsText);
|
|
1849
|
+
if (
|
|
1850
|
+
args &&
|
|
1851
|
+
typeof args.description === "string" &&
|
|
1852
|
+
args.description.trim()
|
|
1853
|
+
) {
|
|
1854
|
+
let desc = args.description.trim();
|
|
1855
|
+
if (desc.length > 30) {
|
|
1856
|
+
desc = desc.substring(0, 30) + "...";
|
|
1857
|
+
}
|
|
1858
|
+
label = label + " - " + desc;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
} catch (e) {
|
|
1863
|
+
// Ignore JSON parse errors
|
|
1864
|
+
}
|
|
1673
1865
|
} else {
|
|
1674
1866
|
label = "Tool";
|
|
1675
1867
|
}
|
|
@@ -1678,7 +1870,7 @@
|
|
|
1678
1870
|
if (label) {
|
|
1679
1871
|
child.id =
|
|
1680
1872
|
child.id ||
|
|
1681
|
-
|
|
1873
|
+
`$$$${idPrefix}-$$$${Math.random().toString(36).substr(2, 9)}`;
|
|
1682
1874
|
sections.push({ id: child.id, label: label, el: child });
|
|
1683
1875
|
}
|
|
1684
1876
|
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import contextlib
|
|
5
4
|
import shutil
|
|
6
|
-
import time
|
|
7
5
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
from typing import NamedTuple, override
|
|
@@ -245,9 +243,6 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
245
243
|
self._get_current_llm_config = get_current_llm_config
|
|
246
244
|
self._command_info_provider = command_info_provider
|
|
247
245
|
|
|
248
|
-
self._toast_message: str | None = None
|
|
249
|
-
self._toast_until: float = 0.0
|
|
250
|
-
|
|
251
246
|
# Use provided value if available to avoid redundant TTY queries that may interfere
|
|
252
247
|
# with prompt_toolkit's terminal state after interactive UIs have been used.
|
|
253
248
|
self._is_light_terminal_background = (
|
|
@@ -494,7 +489,6 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
494
489
|
if self._on_change_model is None:
|
|
495
490
|
return
|
|
496
491
|
await self._on_change_model(model_name)
|
|
497
|
-
self._set_toast(f"model: {model_name}")
|
|
498
492
|
|
|
499
493
|
# -------------------------------------------------------------------------
|
|
500
494
|
# Thinking picker
|
|
@@ -537,38 +531,7 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
537
531
|
new_thinking = parse_thinking_value(value)
|
|
538
532
|
if new_thinking is None:
|
|
539
533
|
return
|
|
540
|
-
|
|
541
|
-
# Build toast label
|
|
542
|
-
if value.startswith("effort:"):
|
|
543
|
-
toast_label = value[7:]
|
|
544
|
-
elif value.startswith("budget:"):
|
|
545
|
-
budget = int(value[7:])
|
|
546
|
-
toast_label = "off" if budget == 0 else f"{budget} tokens"
|
|
547
|
-
else:
|
|
548
|
-
toast_label = "updated"
|
|
549
|
-
|
|
550
534
|
await self._on_change_thinking(new_thinking)
|
|
551
|
-
self._set_toast(f"thinking: {toast_label}")
|
|
552
|
-
|
|
553
|
-
# -------------------------------------------------------------------------
|
|
554
|
-
# Toast notifications
|
|
555
|
-
# -------------------------------------------------------------------------
|
|
556
|
-
|
|
557
|
-
def _set_toast(self, message: str, *, duration_sec: float = 2.0) -> None:
|
|
558
|
-
self._toast_message = message
|
|
559
|
-
self._toast_until = time.monotonic() + duration_sec
|
|
560
|
-
with contextlib.suppress(Exception):
|
|
561
|
-
self._session.app.invalidate()
|
|
562
|
-
|
|
563
|
-
async def _clear_later() -> None:
|
|
564
|
-
await asyncio.sleep(duration_sec)
|
|
565
|
-
self._toast_message = None
|
|
566
|
-
self._toast_until = 0.0
|
|
567
|
-
with contextlib.suppress(Exception):
|
|
568
|
-
self._session.app.invalidate()
|
|
569
|
-
|
|
570
|
-
with contextlib.suppress(Exception):
|
|
571
|
-
self._session.app.create_background_task(_clear_later())
|
|
572
535
|
|
|
573
536
|
# -------------------------------------------------------------------------
|
|
574
537
|
# Bottom toolbar
|
|
@@ -588,23 +551,17 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
588
551
|
except (AttributeError, RuntimeError):
|
|
589
552
|
update_message = None
|
|
590
553
|
|
|
591
|
-
toast: str | None = None
|
|
592
|
-
now = time.monotonic()
|
|
593
|
-
if self._toast_message is not None and now < self._toast_until:
|
|
594
|
-
toast = self._toast_message
|
|
595
|
-
|
|
596
554
|
# If nothing to show, return a blank line to actively clear any previously
|
|
597
555
|
# rendered content. (When `bottom_toolbar` is a callable, prompt_toolkit
|
|
598
556
|
# will still reserve the toolbar line.)
|
|
599
|
-
if not
|
|
557
|
+
if not update_message:
|
|
600
558
|
try:
|
|
601
559
|
terminal_width = shutil.get_terminal_size().columns
|
|
602
560
|
except (OSError, ValueError):
|
|
603
561
|
terminal_width = 0
|
|
604
562
|
return FormattedText([("", " " * max(0, terminal_width))])
|
|
605
563
|
|
|
606
|
-
|
|
607
|
-
left_text = " " + " · ".join(parts)
|
|
564
|
+
left_text = " " + update_message
|
|
608
565
|
try:
|
|
609
566
|
terminal_width = shutil.get_terminal_size().columns
|
|
610
567
|
padding = " " * max(0, terminal_width - len(left_text))
|
|
@@ -667,7 +624,10 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
667
624
|
with contextlib.suppress(Exception):
|
|
668
625
|
self._pre_prompt()
|
|
669
626
|
|
|
670
|
-
|
|
627
|
+
# Keep ANSI escape sequences intact while prompt_toolkit is active.
|
|
628
|
+
# This allows Rich-rendered panels (e.g. WelcomeEvent) to display with
|
|
629
|
+
# proper styling instead of showing raw escape codes.
|
|
630
|
+
with patch_stdout(raw=True):
|
|
671
631
|
line: str = await self._session.prompt_async(
|
|
672
632
|
placeholder=self._render_input_placeholder(),
|
|
673
633
|
bottom_toolbar=self._get_bottom_toolbar,
|
|
@@ -266,7 +266,11 @@ class REPLRenderer:
|
|
|
266
266
|
self.print(r_user_input.render_interrupt())
|
|
267
267
|
|
|
268
268
|
def display_error(self, event: events.ErrorEvent) -> None:
|
|
269
|
-
|
|
269
|
+
if event.session_id:
|
|
270
|
+
with self.session_print_context(event.session_id):
|
|
271
|
+
self.print(r_errors.render_error(truncate_display(event.error_message)))
|
|
272
|
+
else:
|
|
273
|
+
self.print(r_errors.render_error(truncate_display(event.error_message)))
|
|
270
274
|
|
|
271
275
|
# -------------------------------------------------------------------------
|
|
272
276
|
# Spinner control methods
|
|
@@ -167,7 +167,7 @@ def _render_fork_session_output(command_output: model.CommandOutput) -> Renderab
|
|
|
167
167
|
session_id = command_output.ui_extra.session_id
|
|
168
168
|
grid.add_column(style=ThemeKey.METADATA, overflow="fold")
|
|
169
169
|
|
|
170
|
-
grid.add_row(Text("Session forked.
|
|
170
|
+
grid.add_row(Text("Session forked. Resume command copied to clipboard:", style=ThemeKey.METADATA))
|
|
171
171
|
grid.add_row(Text(f" klaude --resume-by-id {session_id}", style=ThemeKey.METADATA_BOLD))
|
|
172
172
|
|
|
173
173
|
return Padding.indent(grid, level=2)
|
|
@@ -107,7 +107,7 @@ def render_sub_agent_result(
|
|
|
107
107
|
Group(
|
|
108
108
|
NoInsetMarkdown(head_text, code_theme=code_theme, style=style or ""),
|
|
109
109
|
Text(
|
|
110
|
-
f"\n… more {hidden_count} lines — use /export to view full output\n",
|
|
110
|
+
f"\n( … more {hidden_count} lines — use /export to view full output )\n",
|
|
111
111
|
style=ThemeKey.TOOL_RESULT_TRUNCATED,
|
|
112
112
|
),
|
|
113
113
|
NoInsetMarkdown(tail_text, code_theme=code_theme, style=style or ""),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.1
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -146,20 +146,62 @@ klaude config
|
|
|
146
146
|
|
|
147
147
|
##### Adding Models to Built-in Providers
|
|
148
148
|
|
|
149
|
-
You can add custom models to existing providers without redefining the entire provider
|
|
149
|
+
You can add custom models to existing built-in providers without redefining the entire provider. Just reference the `provider_name` and add your `model_list`:
|
|
150
150
|
|
|
151
151
|
```yaml
|
|
152
|
-
#
|
|
152
|
+
# ~/.klaude/klaude-config.yaml
|
|
153
153
|
provider_list:
|
|
154
|
+
- provider_name: openrouter # Reference existing built-in provider
|
|
155
|
+
model_list:
|
|
156
|
+
- model_name: seed
|
|
157
|
+
model_params:
|
|
158
|
+
model: bytedance-seed/seed-1.6 # Model ID from OpenRouter
|
|
159
|
+
context_limit: 262000
|
|
160
|
+
cost:
|
|
161
|
+
input: 0.25
|
|
162
|
+
output: 2
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**How merging works:**
|
|
166
|
+
- Your models are merged with the built-in models for that provider
|
|
167
|
+
- You only need `provider_name` and `model_list` - protocol, api_key, etc. are inherited from the built-in config
|
|
168
|
+
- To override a built-in model, use the same `model_name` (e.g., `sonnet` to customize the built-in sonnet)
|
|
169
|
+
|
|
170
|
+
**More examples:**
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
provider_list:
|
|
174
|
+
# Add multiple models to OpenRouter
|
|
154
175
|
- provider_name: openrouter
|
|
155
176
|
model_list:
|
|
156
|
-
- model_name:
|
|
177
|
+
- model_name: qwen-coder
|
|
178
|
+
model_params:
|
|
179
|
+
model: qwen/qwen-2.5-coder-32b-instruct
|
|
180
|
+
context_limit: 131072
|
|
181
|
+
cost:
|
|
182
|
+
input: 0.3
|
|
183
|
+
output: 0.9
|
|
184
|
+
- model_name: llama-405b
|
|
185
|
+
model_params:
|
|
186
|
+
model: meta-llama/llama-3.1-405b-instruct
|
|
187
|
+
context_limit: 131072
|
|
188
|
+
cost:
|
|
189
|
+
input: 0.8
|
|
190
|
+
output: 0.8
|
|
191
|
+
|
|
192
|
+
# Add models to Anthropic provider
|
|
193
|
+
- provider_name: anthropic
|
|
194
|
+
model_list:
|
|
195
|
+
- model_name: haiku@ant
|
|
157
196
|
model_params:
|
|
158
|
-
model:
|
|
197
|
+
model: claude-3-5-haiku-20241022
|
|
159
198
|
context_limit: 200000
|
|
199
|
+
cost:
|
|
200
|
+
input: 1.0
|
|
201
|
+
output: 5.0
|
|
160
202
|
```
|
|
161
203
|
|
|
162
|
-
|
|
204
|
+
After adding models, run `klaude list` to verify they appear in the model list.
|
|
163
205
|
|
|
164
206
|
##### Overriding Provider Settings
|
|
165
207
|
|
|
@@ -230,6 +272,8 @@ provider_list:
|
|
|
230
272
|
- `openai` - OpenAI-compatible API
|
|
231
273
|
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
232
274
|
- `openrouter` - OpenRouter API
|
|
275
|
+
- `google` - Google Gemini API
|
|
276
|
+
- `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
|
|
233
277
|
- `codex` - OpenAI Codex CLI (OAuth-based)
|
|
234
278
|
|
|
235
279
|
List configured providers and models:
|
|
@@ -10,8 +10,8 @@ klaude_code/cli/auth_cmd.py,sha256=UWMHjn9xZp2o8OZc-x8y9MnkZgRWOkFXk05iKJYcySE,2
|
|
|
10
10
|
klaude_code/cli/config_cmd.py,sha256=hlvslLNgdRHkokq1Pnam0XOdR3jqO3K0vNLqtWnPa6Q,3261
|
|
11
11
|
klaude_code/cli/debug.py,sha256=cPQ7cgATcJTyBIboleW_Q4Pa_t-tGG6x-Hj3woeeuHE,2669
|
|
12
12
|
klaude_code/cli/list_model.py,sha256=3SLURZXH_WgX-vGWIt52NuRm2D14-jcONtiS5GDM2xA,11248
|
|
13
|
-
klaude_code/cli/main.py,sha256=
|
|
14
|
-
klaude_code/cli/runtime.py,sha256=
|
|
13
|
+
klaude_code/cli/main.py,sha256=2hd0peqkzMrbZzfXwThx432iR559k96iS_uTKdcVcgA,13469
|
|
14
|
+
klaude_code/cli/runtime.py,sha256=muuuqWPDdZPNFwq_Eb08MFbnaCzUGZCDFiC-dMvpzCY,19824
|
|
15
15
|
klaude_code/cli/self_update.py,sha256=iGuj0i869Zi0M70W52-VVLxZp90ISr30fQpZkHGMK2o,8059
|
|
16
16
|
klaude_code/cli/session_cmd.py,sha256=9C30dzXCbPobminqenCjYvEuzZBS5zWXg3JpuhxT_OQ,3199
|
|
17
17
|
klaude_code/command/__init__.py,sha256=IK2jz2SFMLVIcVzD5evKk3zWv6u1CjgCgfJXzWdvDlk,3470
|
|
@@ -20,7 +20,7 @@ klaude_code/command/command_abc.py,sha256=wZl_azY6Dpd4OvjtkSEPI3ilXaygLIVkO7NCgN
|
|
|
20
20
|
klaude_code/command/debug_cmd.py,sha256=9sBIAwHz28QoI-tHsU3ksQlDObF1ilIbtAAEAVMR0v0,2734
|
|
21
21
|
klaude_code/command/export_cmd.py,sha256=Cs7YXWtos-ZfN9OEppIl8Xrb017kDG7R6hGiilqt2bM,1623
|
|
22
22
|
klaude_code/command/export_online_cmd.py,sha256=RYYLnkLtg6edsgysmhsfTw16ncFRIT6PqeTdWhWXLHE,6094
|
|
23
|
-
klaude_code/command/fork_session_cmd.py,sha256=
|
|
23
|
+
klaude_code/command/fork_session_cmd.py,sha256=_8QXiFVC3tMjlW3F5jUjhCWQ0CkhWfKJnlNFujjbVHs,9950
|
|
24
24
|
klaude_code/command/help_cmd.py,sha256=wtmOoi4DVaMJPCXLlNKJ4s-kNycNKuYk0MZkZijXLcQ,1666
|
|
25
25
|
klaude_code/command/model_cmd.py,sha256=h3jUi9YOhT9rN87yfCxxU-yN3UiUzwI7Xf2UsRjjP5I,2956
|
|
26
26
|
klaude_code/command/model_select.py,sha256=_TquYw8zDQHkEaRCqOCIcD2XWt8Jg-3WfGhHFSsjFw0,3189
|
|
@@ -36,15 +36,15 @@ klaude_code/command/terminal_setup_cmd.py,sha256=SivM1gX_anGY_8DCQNFZ5VblFqt4sVg
|
|
|
36
36
|
klaude_code/command/thinking_cmd.py,sha256=NPejWmx6HDxoWzAJVLEENCr3Wi6sQSbT8A8LRh1-2Nk,3059
|
|
37
37
|
klaude_code/config/__init__.py,sha256=Qe1BeMekBfO2-Zd30x33lB70hdM1QQZGrp4DbWSQ-II,353
|
|
38
38
|
klaude_code/config/assets/__init__.py,sha256=uMUfmXT3I-gYiI-HVr1DrE60mx5cY1o8V7SYuGqOmvY,32
|
|
39
|
-
klaude_code/config/assets/builtin_config.yaml,sha256=
|
|
39
|
+
klaude_code/config/assets/builtin_config.yaml,sha256=m-jWYrfe-cv5NBZ8lrsKTnQWNj7-MiGjDkvrEl7mg0w,7042
|
|
40
40
|
klaude_code/config/builtin_config.py,sha256=LkHr7Ml-6ir6rObn9hUj5-wa-fgfJsc4T2_NdRa1ax0,1135
|
|
41
|
-
klaude_code/config/config.py,sha256=
|
|
41
|
+
klaude_code/config/config.py,sha256=4CRkpR0ect1as0j4MK6F1Rd2uLX-ZV1orzQ-DIfB9Z8,17326
|
|
42
42
|
klaude_code/config/select_model.py,sha256=PPbQ-BAJkwXPINBcCSPAlZjiXm4rEtg2y0hPnZE8Bnc,5183
|
|
43
43
|
klaude_code/config/thinking.py,sha256=X-vywa36ggO_2z4iVhss1mAVEPAwAbcj1s68F0-B0G4,9223
|
|
44
|
-
klaude_code/const.py,sha256=
|
|
44
|
+
klaude_code/const.py,sha256=49ic3m4NHUtmA2xZjOSkYxy0KxFoADVAaMMtH6E5C0g,5129
|
|
45
45
|
klaude_code/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
klaude_code/core/agent.py,sha256=bWm-UFX_0-KAy5j_YHH8X8o3MJT4-40Ni2EaDP2SL5k,5819
|
|
47
|
-
klaude_code/core/executor.py,sha256=
|
|
47
|
+
klaude_code/core/executor.py,sha256=oIDE0O_kxtudSjfQcN3o0AWinkz3ddAEgqj84tYh7yY,28191
|
|
48
48
|
klaude_code/core/manager/__init__.py,sha256=hdIbpnYj6i18byiWjtJIm5l7NYYDQMvafw8fePVPydc,562
|
|
49
49
|
klaude_code/core/manager/llm_clients.py,sha256=X2oMFWgJcP0tK8GEtMMDYR3HyR6_H8FuyCqpzWF5x2k,871
|
|
50
50
|
klaude_code/core/manager/llm_clients_builder.py,sha256=vvNV6hSpWtUCMZORQN6OfFWyA_3OAOGS5SFGy6oaKMs,1641
|
|
@@ -61,7 +61,7 @@ klaude_code/core/prompts/prompt-sub-agent-oracle.md,sha256=1PLI3snvxnenCOPVrL0Ix
|
|
|
61
61
|
klaude_code/core/prompts/prompt-sub-agent-web.md,sha256=ewS7-h8_u4QZftFpqrZWpht9Ap08s7zF9D4k4md8oD8,2360
|
|
62
62
|
klaude_code/core/prompts/prompt-sub-agent.md,sha256=dmmdsOenbAOfqG6FmdR88spOLZkXmntDBs-cmZ9DN_g,897
|
|
63
63
|
klaude_code/core/reminders.py,sha256=M_YPlOuZ2TpTjoxfEp1FbswB4yuk9_XUgSGb9MoMBCA,24741
|
|
64
|
-
klaude_code/core/task.py,sha256=
|
|
64
|
+
klaude_code/core/task.py,sha256=hVplXxo9RvMQgCBJSMH7vhftYp29ZzAhmhSJK0OjWOw,12017
|
|
65
65
|
klaude_code/core/tool/__init__.py,sha256=Y1r-xka9Zk5e5SrB0kcweFp4LyH0aafQ-BDiwYCAqFY,1992
|
|
66
66
|
klaude_code/core/tool/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
67
|
klaude_code/core/tool/file/_utils.py,sha256=OG4BE9WyJqzH8ilVCL3D9yvAcHk-r-L9snd-E8gO_io,967
|
|
@@ -81,7 +81,7 @@ klaude_code/core/tool/report_back_tool.py,sha256=KRZzQAIxniwXe58SDJcfK_DCf9TFFAx
|
|
|
81
81
|
klaude_code/core/tool/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
82
|
klaude_code/core/tool/shell/bash_tool.md,sha256=ILKpnRCBTkU2uSDEdZQjNYo1l6hsM4TO-3RD5zWC61c,3935
|
|
83
83
|
klaude_code/core/tool/shell/bash_tool.py,sha256=H3DlUZUdtmFYwI9wlkJpv8daGeeqP7GhOjQIKriGoog,14759
|
|
84
|
-
klaude_code/core/tool/shell/command_safety.py,sha256=
|
|
84
|
+
klaude_code/core/tool/shell/command_safety.py,sha256=Xlyn8QvjaA_krPNScpcTQwP3byoXxwoX_jc97AWvr6Q,13039
|
|
85
85
|
klaude_code/core/tool/skill/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
86
|
klaude_code/core/tool/skill/skill_tool.md,sha256=UfjJK5EGAd3mf7ym5rIrRdPyV3kBTxNnwzOjNnHOBrE,973
|
|
87
87
|
klaude_code/core/tool/skill/skill_tool.py,sha256=QLcweYEfO-ncv2tdPSqANZrq2U651AG4RjGpFKeAweY,2880
|
|
@@ -134,7 +134,7 @@ klaude_code/llm/responses/input.py,sha256=qr61LmQJdcb_f-ofrAz06WpK_k4PEcI36XsyuZ
|
|
|
134
134
|
klaude_code/llm/usage.py,sha256=ohQ6EBsWXZj6B4aJ4lDPqfhXRyd0LUAM1nXEJ_elD7A,4207
|
|
135
135
|
klaude_code/protocol/__init__.py,sha256=aGUgzhYqvhuT3Mk2vj7lrHGriH4h9TSbqV1RsRFAZjQ,194
|
|
136
136
|
klaude_code/protocol/commands.py,sha256=4tFt98CD_KvS9C-XEaHLN-S-QFsbDxQb_kGKnPkQlrk,958
|
|
137
|
-
klaude_code/protocol/events.py,sha256=
|
|
137
|
+
klaude_code/protocol/events.py,sha256=LlrxsHdWitcIg5tLrS2KbSd5Jv1TH-XlhF8e1xprYAo,3969
|
|
138
138
|
klaude_code/protocol/llm_param.py,sha256=O5sn3-KJnhS_0FOxDDvAFJ2Sx7ZYdJ74ugnwu-gHZG8,4538
|
|
139
139
|
klaude_code/protocol/model.py,sha256=zz1DeSkpUWDT-OZBlypaGWA4z78TSeefA-Tj8mJMHp4,14257
|
|
140
140
|
klaude_code/protocol/op.py,sha256=--RllgP6Upacb6cqHd26RSwrvqZg4w6GhcebmV8gKJo,5763
|
|
@@ -147,11 +147,11 @@ klaude_code/protocol/sub_agent/web.py,sha256=Z5vUM367kz8CIexN6UVPG4XxzVOaaRek-Ga
|
|
|
147
147
|
klaude_code/protocol/tools.py,sha256=ejhMCBBMz1ODbPEiynhzjB-aLbIRKL-wipPFv-nEz4g,373
|
|
148
148
|
klaude_code/session/__init__.py,sha256=4sw81uQvEd3YUOOjamKk1KqGmxeb4Ic9T1Tee5zztyU,241
|
|
149
149
|
klaude_code/session/codec.py,sha256=ummbqT7t6uHHXtaS9lOkyhi1h0YpMk7SNSms8DyGAHU,2015
|
|
150
|
-
klaude_code/session/export.py,sha256=
|
|
150
|
+
klaude_code/session/export.py,sha256=30FZrkEDDjccMgXlAwsBMlCXuPDS9531__SCX_nk29M,31538
|
|
151
151
|
klaude_code/session/selector.py,sha256=FpKpGs06fM-LdV-yVUqEY-FJsFn2OtGK-0paXjsZVTg,2770
|
|
152
|
-
klaude_code/session/session.py,sha256=
|
|
153
|
-
klaude_code/session/store.py,sha256
|
|
154
|
-
klaude_code/session/templates/export_session.html,sha256=
|
|
152
|
+
klaude_code/session/session.py,sha256=kepapzAcidzOi9Tl7WjisbGgQzkkmL7HlMeGNVYUKU0,19546
|
|
153
|
+
klaude_code/session/store.py,sha256=DOf5R3pxQmmexfWIjCwZ27SBVsk7b_qIN7deXghCy90,7114
|
|
154
|
+
klaude_code/session/templates/export_session.html,sha256=hGmlGFb7a2Iu4mkev_Q2-GywIQ9lEXuDlh83dDs4bgs,125862
|
|
155
155
|
klaude_code/session/templates/mermaid_viewer.html,sha256=lOkETxlctX1C1WJtS1wFw6KhNQmemxwJZFpXDSjlMOk,27842
|
|
156
156
|
klaude_code/skill/__init__.py,sha256=yeWeCfRGPOhT4mx_pjdo4fLondQ_Vx0edBtnFusLhls,839
|
|
157
157
|
klaude_code/skill/assets/deslop/SKILL.md,sha256=XMBER6gOyYnZof_u7l30CZSzmDcINe8XP-n_loah0EQ,873
|
|
@@ -179,19 +179,19 @@ klaude_code/ui/modes/repl/clipboard.py,sha256=ZCpk7kRSXGhh0Q_BWtUUuSYT7ZOqRjAoRc
|
|
|
179
179
|
klaude_code/ui/modes/repl/completers.py,sha256=zH5zslovTKJwH1Gu8ZufvMDGkSd342F6fHE1hjlxHgM,31849
|
|
180
180
|
klaude_code/ui/modes/repl/display.py,sha256=06wawOHWO2ItEA9EIEh97p3GDID7TJhAtpaA03nPQXs,2335
|
|
181
181
|
klaude_code/ui/modes/repl/event_handler.py,sha256=O8yDr4xNMAqgXEiT90KWBoQX-2pIPjVf591QJ0ftjIo,25482
|
|
182
|
-
klaude_code/ui/modes/repl/input_prompt_toolkit.py,sha256=
|
|
182
|
+
klaude_code/ui/modes/repl/input_prompt_toolkit.py,sha256=QZBBAUK7Bkq1TBwtvBnJ0bGYtGH5HWvpWcb1bppGnl0,27072
|
|
183
183
|
klaude_code/ui/modes/repl/key_bindings.py,sha256=tZV0ILMWpHCPcVFpf9bnbTSXgnnqsW0-6cCMMVTRciA,13023
|
|
184
|
-
klaude_code/ui/modes/repl/renderer.py,sha256=
|
|
184
|
+
klaude_code/ui/modes/repl/renderer.py,sha256=ljsgu4bLLnGOeCpbWbQPdJnMI7SP_K5WD88yOhZLGjU,16111
|
|
185
185
|
klaude_code/ui/renderers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
186
186
|
klaude_code/ui/renderers/assistant.py,sha256=7iu5zlHR7JGviHs2eA25Dsbd7ZkzCR2_0XzkqMPVxDI,862
|
|
187
187
|
klaude_code/ui/renderers/bash_syntax.py,sha256=VcX_tuojOtS58s_Ff-Zmhw_6LBRn2wsvR5UBtEr_qQU,5923
|
|
188
188
|
klaude_code/ui/renderers/common.py,sha256=l9V7yuiejowyw3FdZ2n3VJ2OO_K1rEUINmFz-mC2xlw,2648
|
|
189
|
-
klaude_code/ui/renderers/developer.py,sha256=
|
|
189
|
+
klaude_code/ui/renderers/developer.py,sha256=GI6MreYDvLHNa40KlhOR0PQoR_ePOLtlYhv3pAPKHp8,8164
|
|
190
190
|
klaude_code/ui/renderers/diffs.py,sha256=uLpgYTudH38wucozoUw4xbPWMC6uYTQTaDTHcg-0zvM,10418
|
|
191
191
|
klaude_code/ui/renderers/errors.py,sha256=MavmYOQ7lyjA_VpuUpDVFCuY9W7XrMVdLsg2lCOn4GY,655
|
|
192
192
|
klaude_code/ui/renderers/mermaid_viewer.py,sha256=TIUFLtTqdG-iFD4Mgm8OdzU_9UO14niftTJ11f4makc,1691
|
|
193
193
|
klaude_code/ui/renderers/metadata.py,sha256=EWxh5UTSZG_vRVf6taKI_E1YkR_56U1Gs9soDuZcpq4,8576
|
|
194
|
-
klaude_code/ui/renderers/sub_agent.py,sha256=
|
|
194
|
+
klaude_code/ui/renderers/sub_agent.py,sha256=zG4T7hKEb7G-kD_Mgc7lmzwbukvdNLG7Y7a9K1WlHGA,5950
|
|
195
195
|
klaude_code/ui/renderers/thinking.py,sha256=TbQxkjR6MuDXzASBK_rMaxxqvSdhfwDtVwXhOExuvlM,1946
|
|
196
196
|
klaude_code/ui/renderers/tools.py,sha256=lebQHccj2tkJIjO-JB0TvCIixx-BKXHfD-egXSxBV7Y,27891
|
|
197
197
|
klaude_code/ui/renderers/user_input.py,sha256=OPHVOZsCefIRVpgl6WMBFusc3Vwyr1fk5ilFaQ64AqU,4500
|
|
@@ -212,7 +212,7 @@ klaude_code/ui/terminal/progress_bar.py,sha256=MDnhPbqCnN4GDgLOlxxOEVZPDwVC_XL2N
|
|
|
212
212
|
klaude_code/ui/terminal/selector.py,sha256=NblhWxUp0AW2OyepG4DHNy4yKE947Oi0OiqlafvBCEE,22144
|
|
213
213
|
klaude_code/ui/utils/__init__.py,sha256=YEsCLjbCPaPza-UXTPUMTJTrc9BmNBUP5CbFWlshyOQ,15
|
|
214
214
|
klaude_code/ui/utils/common.py,sha256=tqHqwgLtAyP805kwRFyoAL4EgMutcNb3Y-GAXJ4IeuM,2263
|
|
215
|
-
klaude_code-1.7.
|
|
216
|
-
klaude_code-1.7.
|
|
217
|
-
klaude_code-1.7.
|
|
218
|
-
klaude_code-1.7.
|
|
215
|
+
klaude_code-1.7.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
216
|
+
klaude_code-1.7.1.dist-info/entry_points.txt,sha256=kkXIXedaTOtjXPr2rVjRVVXZYlFUcBHELaqmyVlWUFA,92
|
|
217
|
+
klaude_code-1.7.1.dist-info/METADATA,sha256=mzpnDrtduulkJRur3kZutGAMR58k3hYHvgZ_JskQbH8,12006
|
|
218
|
+
klaude_code-1.7.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|