klaude-code 1.2.2__py3-none-any.whl → 1.2.4__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 (60) hide show
  1. klaude_code/cli/main.py +7 -0
  2. klaude_code/cli/runtime.py +6 -6
  3. klaude_code/command/__init__.py +9 -5
  4. klaude_code/command/clear_cmd.py +3 -24
  5. klaude_code/command/command_abc.py +36 -1
  6. klaude_code/command/export_cmd.py +16 -20
  7. klaude_code/command/help_cmd.py +1 -0
  8. klaude_code/command/model_cmd.py +3 -30
  9. klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
  10. klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
  11. klaude_code/command/prompt-init.md +2 -5
  12. klaude_code/command/prompt_command.py +3 -3
  13. klaude_code/command/registry.py +6 -7
  14. klaude_code/command/status_cmd.py +111 -0
  15. klaude_code/config/config.py +1 -1
  16. klaude_code/config/list_model.py +1 -1
  17. klaude_code/const/__init__.py +1 -1
  18. klaude_code/core/agent.py +2 -11
  19. klaude_code/core/executor.py +155 -14
  20. klaude_code/core/prompts/prompt-gemini.md +1 -1
  21. klaude_code/core/reminders.py +24 -0
  22. klaude_code/core/task.py +10 -0
  23. klaude_code/core/tool/shell/bash_tool.py +6 -2
  24. klaude_code/core/tool/sub_agent_tool.py +1 -1
  25. klaude_code/core/tool/tool_context.py +1 -1
  26. klaude_code/core/tool/tool_registry.py +1 -1
  27. klaude_code/core/tool/tool_runner.py +1 -1
  28. klaude_code/core/tool/web/mermaid_tool.py +1 -1
  29. klaude_code/llm/__init__.py +3 -4
  30. klaude_code/llm/anthropic/client.py +12 -9
  31. klaude_code/llm/openai_compatible/client.py +2 -18
  32. klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
  33. klaude_code/llm/openrouter/client.py +2 -18
  34. klaude_code/llm/openrouter/input.py +6 -2
  35. klaude_code/llm/registry.py +2 -71
  36. klaude_code/llm/responses/client.py +2 -0
  37. klaude_code/llm/{metadata_tracker.py → usage.py} +49 -2
  38. klaude_code/protocol/commands.py +1 -0
  39. klaude_code/protocol/llm_param.py +12 -0
  40. klaude_code/protocol/model.py +30 -3
  41. klaude_code/protocol/op.py +14 -14
  42. klaude_code/protocol/op_handler.py +28 -0
  43. klaude_code/protocol/tools.py +0 -2
  44. klaude_code/session/export.py +124 -35
  45. klaude_code/session/session.py +1 -1
  46. klaude_code/session/templates/export_session.html +383 -39
  47. klaude_code/ui/__init__.py +6 -2
  48. klaude_code/ui/modes/exec/display.py +26 -0
  49. klaude_code/ui/modes/repl/event_handler.py +5 -1
  50. klaude_code/ui/renderers/developer.py +62 -11
  51. klaude_code/ui/renderers/metadata.py +33 -24
  52. klaude_code/ui/renderers/sub_agent.py +1 -1
  53. klaude_code/ui/renderers/tools.py +2 -2
  54. klaude_code/ui/renderers/user_input.py +18 -22
  55. klaude_code/ui/rich/status.py +13 -2
  56. {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.dist-info}/METADATA +1 -1
  57. {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.dist-info}/RECORD +60 -58
  58. /klaude_code/{core → protocol}/sub_agent.py +0 -0
  59. {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.dist-info}/WHEEL +0 -0
  60. {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.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
  }
@@ -825,9 +908,81 @@
825
908
  font-size: var(--font-size-sm);
826
909
  margin-top: 2px;
827
910
  }
911
+ /* TOC Sidebar */
912
+ .toc-sidebar {
913
+ position: fixed;
914
+ top: 33vh;
915
+ left: 20px;
916
+ width: 220px;
917
+ bottom: 33vh;
918
+ overflow-y: auto;
919
+ padding-right: 12px;
920
+ /* Vertical padding to offset mask */
921
+ padding-top: 30px;
922
+ padding-bottom: 30px;
923
+ display: none;
924
+ scrollbar-width: none;
925
+ z-index: 100;
926
+ /* Linear mask for fading edges */
927
+ -webkit-mask-image: linear-gradient(
928
+ to bottom,
929
+ transparent 0%,
930
+ black 30px,
931
+ black calc(100% - 30px),
932
+ transparent 100%
933
+ );
934
+ mask-image: linear-gradient(
935
+ to bottom,
936
+ transparent 0%,
937
+ black 30px,
938
+ black calc(100% - 30px),
939
+ transparent 100%
940
+ );
941
+ }
942
+
943
+ .toc-sidebar::-webkit-scrollbar {
944
+ display: none;
945
+ }
946
+
947
+ /* Show TOC on wide screens */
948
+ @media (min-width: 1400px) {
949
+ .toc-sidebar {
950
+ display: block;
951
+ }
952
+ }
953
+
954
+ .toc-item {
955
+ display: block;
956
+ padding: 3px 10px;
957
+ margin-bottom: 1px;
958
+ font-family: var(--font-mono);
959
+ font-size: 12px;
960
+ line-height: 1.3;
961
+ color: var(--text-dim);
962
+ text-decoration: none;
963
+ cursor: pointer;
964
+ transition: all 0.2s;
965
+ white-space: nowrap;
966
+ overflow: hidden;
967
+ text-overflow: ellipsis;
968
+ border-radius: 4px;
969
+ }
970
+
971
+ .toc-item:hover {
972
+ color: var(--text);
973
+ background: rgba(0, 0, 0, 0.03);
974
+ }
975
+
976
+ .toc-item.active {
977
+ color: var(--text);
978
+ background: var(--bg-card);
979
+ font-weight: bold;
980
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
981
+ }
828
982
  </style>
829
983
  </head>
830
984
  <body>
985
+ <div id="toc-sidebar" class="toc-sidebar"></div>
831
986
  <div class="container">
832
987
  <div class="header">
833
988
  <h1>Klaude Code</h1>
@@ -871,17 +1026,48 @@
871
1026
  <div class="message-stream">$messages_html</div>
872
1027
 
873
1028
  <div class="footer">
874
- Generated by klaude-code &bull; $footer_time &bull; $total_messages
875
- messages
1029
+ Generated by
1030
+ <a
1031
+ href="https://github.com/inspirepan/klaude-code"
1032
+ class="footer-link"
1033
+ target="_blank"
1034
+ >klaude-code</a
1035
+ >
1036
+ &bull; $footer_time &bull; $total_messages messages
876
1037
  </div>
877
1038
  </div>
878
1039
 
1040
+ <div id="scroll-btn" class="scroll-btn" title="Scroll to bottom">
1041
+ <svg
1042
+ xmlns="http://www.w3.org/2000/svg"
1043
+ width="20"
1044
+ height="20"
1045
+ viewBox="0 0 24 24"
1046
+ fill="none"
1047
+ stroke="currentColor"
1048
+ stroke-width="2"
1049
+ stroke-linecap="round"
1050
+ stroke-linejoin="round"
1051
+ >
1052
+ <line x1="12" y1="5" x2="12" y2="19"></line>
1053
+ <polyline points="19 12 12 19 5 12"></polyline>
1054
+ </svg>
1055
+ </div>
1056
+
879
1057
  <link
880
1058
  rel="stylesheet"
881
1059
  href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css"
882
1060
  />
883
1061
  <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
884
1062
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1063
+ <script type="module">
1064
+ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
1065
+ mermaid.initialize({
1066
+ startOnLoad: true,
1067
+ theme: "neutral",
1068
+ fontFamily: '"Iosevka", "SF Mono", Menlo, monospace',
1069
+ });
1070
+ </script>
885
1071
  <script>
886
1072
  // Markdown rendering and Syntax Highlighting
887
1073
  document.querySelectorAll(".markdown-content").forEach((el) => {
@@ -1077,6 +1263,164 @@
1077
1263
  }
1078
1264
  });
1079
1265
  });
1266
+
1267
+ // Scroll to bottom button
1268
+ const scrollBtn = document.getElementById("scroll-btn");
1269
+
1270
+ function updateScrollBtn() {
1271
+ // Show button if we are not at the bottom
1272
+ const isAtBottom =
1273
+ window.innerHeight + window.scrollY >=
1274
+ document.body.offsetHeight - 100;
1275
+ if (isAtBottom) {
1276
+ scrollBtn.classList.remove("visible");
1277
+ } else {
1278
+ scrollBtn.classList.add("visible");
1279
+ }
1280
+ }
1281
+
1282
+ window.addEventListener("scroll", updateScrollBtn);
1283
+ window.addEventListener("resize", updateScrollBtn);
1284
+ updateScrollBtn(); // Initial check
1285
+
1286
+ scrollBtn.addEventListener("click", () => {
1287
+ window.scrollTo({
1288
+ top: document.body.scrollHeight,
1289
+ behavior: "smooth",
1290
+ });
1291
+ });
1292
+
1293
+ // TOC Logic
1294
+ document.addEventListener("DOMContentLoaded", () => {
1295
+ const tocSidebar = document.getElementById("toc-sidebar");
1296
+ if (!tocSidebar) return;
1297
+
1298
+ const sections = [];
1299
+ let userCount = 0;
1300
+ let assistantCount = 0;
1301
+
1302
+ // 1. System Prompt
1303
+ const sysPrompt = document.querySelector(
1304
+ ".collapsible-section summary"
1305
+ );
1306
+ if (sysPrompt && sysPrompt.textContent.includes("System Prompt")) {
1307
+ const details = sysPrompt.parentElement;
1308
+ details.id = details.id || "section-system-prompt";
1309
+ sections.push({
1310
+ id: details.id,
1311
+ label: "System Prompt",
1312
+ el: details,
1313
+ });
1314
+ }
1315
+
1316
+ // 2. Messages and Tools
1317
+ const stream = document.querySelector(".message-stream");
1318
+ if (stream) {
1319
+ // Use a Walker to find top-level relevant items if structure is complex
1320
+ // But assuming flat children of message-stream based on CSS
1321
+ Array.from(stream.children).forEach((child) => {
1322
+ let label = null;
1323
+ let idPrefix = "section";
1324
+
1325
+ if (child.classList.contains("message-group")) {
1326
+ const roleLabel = child.querySelector(".role-label");
1327
+ if (roleLabel) {
1328
+ if (roleLabel.classList.contains("user")) {
1329
+ userCount++;
1330
+ label = `USER $${userCount}`;
1331
+ idPrefix = "user";
1332
+ } else if (roleLabel.classList.contains("assistant")) {
1333
+ assistantCount++;
1334
+ label = `ASSISTANT $${assistantCount}`;
1335
+ idPrefix = "assistant";
1336
+ } else {
1337
+ label = roleLabel.textContent.trim();
1338
+ }
1339
+ }
1340
+ } else if (child.classList.contains("tool-call")) {
1341
+ const toolName = child.querySelector(".tool-name");
1342
+ if (toolName) {
1343
+ label = toolName.textContent.trim();
1344
+ idPrefix = "tool";
1345
+ } else {
1346
+ label = "Tool";
1347
+ }
1348
+ }
1349
+
1350
+ if (label) {
1351
+ child.id =
1352
+ child.id ||
1353
+ `$${idPrefix}-$${Math.random().toString(36).substr(2, 9)}`;
1354
+ sections.push({ id: child.id, label: label, el: child });
1355
+ }
1356
+ });
1357
+ }
1358
+
1359
+ // Render TOC
1360
+ sections.forEach((section) => {
1361
+ const item = document.createElement("div");
1362
+ item.className = "toc-item";
1363
+ item.textContent = section.label;
1364
+ item.dataset.target = section.id;
1365
+ item.addEventListener("click", () => {
1366
+ section.el.scrollIntoView({ behavior: "smooth", block: "start" });
1367
+ });
1368
+ tocSidebar.appendChild(item);
1369
+ });
1370
+
1371
+ // Scroll Spy with throttling
1372
+ let ticking = false;
1373
+ const offset = 150; // Pixel offset for "active" area
1374
+
1375
+ function updateActiveSection() {
1376
+ let currentId = sections.length > 0 ? sections[0].id : null;
1377
+ const scrollY = window.scrollY;
1378
+
1379
+ // We look for the last section that has passed the top threshold (+ offset)
1380
+ for (let i = 0; i < sections.length; i++) {
1381
+ const el = sections[i].el;
1382
+ // If element top is above the "active line" (scrollY + offset)
1383
+ if (el.offsetTop <= scrollY + offset) {
1384
+ currentId = sections[i].id;
1385
+ } else {
1386
+ // Since sections are ordered by position, we can stop once we find one below the line
1387
+ break;
1388
+ }
1389
+ }
1390
+
1391
+ // Update UI
1392
+ document.querySelectorAll(".toc-item").forEach((item) => {
1393
+ const isActive = item.dataset.target === currentId;
1394
+ if (item.classList.contains("active") !== isActive) {
1395
+ item.classList.toggle("active", isActive);
1396
+ if (isActive) {
1397
+ // Auto-scroll sidebar to visible
1398
+ const sidebarRect = tocSidebar.getBoundingClientRect();
1399
+ const itemRect = item.getBoundingClientRect();
1400
+ // Check if item is out of view in sidebar (accounting for mask)
1401
+ if (
1402
+ itemRect.top < sidebarRect.top + 40 ||
1403
+ itemRect.bottom > sidebarRect.bottom - 40
1404
+ ) {
1405
+ item.scrollIntoView({ behavior: "smooth", block: "center" });
1406
+ }
1407
+ }
1408
+ }
1409
+ });
1410
+
1411
+ ticking = false;
1412
+ }
1413
+
1414
+ window.addEventListener("scroll", () => {
1415
+ if (!ticking) {
1416
+ window.requestAnimationFrame(updateActiveSection);
1417
+ ticking = true;
1418
+ }
1419
+ });
1420
+
1421
+ // Initial update
1422
+ updateActiveSection();
1423
+ });
1080
1424
  </script>
1081
1425
  </body>
1082
1426
  </html>
@@ -21,7 +21,7 @@ Factory Functions:
21
21
  from .core.display import DisplayABC
22
22
  from .core.input import InputProviderABC
23
23
  from .modes.debug.display import DebugEventDisplay
24
- from .modes.exec.display import ExecDisplay
24
+ from .modes.exec.display import ExecDisplay, StreamJsonDisplay
25
25
 
26
26
  # --- Display Mode Implementations ---
27
27
  from .modes.repl.display import REPLDisplay
@@ -53,16 +53,19 @@ def create_default_display(
53
53
  return repl_display
54
54
 
55
55
 
56
- def create_exec_display(debug: bool = False) -> DisplayABC:
56
+ def create_exec_display(debug: bool = False, stream_json: bool = False) -> DisplayABC:
57
57
  """
58
58
  Create a display for exec (non-interactive) mode.
59
59
 
60
60
  Args:
61
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.
62
63
 
63
64
  Returns:
64
65
  A DisplayABC implementation that only outputs task results.
65
66
  """
67
+ if stream_json:
68
+ return StreamJsonDisplay()
66
69
  exec_display = ExecDisplay()
67
70
  if debug:
68
71
  return DebugEventDisplay(exec_display)
@@ -76,6 +79,7 @@ __all__ = [
76
79
  # Display mode implementations
77
80
  "REPLDisplay",
78
81
  "ExecDisplay",
82
+ "StreamJsonDisplay",
79
83
  "DebugEventDisplay",
80
84
  # Input implementations
81
85
  "PromptToolkitInput",
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  from typing import override
2
3
 
3
4
  from klaude_code.protocol import events
@@ -35,3 +36,28 @@ class ExecDisplay(DisplayABC):
35
36
  async def stop(self) -> None:
36
37
  """Do nothing on stop."""
37
38
  pass
39
+
40
+
41
+ class StreamJsonDisplay(DisplayABC):
42
+ """A display implementation that streams all events as JSON lines."""
43
+
44
+ @override
45
+ async def consume_event(self, event: events.Event) -> None:
46
+ """Stream each event as a JSON line."""
47
+ if isinstance(event, events.EndEvent):
48
+ return
49
+ event_type = type(event).__name__
50
+ json_data = event.model_dump_json()
51
+ # Output format: {"type": "EventName", "data": {...}}
52
+ print(f'{{"type": "{event_type}", "data": {json_data}}}', flush=True)
53
+ sys.stdout.flush()
54
+
55
+ @override
56
+ async def start(self) -> None:
57
+ """Do nothing on start."""
58
+ pass
59
+
60
+ @override
61
+ async def stop(self) -> None:
62
+ """Do nothing on stop."""
63
+ pass