klaude-code 1.2.6__py3-none-any.whl → 1.8.0__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 (205) hide show
  1. klaude_code/auth/__init__.py +24 -0
  2. klaude_code/auth/codex/__init__.py +20 -0
  3. klaude_code/auth/codex/exceptions.py +17 -0
  4. klaude_code/auth/codex/jwt_utils.py +45 -0
  5. klaude_code/auth/codex/oauth.py +229 -0
  6. klaude_code/auth/codex/token_manager.py +84 -0
  7. klaude_code/cli/auth_cmd.py +73 -0
  8. klaude_code/cli/config_cmd.py +91 -0
  9. klaude_code/cli/cost_cmd.py +338 -0
  10. klaude_code/cli/debug.py +78 -0
  11. klaude_code/cli/list_model.py +307 -0
  12. klaude_code/cli/main.py +233 -134
  13. klaude_code/cli/runtime.py +309 -117
  14. klaude_code/{version.py → cli/self_update.py} +114 -5
  15. klaude_code/cli/session_cmd.py +37 -21
  16. klaude_code/command/__init__.py +88 -27
  17. klaude_code/command/clear_cmd.py +8 -7
  18. klaude_code/command/command_abc.py +31 -31
  19. klaude_code/command/debug_cmd.py +79 -0
  20. klaude_code/command/export_cmd.py +19 -53
  21. klaude_code/command/export_online_cmd.py +154 -0
  22. klaude_code/command/fork_session_cmd.py +267 -0
  23. klaude_code/command/help_cmd.py +7 -8
  24. klaude_code/command/model_cmd.py +60 -10
  25. klaude_code/command/model_select.py +84 -0
  26. klaude_code/command/prompt-jj-describe.md +32 -0
  27. klaude_code/command/prompt_command.py +19 -11
  28. klaude_code/command/refresh_cmd.py +8 -10
  29. klaude_code/command/registry.py +139 -40
  30. klaude_code/command/release_notes_cmd.py +84 -0
  31. klaude_code/command/resume_cmd.py +111 -0
  32. klaude_code/command/status_cmd.py +104 -60
  33. klaude_code/command/terminal_setup_cmd.py +7 -9
  34. klaude_code/command/thinking_cmd.py +98 -0
  35. klaude_code/config/__init__.py +14 -6
  36. klaude_code/config/assets/__init__.py +1 -0
  37. klaude_code/config/assets/builtin_config.yaml +303 -0
  38. klaude_code/config/builtin_config.py +38 -0
  39. klaude_code/config/config.py +378 -109
  40. klaude_code/config/select_model.py +117 -53
  41. klaude_code/config/thinking.py +269 -0
  42. klaude_code/{const/__init__.py → const.py} +50 -19
  43. klaude_code/core/agent.py +20 -28
  44. klaude_code/core/executor.py +327 -112
  45. klaude_code/core/manager/__init__.py +2 -4
  46. klaude_code/core/manager/llm_clients.py +1 -15
  47. klaude_code/core/manager/llm_clients_builder.py +10 -11
  48. klaude_code/core/manager/sub_agent_manager.py +37 -6
  49. klaude_code/core/prompt.py +63 -44
  50. klaude_code/core/prompts/prompt-claude-code.md +2 -13
  51. klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
  52. klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
  53. klaude_code/core/prompts/prompt-codex.md +9 -42
  54. klaude_code/core/prompts/prompt-minimal.md +12 -0
  55. klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
  56. klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
  57. klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
  58. klaude_code/core/reminders.py +283 -95
  59. klaude_code/core/task.py +113 -75
  60. klaude_code/core/tool/__init__.py +24 -31
  61. klaude_code/core/tool/file/_utils.py +36 -0
  62. klaude_code/core/tool/file/apply_patch.py +17 -25
  63. klaude_code/core/tool/file/apply_patch_tool.py +57 -77
  64. klaude_code/core/tool/file/diff_builder.py +151 -0
  65. klaude_code/core/tool/file/edit_tool.py +50 -63
  66. klaude_code/core/tool/file/move_tool.md +41 -0
  67. klaude_code/core/tool/file/move_tool.py +435 -0
  68. klaude_code/core/tool/file/read_tool.md +1 -1
  69. klaude_code/core/tool/file/read_tool.py +86 -86
  70. klaude_code/core/tool/file/write_tool.py +59 -69
  71. klaude_code/core/tool/report_back_tool.py +84 -0
  72. klaude_code/core/tool/shell/bash_tool.py +265 -22
  73. klaude_code/core/tool/shell/command_safety.py +3 -6
  74. klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
  75. klaude_code/core/tool/sub_agent_tool.py +13 -2
  76. klaude_code/core/tool/todo/todo_write_tool.md +0 -157
  77. klaude_code/core/tool/todo/todo_write_tool.py +1 -1
  78. klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
  79. klaude_code/core/tool/todo/update_plan_tool.py +1 -1
  80. klaude_code/core/tool/tool_abc.py +18 -0
  81. klaude_code/core/tool/tool_context.py +27 -12
  82. klaude_code/core/tool/tool_registry.py +7 -7
  83. klaude_code/core/tool/tool_runner.py +44 -36
  84. klaude_code/core/tool/truncation.py +29 -14
  85. klaude_code/core/tool/web/mermaid_tool.md +43 -0
  86. klaude_code/core/tool/web/mermaid_tool.py +2 -5
  87. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  88. klaude_code/core/tool/web/web_fetch_tool.py +112 -22
  89. klaude_code/core/tool/web/web_search_tool.md +23 -0
  90. klaude_code/core/tool/web/web_search_tool.py +130 -0
  91. klaude_code/core/turn.py +168 -66
  92. klaude_code/llm/__init__.py +2 -10
  93. klaude_code/llm/anthropic/client.py +190 -178
  94. klaude_code/llm/anthropic/input.py +39 -15
  95. klaude_code/llm/bedrock/__init__.py +3 -0
  96. klaude_code/llm/bedrock/client.py +60 -0
  97. klaude_code/llm/client.py +7 -21
  98. klaude_code/llm/codex/__init__.py +5 -0
  99. klaude_code/llm/codex/client.py +149 -0
  100. klaude_code/llm/google/__init__.py +3 -0
  101. klaude_code/llm/google/client.py +309 -0
  102. klaude_code/llm/google/input.py +215 -0
  103. klaude_code/llm/input_common.py +3 -9
  104. klaude_code/llm/openai_compatible/client.py +72 -164
  105. klaude_code/llm/openai_compatible/input.py +6 -4
  106. klaude_code/llm/openai_compatible/stream.py +273 -0
  107. klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
  108. klaude_code/llm/openrouter/client.py +89 -160
  109. klaude_code/llm/openrouter/input.py +18 -30
  110. klaude_code/llm/openrouter/reasoning.py +118 -0
  111. klaude_code/llm/registry.py +39 -7
  112. klaude_code/llm/responses/client.py +184 -171
  113. klaude_code/llm/responses/input.py +20 -1
  114. klaude_code/llm/usage.py +17 -12
  115. klaude_code/protocol/commands.py +17 -1
  116. klaude_code/protocol/events.py +31 -4
  117. klaude_code/protocol/llm_param.py +13 -10
  118. klaude_code/protocol/model.py +232 -29
  119. klaude_code/protocol/op.py +90 -1
  120. klaude_code/protocol/op_handler.py +35 -1
  121. klaude_code/protocol/sub_agent/__init__.py +117 -0
  122. klaude_code/protocol/sub_agent/explore.py +63 -0
  123. klaude_code/protocol/sub_agent/oracle.py +91 -0
  124. klaude_code/protocol/sub_agent/task.py +61 -0
  125. klaude_code/protocol/sub_agent/web.py +79 -0
  126. klaude_code/protocol/tools.py +4 -2
  127. klaude_code/session/__init__.py +2 -2
  128. klaude_code/session/codec.py +71 -0
  129. klaude_code/session/export.py +293 -86
  130. klaude_code/session/selector.py +89 -67
  131. klaude_code/session/session.py +320 -309
  132. klaude_code/session/store.py +220 -0
  133. klaude_code/session/templates/export_session.html +595 -83
  134. klaude_code/session/templates/mermaid_viewer.html +926 -0
  135. klaude_code/skill/__init__.py +27 -0
  136. klaude_code/skill/assets/deslop/SKILL.md +17 -0
  137. klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
  138. klaude_code/skill/assets/handoff/SKILL.md +39 -0
  139. klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
  140. klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
  141. klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
  142. klaude_code/skill/manager.py +70 -0
  143. klaude_code/skill/system_skills.py +192 -0
  144. klaude_code/trace/__init__.py +20 -2
  145. klaude_code/trace/log.py +150 -5
  146. klaude_code/ui/__init__.py +4 -9
  147. klaude_code/ui/core/input.py +1 -1
  148. klaude_code/ui/core/stage_manager.py +7 -7
  149. klaude_code/ui/modes/debug/display.py +2 -1
  150. klaude_code/ui/modes/repl/__init__.py +3 -48
  151. klaude_code/ui/modes/repl/clipboard.py +5 -5
  152. klaude_code/ui/modes/repl/completers.py +487 -123
  153. klaude_code/ui/modes/repl/display.py +5 -4
  154. klaude_code/ui/modes/repl/event_handler.py +370 -117
  155. klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
  156. klaude_code/ui/modes/repl/key_bindings.py +146 -23
  157. klaude_code/ui/modes/repl/renderer.py +189 -99
  158. klaude_code/ui/renderers/assistant.py +9 -2
  159. klaude_code/ui/renderers/bash_syntax.py +178 -0
  160. klaude_code/ui/renderers/common.py +78 -0
  161. klaude_code/ui/renderers/developer.py +104 -48
  162. klaude_code/ui/renderers/diffs.py +87 -6
  163. klaude_code/ui/renderers/errors.py +11 -6
  164. klaude_code/ui/renderers/mermaid_viewer.py +57 -0
  165. klaude_code/ui/renderers/metadata.py +112 -76
  166. klaude_code/ui/renderers/sub_agent.py +92 -7
  167. klaude_code/ui/renderers/thinking.py +40 -18
  168. klaude_code/ui/renderers/tools.py +405 -227
  169. klaude_code/ui/renderers/user_input.py +73 -13
  170. klaude_code/ui/rich/__init__.py +10 -1
  171. klaude_code/ui/rich/cjk_wrap.py +228 -0
  172. klaude_code/ui/rich/code_panel.py +131 -0
  173. klaude_code/ui/rich/live.py +17 -0
  174. klaude_code/ui/rich/markdown.py +305 -170
  175. klaude_code/ui/rich/searchable_text.py +10 -13
  176. klaude_code/ui/rich/status.py +190 -49
  177. klaude_code/ui/rich/theme.py +135 -39
  178. klaude_code/ui/terminal/__init__.py +55 -0
  179. klaude_code/ui/terminal/color.py +1 -1
  180. klaude_code/ui/terminal/control.py +13 -22
  181. klaude_code/ui/terminal/notifier.py +44 -4
  182. klaude_code/ui/terminal/selector.py +658 -0
  183. klaude_code/ui/utils/common.py +0 -18
  184. klaude_code-1.8.0.dist-info/METADATA +377 -0
  185. klaude_code-1.8.0.dist-info/RECORD +219 -0
  186. {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
  187. klaude_code/command/diff_cmd.py +0 -138
  188. klaude_code/command/prompt-dev-docs-update.md +0 -56
  189. klaude_code/command/prompt-dev-docs.md +0 -46
  190. klaude_code/config/list_model.py +0 -162
  191. klaude_code/core/manager/agent_manager.py +0 -127
  192. klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
  193. klaude_code/core/tool/file/multi_edit_tool.md +0 -42
  194. klaude_code/core/tool/file/multi_edit_tool.py +0 -199
  195. klaude_code/core/tool/memory/memory_tool.md +0 -16
  196. klaude_code/core/tool/memory/memory_tool.py +0 -462
  197. klaude_code/llm/openrouter/reasoning_handler.py +0 -209
  198. klaude_code/protocol/sub_agent.py +0 -348
  199. klaude_code/ui/utils/debouncer.py +0 -42
  200. klaude_code-1.2.6.dist-info/METADATA +0 -178
  201. klaude_code-1.2.6.dist-info/RECORD +0 -167
  202. /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
  203. /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
  204. /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
  205. {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
@@ -1,12 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import math
5
+ import random
4
6
  import time
7
+ from collections.abc import Callable
5
8
 
6
9
  import rich.status as rich_status
7
- from rich._spinners import SPINNERS
10
+ from rich.cells import cell_len
8
11
  from rich.color import Color
9
12
  from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
13
+ from rich.measure import Measurement
10
14
  from rich.spinner import Spinner as RichSpinner
11
15
  from rich.style import Style
12
16
  from rich.table import Table
@@ -16,20 +20,21 @@ from klaude_code import const
16
20
  from klaude_code.ui.rich.theme import ThemeKey
17
21
  from klaude_code.ui.terminal.color import get_last_terminal_background_rgb
18
22
 
19
- BREATHING_SPINNER_NAME = "dot"
23
+ # Use an existing Rich spinner name; BreathingSpinner overrides its rendering
24
+ BREATHING_SPINNER_NAME = "dots"
25
+
26
+ # Alternating glyphs for the breathing spinner - switches at each "transparent" point
27
+ _BREATHING_SPINNER_GLYPHS_BASE = [
28
+ "✦",
29
+ ]
30
+
31
+ # Shuffle glyphs on module load for variety across sessions
32
+ BREATHING_SPINNER_GLYPHS = _BREATHING_SPINNER_GLYPHS_BASE.copy()
33
+ random.shuffle(BREATHING_SPINNER_GLYPHS)
20
34
 
21
- SPINNERS.update(
22
- {
23
- BREATHING_SPINNER_NAME: {
24
- "interval": 100,
25
- # Frames content is ignored by the custom breathing spinner implementation,
26
- # but we keep a single-frame list for correct width measurement.
27
- "frames": ["⏺"],
28
- }
29
- }
30
- )
31
35
 
32
36
  _process_start: float | None = None
37
+ _task_start: float | None = None
33
38
 
34
39
 
35
40
  def _elapsed_since_start() -> float:
@@ -41,6 +46,102 @@ def _elapsed_since_start() -> float:
41
46
  return now - _process_start
42
47
 
43
48
 
49
+ def set_task_start(start: float | None = None) -> None:
50
+ """Set the current task start time (perf_counter seconds)."""
51
+
52
+ global _task_start
53
+ _task_start = time.perf_counter() if start is None else start
54
+
55
+
56
+ def clear_task_start() -> None:
57
+ """Clear the current task start time."""
58
+
59
+ global _task_start
60
+ _task_start = None
61
+
62
+
63
+ def _task_elapsed_seconds(now: float | None = None) -> float | None:
64
+ if _task_start is None:
65
+ return None
66
+ current = time.perf_counter() if now is None else now
67
+ return max(0.0, current - _task_start)
68
+
69
+
70
+ def _format_elapsed_compact(seconds: float) -> str:
71
+ total_seconds = max(0, int(seconds))
72
+ if total_seconds < 60:
73
+ return f"{total_seconds}s"
74
+
75
+ minutes, sec = divmod(total_seconds, 60)
76
+ if minutes < 60:
77
+ return f"{minutes}m{sec:02d}s"
78
+
79
+ hours, minute = divmod(minutes, 60)
80
+ return f"{hours}h{minute:02d}m{sec:02d}s"
81
+
82
+
83
+ def current_hint_text(*, min_time_width: int = 0) -> str:
84
+ """Return the full hint string shown on the status line.
85
+
86
+ The hint is the constant suffix shown after the main status text.
87
+
88
+ The elapsed task time is rendered on the right side of the status line
89
+ (near context usage), not inside the hint.
90
+ """
91
+
92
+ # Keep the signature stable; min_time_width is intentionally ignored.
93
+ _ = min_time_width
94
+ return const.STATUS_HINT_TEXT
95
+
96
+
97
+ def current_elapsed_text(*, min_time_width: int = 0) -> str | None:
98
+ """Return the current task elapsed time text (e.g. "11s", "1m02s")."""
99
+
100
+ elapsed = _task_elapsed_seconds()
101
+ if elapsed is None:
102
+ return None
103
+ time_text = _format_elapsed_compact(elapsed)
104
+ if min_time_width > 0:
105
+ time_text = time_text.rjust(min_time_width)
106
+ return time_text
107
+
108
+
109
+ class DynamicText:
110
+ """Renderable that materializes a Text instance at render time.
111
+
112
+ This is useful for status line elements that should refresh without
113
+ requiring explicit spinner_update calls (e.g. elapsed time).
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ factory: Callable[[], Text],
119
+ *,
120
+ min_width_cells: int = 0,
121
+ ) -> None:
122
+ self._factory = factory
123
+ self.min_width_cells = min_width_cells
124
+
125
+ @property
126
+ def plain(self) -> str:
127
+ return self._factory().plain
128
+
129
+ def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
130
+ # Ensure Table/grid layout allocates a stable width for this renderable.
131
+ text = self._factory()
132
+ measured = Measurement.get(console, options, text)
133
+ min_width = max(measured.minimum, self.min_width_cells)
134
+ max_width = max(measured.maximum, self.min_width_cells)
135
+
136
+ limit = getattr(options, "max_width", options.size.width)
137
+ max_width = min(max_width, limit)
138
+ min_width = min(min_width, max_width)
139
+ return Measurement(min_width, max_width)
140
+
141
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
142
+ yield self._factory()
143
+
144
+
44
145
  def _shimmer_profile(main_text: str) -> list[tuple[str, float]]:
45
146
  """Compute per-character shimmer intensity for a horizontal band.
46
147
 
@@ -55,18 +156,12 @@ def _shimmer_profile(main_text: str) -> list[tuple[str, float]]:
55
156
  char_count = len(chars)
56
157
  period = char_count + padding * 2
57
158
 
58
- # Keep a roughly constant shimmer speed (characters per second)
59
- # regardless of text length by deriving a character velocity from a
60
- # baseline text length and the configured sweep duration.
61
- # The baseline is chosen to be close to the default
62
- # "Thinking … (esc to interrupt)" status line.
63
- baseline_chars = 30
64
- base_period = baseline_chars + padding * 2
65
- sweep_seconds = const.STATUS_SHIMMER_SWEEP_SECONDS
66
- char_speed = base_period / sweep_seconds if sweep_seconds > 0 else base_period
159
+ # Use same period as breathing spinner for visual consistency
160
+ sweep_seconds = max(const.SPINNER_BREATH_PERIOD_SECONDS, 0.1)
67
161
 
68
162
  elapsed = _elapsed_since_start()
69
- pos_f = (elapsed * char_speed) % float(period)
163
+ # Complete one full sweep in sweep_seconds, regardless of text length
164
+ pos_f = (elapsed / sweep_seconds % 1.0) * period
70
165
  pos = int(pos_f)
71
166
  band_half_width = const.STATUS_SHIMMER_BAND_HALF_WIDTH
72
167
 
@@ -102,7 +197,6 @@ def _shimmer_style(console: Console, base_style: Style, intensity: float) -> Sty
102
197
 
103
198
  base_r, base_g, base_b = base_triplet
104
199
  bg_r, bg_g, bg_b = bg_triplet
105
-
106
200
  r = int(bg_r * alpha + base_r * (1.0 - alpha))
107
201
  g = int(bg_g * alpha + base_g * (1.0 - alpha))
108
202
  b = int(bg_b * alpha + base_b * (1.0 - alpha))
@@ -125,6 +219,17 @@ def _breathing_intensity() -> float:
125
219
  return 0.5 * (1.0 - math.cos(2.0 * math.pi * phase))
126
220
 
127
221
 
222
+ def _breathing_glyph() -> str:
223
+ """Get the current glyph for the breathing spinner.
224
+
225
+ Alternates between glyphs at each breath cycle (when intensity reaches 0).
226
+ """
227
+ period = max(const.SPINNER_BREATH_PERIOD_SECONDS, 0.1)
228
+ elapsed = _elapsed_since_start()
229
+ cycle = int(elapsed / period)
230
+ return BREATHING_SPINNER_GLYPHS[cycle % len(BREATHING_SPINNER_GLYPHS)]
231
+
232
+
128
233
  def _breathing_style(console: Console, base_style: Style, intensity: float) -> Style:
129
234
  """Blend a base style's foreground color toward terminal background.
130
235
 
@@ -153,33 +258,71 @@ def _breathing_style(console: Console, base_style: Style, intensity: float) -> S
153
258
 
154
259
 
155
260
  class ShimmerStatusText:
156
- """Renderable status line with shimmer effect on the main text and hint."""
261
+ """Renderable status line with shimmer effect on the main text and hint.
262
+
263
+ Supports optional right-aligned text that stays fixed at the right edge.
264
+ """
157
265
 
158
- def __init__(self, main_text: str | Text, main_style: ThemeKey) -> None:
159
- self._main_text = main_text if isinstance(main_text, Text) else Text(main_text)
160
- self._main_style = main_style
161
- self._hint_text = Text(" (esc to interrupt)")
266
+ def __init__(
267
+ self,
268
+ main_text: str | Text,
269
+ right_text: RenderableType | None = None,
270
+ main_style: ThemeKey = ThemeKey.STATUS_TEXT,
271
+ ) -> None:
272
+ if isinstance(main_text, Text):
273
+ text = main_text.copy()
274
+ if not text.style:
275
+ text.style = str(main_style)
276
+ self._main_text = text
277
+ else:
278
+ self._main_text = Text(main_text, style=main_style)
162
279
  self._hint_style = ThemeKey.STATUS_HINT
280
+ self._right_text = right_text
281
+
282
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
283
+ left_text = _StatusLeftText(main=self._main_text, hint_style=self._hint_style)
284
+
285
+ if self._right_text is None:
286
+ yield left_text
287
+ return
288
+
289
+ # Use Table.grid to create left-right aligned layout with a stable gap.
290
+ table = Table.grid(expand=True, padding=(0, 1, 0, 0), collapse_padding=True, pad_edge=False)
291
+ table.add_column(justify="left", ratio=1)
292
+ table.add_column(justify="right")
293
+ table.add_row(left_text, self._right_text)
294
+ yield table
295
+
296
+
297
+ class _StatusLeftText:
298
+ def __init__(self, *, main: Text, hint_style: ThemeKey) -> None:
299
+ self._main = main
300
+ self._hint_style = hint_style
163
301
 
164
302
  def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
165
- result = Text()
166
- main_style = console.get_style(str(self._main_style))
167
- hint_style = console.get_style(str(self._hint_style))
168
-
169
- combined_text = self._main_text.plain + self._hint_text.plain
170
- split_index = len(self._main_text.plain)
171
-
172
- for index, (ch, intensity) in enumerate(_shimmer_profile(combined_text)):
173
- if index < split_index:
174
- # Get style from main_text, merge with main_style
175
- char_style = self._main_text.get_style_at_offset(console, index)
176
- base_style = main_style + char_style
177
- else:
178
- base_style = hint_style
303
+ max_width = getattr(options, "max_width", options.size.width)
304
+
305
+ # Keep the hint visually attached to the status text, while truncating only
306
+ # the main status segment when space is tight.
307
+ hint_text = Text(current_hint_text().strip("\n"), style=console.get_style(str(self._hint_style)))
308
+ hint_cells = cell_len(hint_text.plain)
309
+
310
+ main_text = Text()
311
+ for index, (ch, intensity) in enumerate(_shimmer_profile(self._main.plain)):
312
+ base_style = self._main.get_style_at_offset(console, index)
179
313
  style = _shimmer_style(console, base_style, intensity)
180
- result.append(ch, style=style)
314
+ main_text.append(ch, style=style)
315
+
316
+ # If the hint itself can't fit, fall back to truncating the combined text.
317
+ if max_width <= hint_cells:
318
+ combined = Text.assemble(main_text, hint_text)
319
+ combined.truncate(max(1, max_width), overflow="ellipsis", pad=False)
320
+ yield combined
321
+ return
181
322
 
182
- yield result
323
+ main_budget = max_width - hint_cells
324
+ main_text.truncate(max(1, main_budget), overflow="ellipsis", pad=False)
325
+ yield Text.assemble(main_text, hint_text)
183
326
 
184
327
 
185
328
  def spinner_name() -> str:
@@ -218,7 +361,7 @@ class BreathingSpinner(RichSpinner):
218
361
  intensity = _breathing_intensity()
219
362
  style = _breathing_style(console, base_style, intensity)
220
363
 
221
- glyph = self.frames[0] if self.frames else "⏺"
364
+ glyph = _breathing_glyph()
222
365
  frame = Text(glyph, style=style)
223
366
 
224
367
  if not self.text:
@@ -233,8 +376,6 @@ class BreathingSpinner(RichSpinner):
233
376
 
234
377
  # Monkey-patch Rich's Status module to use the breathing spinner implementation
235
378
  # for the configured spinner name, while preserving default behavior elsewhere.
236
- try:
379
+ # Best-effort patch; if it fails we silently fall back to default spinner.
380
+ with contextlib.suppress(Exception):
237
381
  rich_status.Spinner = BreathingSpinner # type: ignore[assignment]
238
- except Exception:
239
- # Best-effort patch; if it fails we silently fall back to default spinner.
240
- pass
@@ -10,69 +10,115 @@ class Palette:
10
10
  red: str
11
11
  yellow: str
12
12
  green: str
13
+ grey_yellow: str
13
14
  cyan: str
14
15
  blue: str
15
16
  orange: str
16
17
  magenta: str
17
- grey_blue: str
18
18
  grey1: str
19
19
  grey2: str
20
20
  grey3: str
21
21
  grey_green: str
22
22
  purple: str
23
+ lavender: str
23
24
  diff_add: str
25
+ diff_add_char: str
24
26
  diff_remove: str
27
+ diff_remove_char: str
25
28
  code_theme: str
26
- text_background: str
29
+ code_background: str
30
+ green_background: str
31
+ blue_grey_background: str
32
+ # Sub-agent backgrounds (corresponding to sub_agent_colors order)
33
+ cyan_background: str
34
+ green_sub_background: str
35
+ blue_sub_background: str
36
+ purple_background: str
37
+ orange_background: str
38
+ red_background: str
39
+ grey_background: str
40
+ yellow_background: str
27
41
 
28
42
 
29
43
  LIGHT_PALETTE = Palette(
30
44
  red="red",
31
45
  yellow="yellow",
32
- green="spring_green4",
46
+ green="#00875f",
47
+ grey_yellow="#5f9f7a",
33
48
  cyan="cyan",
34
- blue="#3678b7",
49
+ blue="#3078C5",
35
50
  orange="#d77757",
36
51
  magenta="magenta",
37
- grey_blue="steel_blue",
38
52
  grey1="#667e90",
39
53
  grey2="#93a4b1",
40
54
  grey3="#c4ced4",
41
- grey_green="#96a696",
42
- purple="slate_blue3",
43
- diff_add="#2e5a32 on #e8f5e9",
44
- diff_remove="#5a2e32 on #ffebee",
55
+ grey_green="#96a096",
56
+ purple="#5f5fd7",
57
+ lavender="#5f87af",
58
+ diff_add="#2e5a32 on #dafbe1",
59
+ diff_add_char="#2e5a32 on #aceebb",
60
+ diff_remove="#82071e on #ffecec",
61
+ diff_remove_char="#82071e on #ffcfcf",
45
62
  code_theme="ansi_light",
46
- text_background="#f0f0f0",
63
+ code_background="#e0e0e0",
64
+ green_background="#e8f1e9",
65
+ blue_grey_background="#e8e9f1",
66
+ cyan_background="#e0f0f0",
67
+ green_sub_background="#e0f0e0",
68
+ blue_sub_background="#e0e8f5",
69
+ purple_background="#ede0f5",
70
+ orange_background="#f5ebe0",
71
+ red_background="#f5e0e0",
72
+ grey_background="#e8e8e8",
73
+ yellow_background="#f5f5e0",
47
74
  )
48
75
 
49
76
  DARK_PALETTE = Palette(
50
- red="indian_red",
77
+ red="#d75f5f",
51
78
  yellow="yellow",
52
- green="sea_green3",
79
+ green="#5fd787",
80
+ grey_yellow="#8ac89a",
53
81
  cyan="cyan",
54
- blue="deep_sky_blue1",
82
+ blue="#00afff",
55
83
  orange="#e6704e",
56
84
  magenta="magenta",
57
- grey_blue="steel_blue",
58
85
  grey1="#99aabb",
59
86
  grey2="#778899",
60
- grey3="#646464",
87
+ grey3="#545c6c",
61
88
  grey_green="#6d8672",
62
89
  purple="#afbafe",
63
- diff_add="#c8e6c9 on #2e4a32",
64
- diff_remove="#ffcdd2 on #4a2e32",
90
+ lavender="#9898b8",
91
+ diff_add="#c8e6c9 on #1b3928",
92
+ diff_add_char="#c8e6c9 on #2d6b42",
93
+ diff_remove="#ffcdd2 on #3d1f23",
94
+ diff_remove_char="#ffcdd2 on #7a3a42",
65
95
  code_theme="ansi_dark",
66
- text_background="#2f3440",
96
+ code_background="#2f3440",
97
+ green_background="#23342c",
98
+ blue_grey_background="#313848",
99
+ cyan_background="#1a3333",
100
+ green_sub_background="#1b3928",
101
+ blue_sub_background="#1a2a3d",
102
+ purple_background="#2a2640",
103
+ orange_background="#3d2a1a",
104
+ red_background="#3d1f23",
105
+ grey_background="#2a2d30",
106
+ yellow_background="#3d3a1a",
67
107
  )
68
108
 
69
109
 
70
110
  class ThemeKey(str, Enum):
71
111
  LINES = "lines"
112
+
113
+ # PANEL
114
+ SUB_AGENT_RESULT_PANEL = "panel.sub_agent_result"
115
+ WRITE_MARKDOWN_PANEL = "panel.write_markdown"
72
116
  # DIFF
73
117
  DIFF_FILE_NAME = "diff.file_name"
74
118
  DIFF_REMOVE = "diff.remove"
75
119
  DIFF_ADD = "diff.add"
120
+ DIFF_ADD_CHAR = "diff.add.char"
121
+ DIFF_REMOVE_CHAR = "diff.remove.char"
76
122
  DIFF_STATS_ADD = "diff.stats.add"
77
123
  DIFF_STATS_REMOVE = "diff.stats.remove"
78
124
  # ERROR
@@ -84,8 +130,10 @@ class ThemeKey(str, Enum):
84
130
  METADATA_DIM = "metadata.dim"
85
131
  METADATA_BOLD = "metadata.bold"
86
132
  # SPINNER_STATUS
87
- SPINNER_STATUS = "spinner.status"
88
- SPINNER_STATUS_TEXT = "spinner.status.text"
133
+ STATUS_SPINNER = "spinner.status"
134
+ STATUS_TEXT = "spinner.status.text"
135
+ STATUS_TEXT_BOLD = "spinner.status.text.bold"
136
+ STATUS_TEXT_BOLD_ITALIC = "spinner.status.text.bold_italic"
89
137
  # STATUS
90
138
  STATUS_HINT = "status.hint"
91
139
  # USER_INPUT
@@ -93,6 +141,9 @@ class ThemeKey(str, Enum):
93
141
  USER_INPUT_PROMPT = "user.input.prompt"
94
142
  USER_INPUT_AT_PATTERN = "user.at_pattern"
95
143
  USER_INPUT_SLASH_COMMAND = "user.slash_command"
144
+ USER_INPUT_SKILL = "user.skill"
145
+ # ASSISTANT
146
+ ASSISTANT_MESSAGE_MARK = "assistant.message_mark"
96
147
  # REMINDER
97
148
  REMINDER = "reminder"
98
149
  REMINDER_BOLD = "reminder.bold"
@@ -103,10 +154,19 @@ class ThemeKey(str, Enum):
103
154
  TOOL_PARAM = "tool.param"
104
155
  TOOL_PARAM_BOLD = "tool.param.bold"
105
156
  TOOL_RESULT = "tool.result"
157
+ TOOL_RESULT_TRUNCATED = "tool.result.truncated"
106
158
  TOOL_RESULT_BOLD = "tool.result.bold"
107
159
  TOOL_MARK = "tool.mark"
108
160
  TOOL_APPROVED = "tool.approved"
109
161
  TOOL_REJECTED = "tool.rejected"
162
+ TOOL_TIMEOUT = "tool.timeout"
163
+ TOOL_RESULT_MERMAID = "tool.result.mermaid"
164
+ # BASH SYNTAX
165
+ BASH_COMMAND = "bash.command"
166
+ BASH_ARGUMENT = "bash.argument"
167
+ BASH_OPERATOR = "bash.operator"
168
+ BASH_STRING = "bash.string"
169
+ BASH_HEREDOC_DELIMITER = "bash.heredoc.delimiter"
110
170
  # THINKING
111
171
  THINKING = "thinking"
112
172
  THINKING_BOLD = "thinking.bold"
@@ -131,9 +191,11 @@ class ThemeKey(str, Enum):
131
191
  RESUME_FLAG = "resume.flag"
132
192
  RESUME_INFO = "resume.info"
133
193
  # CONFIGURATION DISPLAY
194
+ CONFIG_PROVIDER = "config.provider"
134
195
  CONFIG_TABLE_HEADER = "config.table.header"
135
196
  CONFIG_STATUS_OK = "config.status.ok"
136
197
  CONFIG_STATUS_PRIMARY = "config.status.primary"
198
+ CONFIG_STATUS_ERROR = "config.status.error"
137
199
  CONFIG_ITEM_NAME = "config.item.name"
138
200
  CONFIG_PARAM_LABEL = "config.param.label"
139
201
  CONFIG_PANEL_BORDER = "config.panel.border"
@@ -149,21 +211,24 @@ class Themes:
149
211
  thinking_markdown_theme: Theme
150
212
  code_theme: str
151
213
  sub_agent_colors: list[Style]
214
+ sub_agent_backgrounds: list[Style]
152
215
 
153
216
 
154
217
  def get_theme(theme: str | None = None) -> Themes:
155
- if theme == "light":
156
- palette = LIGHT_PALETTE
157
- else:
158
- palette = DARK_PALETTE
218
+ palette = LIGHT_PALETTE if theme == "light" else DARK_PALETTE
159
219
  return Themes(
160
220
  app_theme=Theme(
161
221
  styles={
162
222
  ThemeKey.LINES.value: palette.grey3,
223
+ # PANEL
224
+ ThemeKey.SUB_AGENT_RESULT_PANEL.value: f"on {palette.blue_grey_background}",
225
+ ThemeKey.WRITE_MARKDOWN_PANEL.value: f"on {palette.green_background}",
163
226
  # DIFF
164
227
  ThemeKey.DIFF_FILE_NAME.value: palette.blue,
165
228
  ThemeKey.DIFF_REMOVE.value: palette.diff_remove,
166
229
  ThemeKey.DIFF_ADD.value: palette.diff_add,
230
+ ThemeKey.DIFF_ADD_CHAR.value: palette.diff_add_char,
231
+ ThemeKey.DIFF_REMOVE_CHAR.value: palette.diff_remove_char,
167
232
  ThemeKey.DIFF_STATS_ADD.value: palette.green,
168
233
  ThemeKey.DIFF_STATS_REMOVE.value: palette.red,
169
234
  # ERROR
@@ -172,17 +237,21 @@ def get_theme(theme: str | None = None) -> Themes:
172
237
  ThemeKey.INTERRUPT.value: "reverse bold " + palette.red,
173
238
  # USER_INPUT
174
239
  ThemeKey.USER_INPUT.value: palette.magenta,
175
- ThemeKey.USER_INPUT_PROMPT.value: palette.magenta,
240
+ ThemeKey.USER_INPUT_PROMPT.value: "bold " + palette.magenta,
176
241
  ThemeKey.USER_INPUT_AT_PATTERN.value: palette.purple,
177
242
  ThemeKey.USER_INPUT_SLASH_COMMAND.value: "bold reverse " + palette.blue,
243
+ ThemeKey.USER_INPUT_SKILL.value: "bold reverse " + palette.green,
244
+ # ASSISTANT
245
+ ThemeKey.ASSISTANT_MESSAGE_MARK.value: "bold",
178
246
  # METADATA
179
- ThemeKey.METADATA.value: palette.grey_blue,
180
- ThemeKey.METADATA_DIM.value: "dim " + palette.grey_blue,
181
- ThemeKey.METADATA_BOLD.value: "bold " + palette.grey_blue,
182
- # SPINNER_STATUS
183
- ThemeKey.SPINNER_STATUS.value: palette.blue,
184
- ThemeKey.SPINNER_STATUS_TEXT.value: palette.blue,
247
+ ThemeKey.METADATA.value: palette.lavender,
248
+ ThemeKey.METADATA_DIM.value: "dim " + palette.lavender,
249
+ ThemeKey.METADATA_BOLD.value: "bold " + palette.lavender,
185
250
  # STATUS
251
+ ThemeKey.STATUS_SPINNER.value: palette.blue,
252
+ ThemeKey.STATUS_TEXT.value: palette.blue,
253
+ ThemeKey.STATUS_TEXT_BOLD.value: "bold " + palette.blue,
254
+ ThemeKey.STATUS_TEXT_BOLD_ITALIC.value: "bold italic " + palette.blue,
186
255
  ThemeKey.STATUS_HINT.value: palette.grey2,
187
256
  # REMINDER
188
257
  ThemeKey.REMINDER.value: palette.grey1,
@@ -195,9 +264,18 @@ def get_theme(theme: str | None = None) -> Themes:
195
264
  ThemeKey.TOOL_PARAM_BOLD.value: "bold " + palette.green,
196
265
  ThemeKey.TOOL_RESULT.value: palette.grey_green,
197
266
  ThemeKey.TOOL_RESULT_BOLD.value: "bold " + palette.grey_green,
267
+ ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.yellow,
198
268
  ThemeKey.TOOL_MARK.value: "bold",
199
269
  ThemeKey.TOOL_APPROVED.value: palette.green + " bold reverse",
200
270
  ThemeKey.TOOL_REJECTED.value: palette.red + " bold reverse",
271
+ ThemeKey.TOOL_TIMEOUT.value: palette.yellow,
272
+ ThemeKey.TOOL_RESULT_MERMAID: palette.blue + " underline",
273
+ # BASH SYNTAX
274
+ ThemeKey.BASH_COMMAND.value: "bold " + palette.green,
275
+ ThemeKey.BASH_ARGUMENT.value: palette.green,
276
+ ThemeKey.BASH_OPERATOR.value: palette.grey2,
277
+ ThemeKey.BASH_STRING.value: palette.grey_yellow,
278
+ ThemeKey.BASH_HEREDOC_DELIMITER.value: "bold " + palette.grey1,
201
279
  # THINKING
202
280
  ThemeKey.THINKING.value: "italic " + palette.grey2,
203
281
  ThemeKey.THINKING_BOLD.value: "bold italic " + palette.grey1,
@@ -222,41 +300,52 @@ def get_theme(theme: str | None = None) -> Themes:
222
300
  ThemeKey.RESUME_FLAG.value: "bold reverse " + palette.green,
223
301
  ThemeKey.RESUME_INFO.value: palette.green,
224
302
  # CONFIGURATION DISPLAY
225
- ThemeKey.CONFIG_TABLE_HEADER.value: palette.green,
303
+ ThemeKey.CONFIG_TABLE_HEADER.value: palette.grey1,
226
304
  ThemeKey.CONFIG_STATUS_OK.value: palette.green,
227
305
  ThemeKey.CONFIG_STATUS_PRIMARY.value: palette.yellow,
306
+ ThemeKey.CONFIG_STATUS_ERROR.value: palette.red,
228
307
  ThemeKey.CONFIG_ITEM_NAME.value: palette.cyan,
229
308
  ThemeKey.CONFIG_PARAM_LABEL.value: palette.grey1,
230
309
  ThemeKey.CONFIG_PANEL_BORDER.value: palette.grey3,
310
+ ThemeKey.CONFIG_PROVIDER.value: palette.cyan + " bold",
231
311
  }
232
312
  ),
233
313
  markdown_theme=Theme(
234
314
  styles={
235
315
  "markdown.code": palette.purple,
236
- "markdown.code.panel": palette.grey3,
316
+ "markdown.code.border": palette.grey3,
237
317
  "markdown.h1": "bold reverse",
238
318
  "markdown.h1.border": palette.grey3,
239
- "markdown.h2.border": palette.grey3,
319
+ "markdown.h2": "bold underline",
240
320
  "markdown.h3": "bold " + palette.grey1,
241
321
  "markdown.h4": "bold " + palette.grey2,
242
322
  "markdown.hr": palette.grey3,
243
323
  "markdown.item.bullet": palette.grey2,
244
324
  "markdown.item.number": palette.grey2,
325
+ "markdown.link": "underline " + palette.blue,
326
+ "markdown.link_url": "underline " + palette.blue,
327
+ "markdown.table.border": palette.grey2,
245
328
  }
246
329
  ),
247
330
  thinking_markdown_theme=Theme(
248
331
  styles={
249
- "markdown.code": palette.grey1 + " on " + palette.text_background,
250
- "markdown.code.panel": palette.grey3,
332
+ # THINKING (used for left-side mark in thinking output)
333
+ ThemeKey.THINKING.value: "italic " + palette.grey2,
334
+ ThemeKey.THINKING_BOLD.value: "bold italic " + palette.grey1,
335
+ "markdown.code": palette.grey1 + " italic on " + palette.code_background,
336
+ "markdown.code.block": palette.grey1,
337
+ "markdown.code.border": palette.grey3,
251
338
  "markdown.h1": "bold reverse",
252
339
  "markdown.h1.border": palette.grey3,
253
- "markdown.h2.border": palette.grey3,
254
340
  "markdown.h3": "bold " + palette.grey1,
255
341
  "markdown.h4": "bold " + palette.grey2,
256
342
  "markdown.hr": palette.grey3,
257
343
  "markdown.item.bullet": palette.grey2,
258
344
  "markdown.item.number": palette.grey2,
345
+ "markdown.link": "underline " + palette.blue,
346
+ "markdown.link_url": "underline " + palette.blue,
259
347
  "markdown.strong": "bold italic " + palette.grey1,
348
+ "markdown.table.border": palette.grey2,
260
349
  }
261
350
  ),
262
351
  code_theme=palette.code_theme,
@@ -266,9 +355,16 @@ def get_theme(theme: str | None = None) -> Themes:
266
355
  Style(color=palette.blue),
267
356
  Style(color=palette.purple),
268
357
  Style(color=palette.orange),
269
- Style(color=palette.grey_blue),
270
- Style(color=palette.red),
271
358
  Style(color=palette.grey1),
272
359
  Style(color=palette.yellow),
273
360
  ],
361
+ sub_agent_backgrounds=[
362
+ Style(bgcolor=palette.cyan_background),
363
+ Style(bgcolor=palette.green_sub_background),
364
+ Style(bgcolor=palette.blue_sub_background),
365
+ Style(bgcolor=palette.purple_background),
366
+ Style(bgcolor=palette.orange_background),
367
+ Style(bgcolor=palette.grey_background),
368
+ Style(bgcolor=palette.yellow_background),
369
+ ],
274
370
  )