klaude-code 1.2.1__py3-none-any.whl → 1.2.3__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 (140) hide show
  1. klaude_code/cli/main.py +9 -4
  2. klaude_code/cli/runtime.py +42 -43
  3. klaude_code/command/__init__.py +7 -5
  4. klaude_code/command/clear_cmd.py +6 -29
  5. klaude_code/command/command_abc.py +44 -8
  6. klaude_code/command/diff_cmd.py +33 -27
  7. klaude_code/command/export_cmd.py +18 -26
  8. klaude_code/command/help_cmd.py +10 -8
  9. klaude_code/command/model_cmd.py +11 -40
  10. klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
  11. klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
  12. klaude_code/command/prompt-init.md +2 -5
  13. klaude_code/command/prompt_command.py +6 -6
  14. klaude_code/command/refresh_cmd.py +4 -5
  15. klaude_code/command/registry.py +16 -19
  16. klaude_code/command/terminal_setup_cmd.py +12 -11
  17. klaude_code/config/__init__.py +4 -0
  18. klaude_code/config/config.py +25 -26
  19. klaude_code/config/list_model.py +8 -3
  20. klaude_code/config/select_model.py +1 -1
  21. klaude_code/const/__init__.py +1 -1
  22. klaude_code/core/__init__.py +0 -3
  23. klaude_code/core/agent.py +25 -50
  24. klaude_code/core/executor.py +268 -101
  25. klaude_code/core/prompt.py +12 -12
  26. klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
  27. klaude_code/core/reminders.py +76 -95
  28. klaude_code/core/task.py +21 -14
  29. klaude_code/core/tool/__init__.py +45 -11
  30. klaude_code/core/tool/file/apply_patch.py +5 -1
  31. klaude_code/core/tool/file/apply_patch_tool.py +11 -13
  32. klaude_code/core/tool/file/edit_tool.py +27 -23
  33. klaude_code/core/tool/file/multi_edit_tool.py +15 -17
  34. klaude_code/core/tool/file/read_tool.py +41 -36
  35. klaude_code/core/tool/file/write_tool.py +13 -15
  36. klaude_code/core/tool/memory/memory_tool.py +85 -68
  37. klaude_code/core/tool/memory/skill_tool.py +10 -12
  38. klaude_code/core/tool/shell/bash_tool.py +24 -22
  39. klaude_code/core/tool/shell/command_safety.py +12 -1
  40. klaude_code/core/tool/sub_agent_tool.py +11 -12
  41. klaude_code/core/tool/todo/todo_write_tool.py +21 -28
  42. klaude_code/core/tool/todo/update_plan_tool.py +14 -24
  43. klaude_code/core/tool/tool_abc.py +3 -4
  44. klaude_code/core/tool/tool_context.py +7 -7
  45. klaude_code/core/tool/tool_registry.py +30 -47
  46. klaude_code/core/tool/tool_runner.py +35 -43
  47. klaude_code/core/tool/truncation.py +14 -20
  48. klaude_code/core/tool/web/mermaid_tool.py +12 -14
  49. klaude_code/core/tool/web/web_fetch_tool.py +15 -17
  50. klaude_code/core/turn.py +19 -7
  51. klaude_code/llm/__init__.py +3 -4
  52. klaude_code/llm/anthropic/client.py +30 -46
  53. klaude_code/llm/anthropic/input.py +4 -11
  54. klaude_code/llm/client.py +29 -8
  55. klaude_code/llm/input_common.py +66 -36
  56. klaude_code/llm/openai_compatible/client.py +42 -84
  57. klaude_code/llm/openai_compatible/input.py +11 -16
  58. klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
  59. klaude_code/llm/openrouter/client.py +40 -289
  60. klaude_code/llm/openrouter/input.py +13 -35
  61. klaude_code/llm/openrouter/reasoning_handler.py +209 -0
  62. klaude_code/llm/registry.py +5 -75
  63. klaude_code/llm/responses/client.py +34 -55
  64. klaude_code/llm/responses/input.py +24 -26
  65. klaude_code/llm/usage.py +109 -0
  66. klaude_code/protocol/__init__.py +4 -0
  67. klaude_code/protocol/events.py +3 -2
  68. klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
  69. klaude_code/protocol/model.py +49 -4
  70. klaude_code/protocol/op.py +18 -16
  71. klaude_code/protocol/op_handler.py +28 -0
  72. klaude_code/{core → protocol}/sub_agent.py +7 -0
  73. klaude_code/session/export.py +150 -70
  74. klaude_code/session/session.py +28 -14
  75. klaude_code/session/templates/export_session.html +180 -42
  76. klaude_code/trace/__init__.py +2 -2
  77. klaude_code/trace/log.py +11 -5
  78. klaude_code/ui/__init__.py +91 -8
  79. klaude_code/ui/core/__init__.py +1 -0
  80. klaude_code/ui/core/display.py +103 -0
  81. klaude_code/ui/core/input.py +71 -0
  82. klaude_code/ui/modes/__init__.py +1 -0
  83. klaude_code/ui/modes/debug/__init__.py +1 -0
  84. klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
  85. klaude_code/ui/modes/exec/__init__.py +1 -0
  86. klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
  87. klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
  88. klaude_code/ui/modes/repl/clipboard.py +152 -0
  89. klaude_code/ui/modes/repl/completers.py +429 -0
  90. klaude_code/ui/modes/repl/display.py +60 -0
  91. klaude_code/ui/modes/repl/event_handler.py +375 -0
  92. klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
  93. klaude_code/ui/modes/repl/key_bindings.py +170 -0
  94. klaude_code/ui/{repl → modes/repl}/renderer.py +109 -132
  95. klaude_code/ui/renderers/assistant.py +21 -0
  96. klaude_code/ui/renderers/common.py +0 -16
  97. klaude_code/ui/renderers/developer.py +18 -18
  98. klaude_code/ui/renderers/diffs.py +36 -14
  99. klaude_code/ui/renderers/errors.py +1 -1
  100. klaude_code/ui/renderers/metadata.py +50 -27
  101. klaude_code/ui/renderers/sub_agent.py +43 -9
  102. klaude_code/ui/renderers/thinking.py +33 -1
  103. klaude_code/ui/renderers/tools.py +212 -20
  104. klaude_code/ui/renderers/user_input.py +19 -23
  105. klaude_code/ui/rich/__init__.py +1 -0
  106. klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
  107. klaude_code/ui/{renderers → rich}/status.py +29 -18
  108. klaude_code/ui/{base → rich}/theme.py +8 -2
  109. klaude_code/ui/terminal/__init__.py +1 -0
  110. klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
  111. klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
  112. klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
  113. klaude_code/ui/utils/__init__.py +1 -0
  114. klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
  115. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
  116. klaude_code-1.2.3.dist-info/RECORD +161 -0
  117. klaude_code/core/clipboard_manifest.py +0 -124
  118. klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
  119. klaude_code/ui/base/__init__.py +0 -1
  120. klaude_code/ui/base/display_abc.py +0 -36
  121. klaude_code/ui/base/input_abc.py +0 -20
  122. klaude_code/ui/repl/display.py +0 -36
  123. klaude_code/ui/repl/event_handler.py +0 -247
  124. klaude_code/ui/repl/input.py +0 -773
  125. klaude_code/ui/rich_ext/__init__.py +0 -1
  126. klaude_code-1.2.1.dist-info/RECORD +0 -151
  127. /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
  128. /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
  129. /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
  130. /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
  131. /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
  132. /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
  133. /klaude_code/ui/{base → core}/stage_manager.py +0 -0
  134. /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
  135. /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
  136. /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
  137. /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
  138. /klaude_code/ui/{base → utils}/debouncer.py +0 -0
  139. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
  140. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/entry_points.txt +0 -0
@@ -4,16 +4,29 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Klaude Code - $first_user_message</title>
7
- <link href="https://cdn.jsdelivr.net/npm/@fontsource/commit-mono/400.css" rel="stylesheet" />
8
- <link href="https://cdn.jsdelivr.net/npm/@fontsource/commit-mono/500.css" rel="stylesheet" />
9
- <link href="https://cdn.jsdelivr.net/npm/@fontsource/commit-mono/700.css" rel="stylesheet" />
7
+ <link
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>"
10
+ />
11
+ <link
12
+ href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka/400.css"
13
+ rel="stylesheet"
14
+ />
15
+ <link
16
+ href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka/500.css"
17
+ rel="stylesheet"
18
+ />
19
+ <link
20
+ href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka/800.css"
21
+ rel="stylesheet"
22
+ />
10
23
  <style>
11
24
  :root {
12
25
  --bg-body: #ededed;
13
26
  --bg-container: #f0f0f0;
14
27
  --bg-card: #f0f0f0;
15
28
  --border: #c8c8c8;
16
- --text: #333333;
29
+ --text: #111111;
17
30
  --text-dim: #64748b;
18
31
  --accent: #0851b2;
19
32
  --accent-dim: rgba(8, 145, 178, 0.08);
@@ -21,12 +34,14 @@
21
34
  --error: #dc2626;
22
35
  --bg-error: #ffebee;
23
36
  --bg-code: #f3f3f3;
24
- --font-sans: "Commit Mono", "SF Mono", Menlo, monospace;
25
- --font-mono: "Commit Mono", "SF Mono", Menlo, monospace;
26
- --font-size-xs: 11px;
27
- --font-size-sm: 12px;
28
- --font-size-base: 13px;
29
- --font-size-lg: 16px;
37
+ --font-sans: "Iosevka", "SF Mono", Menlo, monospace;
38
+ --font-mono: "Iosevka", "SF Mono", Menlo, monospace;
39
+ --font-markdown: "Iosevka", "SF Mono", Menlo, monospace;
40
+ --font-weight-bold: 800;
41
+ --font-size-xs: 13px;
42
+ --font-size-sm: 14px;
43
+ --font-size-base: 16px;
44
+ --font-size-lg: 18px;
30
45
  --spacing-xs: 4px;
31
46
  --spacing-sm: 8px;
32
47
  --spacing-md: 12px;
@@ -66,7 +81,7 @@
66
81
 
67
82
  .header h1 {
68
83
  font-size: 20px;
69
- font-weight: 700;
84
+ font-weight: var(--font-weight-bold);
70
85
  margin-bottom: 16px;
71
86
  color: var(--text);
72
87
  display: inline-block;
@@ -86,9 +101,8 @@
86
101
 
87
102
  .meta-label {
88
103
  font-size: var(--font-size-sm);
89
- font-weight: 700;
104
+ font-weight: var(--font-weight-bold);
90
105
  text-transform: uppercase;
91
- letter-spacing: 0.05em;
92
106
  color: var(--text-dim);
93
107
  }
94
108
 
@@ -116,8 +130,7 @@
116
130
  font-family: var(--font-mono);
117
131
  font-size: var(--font-size-sm);
118
132
  text-transform: uppercase;
119
- letter-spacing: 0.05em;
120
- font-weight: 700;
133
+ font-weight: var(--font-weight-bold);
121
134
  cursor: pointer;
122
135
  user-select: none;
123
136
  list-style: none;
@@ -184,14 +197,17 @@
184
197
 
185
198
  .role-label {
186
199
  font-size: var(--font-size-sm);
187
- font-weight: 700;
200
+ font-weight: var(--font-weight-bold);
188
201
  text-transform: uppercase;
189
- letter-spacing: 0.05em;
190
202
  margin-bottom: 4px;
191
203
  color: var(--text-dim);
192
204
  display: flex;
193
205
  align-items: center;
194
206
  gap: 8px;
207
+ width: 100%;
208
+ }
209
+ .message-header .role-label {
210
+ width: auto;
195
211
  }
196
212
  .role-label.user {
197
213
  color: var(--accent);
@@ -210,8 +226,7 @@
210
226
  font-family: var(--font-mono);
211
227
  font-size: var(--font-size-xs);
212
228
  text-transform: uppercase;
213
- letter-spacing: 0.05em;
214
- font-weight: 700;
229
+ font-weight: var(--font-weight-bold);
215
230
  cursor: pointer;
216
231
  user-select: none;
217
232
  list-style: none;
@@ -279,14 +294,14 @@
279
294
  border: 1px solid var(--border);
280
295
  background: transparent;
281
296
  color: var(--text-dim);
297
+ font-family: var(--font-mono);
282
298
  font-size: var(--font-size-xs);
283
299
  text-transform: uppercase;
284
- letter-spacing: 0.08em;
285
300
  padding: 2px 10px;
286
301
  border-radius: 999px;
287
302
  cursor: pointer;
288
303
  transition: color 0.2s, border-color 0.2s, background 0.2s;
289
- font-weight: 700;
304
+ font-weight: var(--font-weight-bold);
290
305
  }
291
306
  .raw-toggle:hover {
292
307
  color: var(--text);
@@ -303,14 +318,14 @@
303
318
  border: 1px solid var(--border);
304
319
  background: transparent;
305
320
  color: var(--text-dim);
321
+ font-family: var(--font-mono);
306
322
  font-size: var(--font-size-xs);
307
323
  text-transform: uppercase;
308
- letter-spacing: 0.08em;
309
324
  padding: 2px 10px;
310
325
  border-radius: 999px;
311
326
  cursor: pointer;
312
327
  transition: color 0.2s, border-color 0.2s, background 0.2s;
313
- font-weight: 700;
328
+ font-weight: var(--font-weight-bold);
314
329
  }
315
330
  .copy-raw-btn:hover {
316
331
  color: var(--text);
@@ -321,14 +336,14 @@
321
336
  border: 1px solid var(--border);
322
337
  background: transparent;
323
338
  color: var(--text-dim);
339
+ font-family: var(--font-mono);
324
340
  font-size: var(--font-size-xs);
325
341
  text-transform: uppercase;
326
- letter-spacing: 0.08em;
327
342
  padding: 2px 10px;
328
343
  border-radius: 999px;
329
344
  cursor: pointer;
330
345
  transition: color 0.2s, border-color 0.2s, background 0.2s;
331
- font-weight: 700;
346
+ font-weight: var(--font-weight-bold);
332
347
  }
333
348
  .copy-mermaid-btn:hover {
334
349
  color: var(--text);
@@ -361,7 +376,7 @@
361
376
  border-radius: 0;
362
377
  padding: 0;
363
378
  box-shadow: none;
364
- font-weight: 700;
379
+ font-weight: var(--font-weight-bold);
365
380
  }
366
381
 
367
382
  .thinking-block {
@@ -371,9 +386,15 @@
371
386
  border-left: 2px solid var(--border);
372
387
  color: var(--text-dim);
373
388
  font-style: italic;
374
- font-size: var(--font-size-base);
389
+ font-size: var(--font-size-sm);
375
390
  background: var(--bg-body);
376
391
  }
392
+ .thinking-block.markdown-body p {
393
+ margin-bottom: 8px;
394
+ }
395
+ .thinking-block.markdown-body p:last-child {
396
+ margin-bottom: 0;
397
+ }
377
398
 
378
399
  /* Response Metadata */
379
400
  .response-metadata {
@@ -392,7 +413,7 @@
392
413
  line-height: 1.4;
393
414
  }
394
415
  .metadata-model {
395
- font-weight: 700;
416
+ font-weight: var(--font-weight-bold);
396
417
  color: var(--text);
397
418
  }
398
419
  .metadata-provider {
@@ -427,7 +448,7 @@
427
448
  }
428
449
 
429
450
  .tool-name {
430
- font-weight: 700;
451
+ font-weight: var(--font-weight-bold);
431
452
  color: var(--accent);
432
453
  }
433
454
  .tool-id {
@@ -435,6 +456,24 @@
435
456
  font-size: var(--font-size-xs);
436
457
  }
437
458
 
459
+ .tool-header-right {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 8px;
463
+ }
464
+
465
+ .timestamp {
466
+ font-family: var(--font-mono);
467
+ font-size: var(--font-size-xs);
468
+ color: var(--text-dim);
469
+ margin-left: auto;
470
+ font-weight: normal;
471
+ }
472
+
473
+ .assistant-toolbar .timestamp {
474
+ margin-right: 8px;
475
+ }
476
+
438
477
  .tool-args {
439
478
  padding: 12px;
440
479
  background: var(--bg-body);
@@ -455,8 +494,7 @@
455
494
  font-family: var(--font-mono);
456
495
  font-size: var(--font-size-xs);
457
496
  text-transform: uppercase;
458
- letter-spacing: 0.05em;
459
- font-weight: 700;
497
+ font-weight: var(--font-weight-bold);
460
498
  cursor: pointer;
461
499
  user-select: none;
462
500
  list-style: none;
@@ -557,7 +595,7 @@
557
595
 
558
596
  /* Markdown Elements */
559
597
  .markdown-body {
560
- font-family: var(--font-sans);
598
+ font-family: var(--font-markdown);
561
599
  }
562
600
  .markdown-body hr {
563
601
  height: 0;
@@ -574,7 +612,7 @@
574
612
  border: 1px solid var(--border);
575
613
  }
576
614
  .markdown-body code {
577
- font-family: var(--font-mono);
615
+ font-family: var(--font-markdown);
578
616
  font-size: 0.9em;
579
617
  background: var(--bg-code);
580
618
  padding: 2px 4px;
@@ -648,8 +686,7 @@
648
686
  font-family: var(--font-mono);
649
687
  font-size: var(--font-size-xs);
650
688
  text-transform: uppercase;
651
- letter-spacing: 0.05em;
652
- font-weight: 700;
689
+ font-weight: var(--font-weight-bold);
653
690
  cursor: pointer;
654
691
  user-select: none;
655
692
  list-style: none;
@@ -689,6 +726,17 @@
689
726
  padding-top: 24px;
690
727
  }
691
728
 
729
+ .footer-link {
730
+ color: inherit;
731
+ text-decoration: none;
732
+ font-weight: 600;
733
+ transition: color 0.2s;
734
+ }
735
+
736
+ .footer-link:hover {
737
+ color: var(--accent);
738
+ }
739
+
692
740
  .expandable {
693
741
  cursor: pointer;
694
742
  }
@@ -757,6 +805,42 @@
757
805
  color: var(--text-dim);
758
806
  }
759
807
 
808
+ /* Scroll to Bottom Button */
809
+ .scroll-btn {
810
+ position: fixed;
811
+ bottom: 24px;
812
+ right: 24px;
813
+ width: 40px;
814
+ height: 40px;
815
+ border-radius: 50%;
816
+ background: var(--bg-card);
817
+ border: 1px solid var(--border);
818
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
819
+ color: var(--text-dim);
820
+ display: flex;
821
+ align-items: center;
822
+ justify-content: center;
823
+ cursor: pointer;
824
+ opacity: 0;
825
+ visibility: hidden;
826
+ transform: translateY(10px);
827
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
828
+ z-index: 100;
829
+ }
830
+
831
+ .scroll-btn:hover {
832
+ color: var(--accent);
833
+ border-color: var(--accent);
834
+ transform: translateY(-2px);
835
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
836
+ }
837
+
838
+ .scroll-btn.visible {
839
+ opacity: 1;
840
+ visibility: visible;
841
+ transform: translateY(0);
842
+ }
843
+
760
844
  /* Tool details in Available Tools section */
761
845
  .tool-details {
762
846
  margin-bottom: 2px;
@@ -798,9 +882,8 @@
798
882
  }
799
883
  .tool-params-title {
800
884
  font-size: var(--font-size-xs);
801
- font-weight: 700;
885
+ font-weight: var(--font-weight-bold);
802
886
  text-transform: uppercase;
803
- letter-spacing: 0.05em;
804
887
  color: var(--text-dim);
805
888
  margin-bottom: 8px;
806
889
  }
@@ -858,9 +941,7 @@
858
941
  <div
859
942
  class="details-content system-prompt-content"
860
943
  style="font-family: var(--font-mono); white-space: pre-wrap"
861
- >
862
- $system_prompt
863
- </div>
944
+ >$system_prompt</div>
864
945
  </details>
865
946
 
866
947
  <details class="collapsible-section">
@@ -871,17 +952,48 @@
871
952
  <div class="message-stream">$messages_html</div>
872
953
 
873
954
  <div class="footer">
874
- Generated by klaude-code &bull; $footer_time &bull; $total_messages
875
- messages
955
+ Generated by
956
+ <a
957
+ href="https://github.com/inspirepan/klaude-code"
958
+ class="footer-link"
959
+ target="_blank"
960
+ >klaude-code</a
961
+ >
962
+ &bull; $footer_time &bull; $total_messages messages
876
963
  </div>
877
964
  </div>
878
965
 
966
+ <div id="scroll-btn" class="scroll-btn" title="Scroll to bottom">
967
+ <svg
968
+ xmlns="http://www.w3.org/2000/svg"
969
+ width="20"
970
+ height="20"
971
+ viewBox="0 0 24 24"
972
+ fill="none"
973
+ stroke="currentColor"
974
+ stroke-width="2"
975
+ stroke-linecap="round"
976
+ stroke-linejoin="round"
977
+ >
978
+ <line x1="12" y1="5" x2="12" y2="19"></line>
979
+ <polyline points="19 12 12 19 5 12"></polyline>
980
+ </svg>
981
+ </div>
982
+
879
983
  <link
880
984
  rel="stylesheet"
881
985
  href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css"
882
986
  />
883
987
  <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
884
988
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
989
+ <script type="module">
990
+ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
991
+ mermaid.initialize({
992
+ startOnLoad: true,
993
+ theme: "neutral",
994
+ fontFamily: '"Iosevka", "SF Mono", Menlo, monospace',
995
+ });
996
+ </script>
885
997
  <script>
886
998
  // Markdown rendering and Syntax Highlighting
887
999
  document.querySelectorAll(".markdown-content").forEach((el) => {
@@ -1077,6 +1189,32 @@
1077
1189
  }
1078
1190
  });
1079
1191
  });
1192
+
1193
+ // Scroll to bottom button
1194
+ const scrollBtn = document.getElementById("scroll-btn");
1195
+
1196
+ function updateScrollBtn() {
1197
+ // Show button if we are not at the bottom
1198
+ const isAtBottom =
1199
+ window.innerHeight + window.scrollY >=
1200
+ document.body.offsetHeight - 100;
1201
+ if (isAtBottom) {
1202
+ scrollBtn.classList.remove("visible");
1203
+ } else {
1204
+ scrollBtn.classList.add("visible");
1205
+ }
1206
+ }
1207
+
1208
+ window.addEventListener("scroll", updateScrollBtn);
1209
+ window.addEventListener("resize", updateScrollBtn);
1210
+ updateScrollBtn(); // Initial check
1211
+
1212
+ scrollBtn.addEventListener("click", () => {
1213
+ window.scrollTo({
1214
+ top: document.body.scrollHeight,
1215
+ behavior: "smooth",
1216
+ });
1217
+ });
1080
1218
  </script>
1081
1219
  </body>
1082
1220
  </html>
@@ -1,3 +1,3 @@
1
- from .log import DebugType, log, log_debug, logger, set_debug_logging
1
+ from .log import DebugType, is_debug_enabled, log, log_debug, logger, set_debug_logging
2
2
 
3
- __all__ = ["log", "log_debug", "logger", "set_debug_logging", "DebugType"]
3
+ __all__ = ["log", "log_debug", "logger", "set_debug_logging", "DebugType", "is_debug_enabled"]
klaude_code/trace/log.py CHANGED
@@ -7,7 +7,7 @@ from rich.console import Console
7
7
  from rich.logging import RichHandler
8
8
  from rich.text import Text
9
9
 
10
- from klaude_code.const import DEFAULT_DEBUG_LOG_FILE, LOG_BACKUP_COUNT, LOG_MAX_BYTES
10
+ from klaude_code import const
11
11
 
12
12
  # Module-level logger
13
13
  logger = logging.getLogger("klaude_code")
@@ -87,13 +87,13 @@ def set_debug_logging(
87
87
 
88
88
  # Determine output mode
89
89
  use_file = write_to_file if write_to_file is not None else True
90
- file_path = log_file if log_file is not None else DEFAULT_DEBUG_LOG_FILE
90
+ file_path = log_file if log_file is not None else const.DEFAULT_DEBUG_LOG_FILE
91
91
 
92
92
  if use_file:
93
93
  _file_handler = RotatingFileHandler(
94
94
  file_path,
95
- maxBytes=LOG_MAX_BYTES,
96
- backupCount=LOG_BACKUP_COUNT,
95
+ maxBytes=const.LOG_MAX_BYTES,
96
+ backupCount=const.LOG_BACKUP_COUNT,
97
97
  encoding="utf-8",
98
98
  )
99
99
  _file_handler.setLevel(logging.DEBUG)
@@ -121,7 +121,8 @@ def log(*objects: str | tuple[str, str], style: str = "") -> None:
121
121
  style: Default style for all objects
122
122
  """
123
123
  log_console.print(
124
- *((Text(obj[0], style=obj[1]) if isinstance(obj, tuple) else Text(obj)) for obj in objects), style=style
124
+ *((Text(obj[0], style=obj[1]) if isinstance(obj, tuple) else Text(obj)) for obj in objects),
125
+ style=style,
125
126
  )
126
127
 
127
128
 
@@ -160,3 +161,8 @@ def _build_message(objects: Iterable[str | tuple[str, str]]) -> str:
160
161
  else:
161
162
  parts.append(obj)
162
163
  return " ".join(parts)
164
+
165
+
166
+ def is_debug_enabled() -> bool:
167
+ """Check if debug logging is currently enabled."""
168
+ return _debug_enabled
@@ -1,8 +1,91 @@
1
- from .base.debug_event_display import DebugEventDisplay
2
- from .base.display_abc import DisplayABC
3
- from .base.exec_display import ExecDisplay
4
- from .base.input_abc import InputProviderABC
5
- from .repl.display import REPLDisplay
6
- from .repl.input import PromptToolkitInput
7
-
8
- __all__ = ["DisplayABC", "InputProviderABC", "REPLDisplay", "PromptToolkitInput", "DebugEventDisplay", "ExecDisplay"]
1
+ """
2
+ UI Module - Display and Input Abstractions for klaude-code
3
+
4
+ This module provides the UI layer for klaude-code, including display modes
5
+ and input providers. The UI is designed around three main concepts:
6
+
7
+ Display Modes:
8
+ - REPLDisplay: Interactive terminal mode with Rich rendering, spinners, and live updates
9
+ - ExecDisplay: Non-interactive exec mode that only outputs task results
10
+ - DebugEventDisplay: Decorator that logs all events for debugging purposes
11
+
12
+ Input Providers:
13
+ - PromptToolkitInput: Interactive input with prompt-toolkit (completions, keybindings)
14
+
15
+ Factory Functions:
16
+ - create_default_display(): Creates the appropriate display for interactive mode
17
+ - create_exec_display(): Creates the appropriate display for exec mode
18
+ """
19
+
20
+ # --- Abstract Interfaces ---
21
+ from .core.display import DisplayABC
22
+ from .core.input import InputProviderABC
23
+ from .modes.debug.display import DebugEventDisplay
24
+ from .modes.exec.display import ExecDisplay, StreamJsonDisplay
25
+
26
+ # --- Display Mode Implementations ---
27
+ from .modes.repl.display import REPLDisplay
28
+
29
+ # --- Input Implementations ---
30
+ from .modes.repl.input_prompt_toolkit import PromptToolkitInput
31
+ from .terminal.notifier import TerminalNotifier
32
+
33
+
34
+ def create_default_display(
35
+ debug: bool = False,
36
+ theme: str | None = None,
37
+ notifier: TerminalNotifier | None = None,
38
+ ) -> DisplayABC:
39
+ """
40
+ Create the default display for interactive REPL mode.
41
+
42
+ Args:
43
+ debug: If True, wrap the display with DebugEventDisplay to log all events.
44
+ theme: Optional theme name ("light" or "dark") for syntax highlighting.
45
+ notifier: Optional terminal notifier for desktop notifications.
46
+
47
+ Returns:
48
+ A DisplayABC implementation suitable for interactive use.
49
+ """
50
+ repl_display = REPLDisplay(theme=theme, notifier=notifier)
51
+ if debug:
52
+ return DebugEventDisplay(repl_display)
53
+ return repl_display
54
+
55
+
56
+ def create_exec_display(debug: bool = False, stream_json: bool = False) -> DisplayABC:
57
+ """
58
+ Create a display for exec (non-interactive) mode.
59
+
60
+ Args:
61
+ debug: If True, wrap the display with DebugEventDisplay to log all events.
62
+ stream_json: If True, stream all events as JSON lines instead of normal output.
63
+
64
+ Returns:
65
+ A DisplayABC implementation that only outputs task results.
66
+ """
67
+ if stream_json:
68
+ return StreamJsonDisplay()
69
+ exec_display = ExecDisplay()
70
+ if debug:
71
+ return DebugEventDisplay(exec_display)
72
+ return exec_display
73
+
74
+
75
+ __all__ = [
76
+ # Abstract interfaces
77
+ "DisplayABC",
78
+ "InputProviderABC",
79
+ # Display mode implementations
80
+ "REPLDisplay",
81
+ "ExecDisplay",
82
+ "StreamJsonDisplay",
83
+ "DebugEventDisplay",
84
+ # Input implementations
85
+ "PromptToolkitInput",
86
+ # Factory functions
87
+ "create_default_display",
88
+ "create_exec_display",
89
+ # Supporting types
90
+ "TerminalNotifier",
91
+ ]
@@ -0,0 +1 @@
1
+ # Core UI abstractions
@@ -0,0 +1,103 @@
1
+ from abc import ABC, abstractmethod
2
+ from asyncio import Queue
3
+
4
+ from klaude_code.protocol import events
5
+ from klaude_code.trace import log
6
+
7
+
8
+ class DisplayABC(ABC):
9
+ """
10
+ Abstract base class for UI display implementations.
11
+
12
+ A Display is responsible for rendering events from the executor to the user.
13
+ Implementations can range from simple text output (ExecDisplay) to rich
14
+ interactive terminals (REPLDisplay) or debugging wrappers (DebugEventDisplay).
15
+
16
+ Lifecycle:
17
+ 1. start() is called once before any events are consumed.
18
+ 2. consume_event() is called for each event from the executor.
19
+ 3. stop() is called once when the display is shutting down (after EndEvent).
20
+
21
+ Typical Usage:
22
+ display = create_default_display()
23
+ await display.consume_event_loop(event_queue)
24
+
25
+ # Or manually:
26
+ await display.start()
27
+ try:
28
+ async for event in events:
29
+ if isinstance(event, EndEvent):
30
+ break
31
+ await display.consume_event(event)
32
+ finally:
33
+ await display.stop()
34
+
35
+ Thread Safety:
36
+ Display implementations should be used from a single async task.
37
+ The consume_event_loop method handles the standard event loop pattern.
38
+ """
39
+
40
+ @abstractmethod
41
+ async def consume_event(self, event: events.Event) -> None:
42
+ """
43
+ Process a single event from the executor.
44
+
45
+ This method is called for each event except EndEvent, which triggers stop().
46
+ Implementations should handle all relevant event types and render them
47
+ appropriately for the user.
48
+
49
+ Args:
50
+ event: The event to process. See klaude_code.protocol.events for types.
51
+ """
52
+
53
+ @abstractmethod
54
+ async def start(self) -> None:
55
+ """
56
+ Initialize the display before processing events.
57
+
58
+ Called once before any consume_event calls. Use this for any setup
59
+ that needs to happen before rendering begins (e.g., initializing
60
+ terminal state, starting background tasks).
61
+ """
62
+
63
+ @abstractmethod
64
+ async def stop(self) -> None:
65
+ """
66
+ Clean up the display after all events have been processed.
67
+
68
+ Called once after EndEvent is received. Use this for cleanup such as
69
+ stopping spinners, restoring terminal state, or flushing output buffers.
70
+ """
71
+
72
+ async def consume_event_loop(self, q: Queue[events.Event]) -> None:
73
+ """
74
+ Main event loop that processes events from a queue.
75
+
76
+ This is the standard entry point for running a display. It handles:
77
+ - Calling start() before processing
78
+ - Consuming events until EndEvent is received
79
+ - Calling stop() after EndEvent
80
+ - Error handling and logging for individual events
81
+
82
+ Args:
83
+ q: An asyncio Queue of events to process. The loop exits when
84
+ an EndEvent is received.
85
+ """
86
+ await self.start()
87
+ while True:
88
+ event = await q.get()
89
+ try:
90
+ if isinstance(event, events.EndEvent):
91
+ await self.stop()
92
+ break
93
+ await self.consume_event(event)
94
+ except Exception as e:
95
+ import traceback
96
+
97
+ log(
98
+ f"Error in consume_event_loop, {e.__class__.__name__}, {e}",
99
+ style="red",
100
+ )
101
+ log(traceback.format_exc(), style="red")
102
+ finally:
103
+ q.task_done()