klaude-code 2.0.1__py3-none-any.whl → 2.0.2__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 (42) hide show
  1. klaude_code/cli/runtime.py +1 -8
  2. klaude_code/command/debug_cmd.py +1 -1
  3. klaude_code/command/export_online_cmd.py +4 -4
  4. klaude_code/command/fork_session_cmd.py +6 -6
  5. klaude_code/command/help_cmd.py +1 -1
  6. klaude_code/command/model_cmd.py +1 -1
  7. klaude_code/command/registry.py +10 -1
  8. klaude_code/command/release_notes_cmd.py +1 -1
  9. klaude_code/command/resume_cmd.py +2 -2
  10. klaude_code/command/status_cmd.py +2 -2
  11. klaude_code/command/terminal_setup_cmd.py +2 -2
  12. klaude_code/command/thinking_cmd.py +1 -1
  13. klaude_code/const.py +1 -0
  14. klaude_code/core/executor.py +15 -7
  15. klaude_code/core/reminders.py +55 -68
  16. klaude_code/core/tool/__init__.py +0 -2
  17. klaude_code/core/tool/file/edit_tool.py +3 -2
  18. klaude_code/core/tool/tool_registry.py +3 -3
  19. klaude_code/protocol/events.py +1 -0
  20. klaude_code/protocol/message.py +3 -11
  21. klaude_code/protocol/model.py +78 -9
  22. klaude_code/protocol/sub_agent/explore.py +0 -15
  23. klaude_code/protocol/sub_agent/task.py +1 -1
  24. klaude_code/protocol/sub_agent/web.py +1 -17
  25. klaude_code/protocol/tools.py +0 -1
  26. klaude_code/ui/modes/exec/display.py +2 -3
  27. klaude_code/ui/modes/repl/display.py +1 -1
  28. klaude_code/ui/modes/repl/event_handler.py +0 -5
  29. klaude_code/ui/modes/repl/input_prompt_toolkit.py +5 -1
  30. klaude_code/ui/modes/repl/renderer.py +2 -1
  31. klaude_code/ui/renderers/developer.py +113 -97
  32. klaude_code/ui/renderers/metadata.py +28 -15
  33. klaude_code/ui/renderers/tools.py +2 -39
  34. klaude_code/ui/rich/markdown.py +69 -11
  35. klaude_code/ui/rich/theme.py +9 -4
  36. klaude_code/ui/terminal/selector.py +36 -17
  37. {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/METADATA +1 -1
  38. {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/RECORD +40 -42
  39. klaude_code/core/tool/file/move_tool.md +0 -41
  40. klaude_code/core/tool/file/move_tool.py +0 -435
  41. {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/WHEEL +0 -0
  42. {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/entry_points.txt +0 -0
@@ -12,8 +12,7 @@ from typing import Annotated, Literal
12
12
  from pydantic import BaseModel, Field, field_validator
13
13
 
14
14
  from klaude_code.protocol.model import (
15
- AtPatternParseResult,
16
- CommandOutput,
15
+ DeveloperUIExtra,
17
16
  StopReason,
18
17
  TaskMetadata,
19
18
  TaskMetadataItem,
@@ -137,15 +136,8 @@ class DeveloperMessage(MessageBase):
137
136
  role: Literal["developer"] = "developer"
138
137
  parts: list[Part]
139
138
 
140
- # Special fields for reminders UI
141
- memory_paths: list[str] | None = None
142
- memory_mentioned: dict[str, list[str]] | None = None # memory_path -> list of @ patterns mentioned in it
143
- external_file_changes: list[str] | None = None
144
- todo_use: bool | None = None
145
- at_files: list[AtPatternParseResult] | None = None
146
- command_output: CommandOutput | None = None
147
- user_image_count: int | None = None
148
- skill_name: str | None = None # Skill name activated via $skill syntax
139
+ # Structured UI-only metadata (never sent to the LLM).
140
+ ui_extra: DeveloperUIExtra | None = None
149
141
 
150
142
 
151
143
  class UserMessage(MessageBase):
@@ -272,21 +272,90 @@ ToolResultUIExtra = Annotated[
272
272
  ]
273
273
 
274
274
 
275
- class AtPatternParseResult(BaseModel):
276
- path: str
277
- tool_name: str
278
- result: str
279
- tool_args: str
280
- operation: Literal["Read", "List"]
281
- mentioned_in: str | None = None # Parent file that referenced this file
282
-
283
-
284
275
  class CommandOutput(BaseModel):
285
276
  command_name: CommandName
286
277
  ui_extra: ToolResultUIExtra | None = None
287
278
  is_error: bool = False
288
279
 
289
280
 
281
+ class MemoryFileLoaded(BaseModel):
282
+ path: str
283
+ mentioned_patterns: list[str] = Field(default_factory=list)
284
+
285
+
286
+ class MemoryLoadedUIItem(BaseModel):
287
+ type: Literal["memory_loaded"] = "memory_loaded"
288
+ files: list[MemoryFileLoaded]
289
+
290
+
291
+ class ExternalFileChangesUIItem(BaseModel):
292
+ type: Literal["external_file_changes"] = "external_file_changes"
293
+ paths: list[str]
294
+
295
+
296
+ class TodoReminderUIItem(BaseModel):
297
+ type: Literal["todo_reminder"] = "todo_reminder"
298
+ reason: Literal["empty", "not_used_recently"]
299
+
300
+
301
+ class AtFileOp(BaseModel):
302
+ operation: Literal["Read", "List"]
303
+ path: str
304
+ mentioned_in: str | None = None
305
+
306
+
307
+ class AtFileOpsUIItem(BaseModel):
308
+ type: Literal["at_file_ops"] = "at_file_ops"
309
+ ops: list[AtFileOp]
310
+
311
+
312
+ class UserImagesUIItem(BaseModel):
313
+ type: Literal["user_images"] = "user_images"
314
+ count: int
315
+
316
+
317
+ class SkillActivatedUIItem(BaseModel):
318
+ type: Literal["skill_activated"] = "skill_activated"
319
+ name: str
320
+
321
+
322
+ class CommandOutputUIItem(BaseModel):
323
+ type: Literal["command_output"] = "command_output"
324
+ output: CommandOutput
325
+
326
+
327
+ type DeveloperUIItem = (
328
+ MemoryLoadedUIItem
329
+ | ExternalFileChangesUIItem
330
+ | TodoReminderUIItem
331
+ | AtFileOpsUIItem
332
+ | UserImagesUIItem
333
+ | SkillActivatedUIItem
334
+ | CommandOutputUIItem
335
+ )
336
+
337
+
338
+ def _empty_developer_ui_items() -> list[DeveloperUIItem]:
339
+ return []
340
+
341
+
342
+ class DeveloperUIExtra(BaseModel):
343
+ items: list[DeveloperUIItem] = Field(default_factory=_empty_developer_ui_items)
344
+
345
+
346
+ def build_command_output_extra(
347
+ command_name: CommandName,
348
+ *,
349
+ ui_extra: ToolResultUIExtra | None = None,
350
+ is_error: bool = False,
351
+ ) -> DeveloperUIExtra:
352
+ return DeveloperUIExtra(
353
+ items=[
354
+ CommandOutputUIItem(output=CommandOutput(command_name=command_name, ui_extra=ui_extra, is_error=is_error))
355
+ ]
356
+ )
357
+
358
+
290
359
  class SubAgentState(BaseModel):
291
360
  sub_agent_type: SubAgentType
292
361
  sub_agent_desc: str
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any
4
-
5
3
  from klaude_code.protocol import tools
6
4
  from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
7
5
 
@@ -37,11 +35,6 @@ EXPLORE_PARAMETERS = {
37
35
  "type": "string",
38
36
  "description": "The task for the agent to perform",
39
37
  },
40
- "thoroughness": {
41
- "type": "string",
42
- "enum": ["quick", "medium", "very thorough"],
43
- "description": "Controls how deep the sub-agent should search the repo",
44
- },
45
38
  "output_format": {
46
39
  "type": "object",
47
40
  "description": "Optional JSON Schema for sub-agent structured output",
@@ -52,13 +45,6 @@ EXPLORE_PARAMETERS = {
52
45
  }
53
46
 
54
47
 
55
- def _explore_prompt_builder(args: dict[str, Any]) -> str:
56
- """Build the Explore prompt from tool arguments."""
57
- prompt = args.get("prompt", "").strip()
58
- thoroughness = args.get("thoroughness", "medium")
59
- return f"{prompt}\nthoroughness: {thoroughness}"
60
-
61
-
62
48
  register_sub_agent(
63
49
  SubAgentProfile(
64
50
  name="Explore",
@@ -66,7 +52,6 @@ register_sub_agent(
66
52
  parameters=EXPLORE_PARAMETERS,
67
53
  prompt_file="prompts/prompt-sub-agent-explore.md",
68
54
  tool_set=(tools.BASH, tools.READ),
69
- prompt_builder=_explore_prompt_builder,
70
55
  active_form="Exploring",
71
56
  output_schema_arg="output_format",
72
57
  )
@@ -64,7 +64,7 @@ register_sub_agent(
64
64
  description=TASK_DESCRIPTION,
65
65
  parameters=TASK_PARAMETERS,
66
66
  prompt_file="prompts/prompt-sub-agent.md",
67
- tool_set=(tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.MOVE),
67
+ tool_set=(tools.BASH, tools.READ, tools.EDIT, tools.WRITE),
68
68
  active_form="Tasking",
69
69
  output_schema_arg="output_format",
70
70
  )
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any
4
-
5
3
  from klaude_code.protocol import tools
6
4
  from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
7
5
 
@@ -21,7 +19,7 @@ Capabilities:
21
19
  How to use:
22
20
  - Write a clear prompt describing what information you need - the agent will search and fetch as needed
23
21
  - Account for "Today's date" in <env>. For example, if <env> says "Today's date: 2025-07-01", and the user wants the latest docs, do not use 2024 in the search query. Use 2025.
24
- - Optionally provide a `url` if you already know the target page
22
+ - Provide the url if you already know the target page
25
23
  - Use `output_format` (JSON Schema) to get structured data back from the agent
26
24
 
27
25
  What you receive:
@@ -48,10 +46,6 @@ WEB_AGENT_PARAMETERS = {
48
46
  "type": "string",
49
47
  "description": "A short (3-5 word) description of the task",
50
48
  },
51
- "url": {
52
- "type": "string",
53
- "description": "The URL to fetch and analyze. If not provided, the agent will search the web first",
54
- },
55
49
  "prompt": {
56
50
  "type": "string",
57
51
  "description": "Instructions for searching, analyzing, or extracting content from the web page",
@@ -66,15 +60,6 @@ WEB_AGENT_PARAMETERS = {
66
60
  }
67
61
 
68
62
 
69
- def _web_agent_prompt_builder(args: dict[str, Any]) -> str:
70
- """Build the WebAgent prompt from tool arguments."""
71
- url = args.get("url", "")
72
- prompt = args.get("prompt", "")
73
- if url:
74
- return f"URL to fetch: {url}\nTask: {prompt}"
75
- return prompt
76
-
77
-
78
63
  register_sub_agent(
79
64
  SubAgentProfile(
80
65
  name="WebAgent",
@@ -82,7 +67,6 @@ register_sub_agent(
82
67
  parameters=WEB_AGENT_PARAMETERS,
83
68
  prompt_file="prompts/prompt-sub-agent-web.md",
84
69
  tool_set=(tools.BASH, tools.READ, tools.WEB_FETCH, tools.WEB_SEARCH, tools.WRITE),
85
- prompt_builder=_web_agent_prompt_builder,
86
70
  active_form="Surfing",
87
71
  output_schema_arg="output_format",
88
72
  )
@@ -1,6 +1,5 @@
1
1
  BASH = "Bash"
2
2
  APPLY_PATCH = "apply_patch"
3
- MOVE = "Move"
4
3
  EDIT = "Edit"
5
4
 
6
5
  READ = "Read"
@@ -14,12 +14,11 @@ class ExecDisplay(DisplayABC):
14
14
  """Only handle TaskFinishEvent."""
15
15
  match event:
16
16
  case events.TaskStartEvent():
17
- emit_osc94(OSC94States.INDETERMINATE)
17
+ pass
18
18
  case events.ErrorEvent() as e:
19
- emit_osc94(OSC94States.HIDDEN)
19
+ emit_osc94(OSC94States.ERROR)
20
20
  print(f"Error: {e.error_message}")
21
21
  case events.TaskFinishEvent() as e:
22
- emit_osc94(OSC94States.HIDDEN)
23
22
  # Print the task result when task finishes
24
23
  if e.task_result.strip():
25
24
  print(e.task_result)
@@ -19,7 +19,7 @@ class REPLDisplay(DisplayABC):
19
19
  - Syntax-highlighted code blocks and diffs
20
20
  - Animated spinners for in-progress operations
21
21
  - Tool call and result visualization
22
- - OSC94 progress bar integration (for supported terminals)
22
+ - OSC94 error indicator (for supported terminals)
23
23
  - Desktop notifications on task completion
24
24
 
25
25
  This is the primary display mode for interactive klaude-code sessions.
@@ -416,14 +416,12 @@ class DisplayEventHandler:
416
416
  self._sub_agent_thinking_headers[event.session_id] = SubAgentThinkingHeaderState()
417
417
  self.renderer.spinner_start()
418
418
  self.renderer.display_task_start(event)
419
- emit_osc94(OSC94States.INDETERMINATE)
420
419
 
421
420
  def _on_developer_message(self, event: events.DeveloperMessageEvent) -> None:
422
421
  self.renderer.display_developer_message(event)
423
422
  self.renderer.display_command_output(event)
424
423
 
425
424
  def _on_turn_start(self, event: events.TurnStartEvent) -> None:
426
- emit_osc94(OSC94States.INDETERMINATE)
427
425
  self.renderer.display_turn_start(event)
428
426
  self.spinner_status.clear_for_new_turn()
429
427
  self.spinner_status.set_reasoning_status(None)
@@ -533,7 +531,6 @@ class DisplayEventHandler:
533
531
  self.renderer.display_task_finish(event)
534
532
  if not self.renderer.is_sub_agent_session(event.session_id):
535
533
  r_status.clear_task_start()
536
- emit_osc94(OSC94States.HIDDEN)
537
534
  self.spinner_status.reset()
538
535
  self.renderer.spinner_stop()
539
536
  self.renderer.console.print(Rule(characters="─", style=ThemeKey.LINES))
@@ -548,7 +545,6 @@ class DisplayEventHandler:
548
545
  self.spinner_status.reset()
549
546
  r_status.clear_task_start()
550
547
  await self.stage_manager.transition_to(Stage.WAITING)
551
- emit_osc94(OSC94States.HIDDEN)
552
548
  self.renderer.display_interrupt()
553
549
 
554
550
  async def _on_error(self, event: events.ErrorEvent) -> None:
@@ -560,7 +556,6 @@ class DisplayEventHandler:
560
556
  self.spinner_status.reset()
561
557
 
562
558
  async def _on_end(self, event: events.EndEvent) -> None:
563
- emit_osc94(OSC94States.HIDDEN)
564
559
  await self.stage_manager.transition_to(Stage.WAITING)
565
560
  self.renderer.spinner_stop()
566
561
  self.spinner_status.reset()
@@ -314,7 +314,7 @@ class PromptToolkitInput(InputProviderABC):
314
314
  "question": "bold",
315
315
  "msg": "",
316
316
  "meta": "fg:ansibrightblack",
317
- "frame.border": "fg:ansibrightblack",
317
+ "frame.border": "fg:ansibrightblack dim",
318
318
  "search_prefix": "fg:ansibrightblack",
319
319
  "search_placeholder": "fg:ansibrightblack italic",
320
320
  "search_input": "",
@@ -604,6 +604,10 @@ class PromptToolkitInput(InputProviderABC):
604
604
  (symbol_style, " ctrl-l "),
605
605
  (text_style, " "),
606
606
  (text_style, "models"),
607
+ (text_style, " "),
608
+ (symbol_style, " ctrl-t "),
609
+ (text_style, " "),
610
+ (text_style, "think"),
607
611
  ]
608
612
  )
609
613
 
@@ -257,7 +257,7 @@ class REPLRenderer:
257
257
  self.print(r_developer.render_developer_message(e))
258
258
 
259
259
  def display_command_output(self, e: events.DeveloperMessageEvent) -> None:
260
- if not e.item.command_output:
260
+ if not r_developer.get_command_output(e.item):
261
261
  return
262
262
  with self.session_print_context(e.session_id):
263
263
  self.print(r_developer.render_command_output(e))
@@ -458,6 +458,7 @@ class REPLRenderer:
458
458
  return
459
459
  with contextlib.suppress(Exception):
460
460
  # Avoid cursor restore when stopping right before prompt_toolkit.
461
+ # This will leave a blank line before prompt input
461
462
  self._bottom_live.transient = False
462
463
  self._bottom_live.stop()
463
464
  self._bottom_live = None
@@ -12,15 +12,19 @@ from klaude_code.ui.rich.theme import ThemeKey
12
12
  REMINDER_BULLET = " ⧉"
13
13
 
14
14
 
15
+ def get_command_output(item: message.DeveloperMessage) -> model.CommandOutput | None:
16
+ if not item.ui_extra:
17
+ return None
18
+ for ui_item in item.ui_extra.items:
19
+ if isinstance(ui_item, model.CommandOutputUIItem):
20
+ return ui_item.output
21
+ return None
22
+
23
+
15
24
  def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
16
- return bool(
17
- e.item.memory_paths
18
- or e.item.external_file_changes
19
- or e.item.todo_use
20
- or e.item.at_files
21
- or e.item.user_image_count
22
- or e.item.skill_name
23
- )
25
+ if not e.item.ui_extra:
26
+ return False
27
+ return any(not isinstance(ui_item, model.CommandOutputUIItem) for ui_item in e.item.ui_extra.items)
24
28
 
25
29
 
26
30
  def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
@@ -31,112 +35,124 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
31
35
  """
32
36
  parts: list[RenderableType] = []
33
37
 
34
- if mp := e.item.memory_paths:
35
- grid = create_grid()
36
- grid.add_row(
37
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
38
- Text.assemble(
39
- ("Load memory ", ThemeKey.REMINDER),
40
- Text(", ", ThemeKey.REMINDER).join(
41
- render_path(memory_path, ThemeKey.REMINDER_BOLD) for memory_path in mp
42
- ),
43
- ),
44
- )
45
- parts.append(grid)
46
-
47
- if fc := e.item.external_file_changes:
48
- grid = create_grid()
49
- for file_path in fc:
50
- grid.add_row(
51
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
52
- Text.assemble(
53
- ("Read ", ThemeKey.REMINDER),
54
- render_path(file_path, ThemeKey.REMINDER_BOLD),
55
- (" after external changes", ThemeKey.REMINDER),
56
- ),
57
- )
58
- parts.append(grid)
59
-
60
- if e.item.todo_use:
61
- grid = create_grid()
62
- grid.add_row(
63
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
64
- Text("Todo hasn't been updated recently", ThemeKey.REMINDER),
65
- )
66
- parts.append(grid)
67
-
68
- if e.item.at_files:
69
- grid = create_grid()
70
- # Group at_files by (operation, mentioned_in)
71
- grouped: dict[tuple[str, str | None], list[str]] = {}
72
- for at_file in e.item.at_files:
73
- key = (at_file.operation, at_file.mentioned_in)
74
- if key not in grouped:
75
- grouped[key] = []
76
- grouped[key].append(at_file.path)
77
-
78
- for (operation, mentioned_in), paths in grouped.items():
79
- path_texts = Text(", ", ThemeKey.REMINDER).join(render_path(p, ThemeKey.REMINDER_BOLD) for p in paths)
80
- if mentioned_in:
81
- grid.add_row(
82
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
83
- Text.assemble(
84
- (f"{operation} ", ThemeKey.REMINDER),
85
- path_texts,
86
- (" mentioned in ", ThemeKey.REMINDER),
87
- render_path(mentioned_in, ThemeKey.REMINDER_BOLD),
88
- ),
89
- )
90
- else:
91
- grid.add_row(
92
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
93
- Text.assemble(
94
- (f"{operation} ", ThemeKey.REMINDER),
95
- path_texts,
96
- ),
97
- )
98
- parts.append(grid)
99
-
100
- if uic := e.item.user_image_count:
101
- grid = create_grid()
102
- grid.add_row(
103
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
104
- Text(f"Attached {uic} image{'s' if uic > 1 else ''}", style=ThemeKey.REMINDER),
105
- )
106
- parts.append(grid)
107
-
108
- if sn := e.item.skill_name:
109
- grid = create_grid()
110
- grid.add_row(
111
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
112
- Text.assemble(
113
- ("Activated skill ", ThemeKey.REMINDER),
114
- (sn, ThemeKey.REMINDER_BOLD),
115
- ),
116
- )
117
- parts.append(grid)
38
+ if e.item.ui_extra:
39
+ for ui_item in e.item.ui_extra.items:
40
+ match ui_item:
41
+ case model.MemoryLoadedUIItem() as item:
42
+ grid = create_grid()
43
+ grid.add_row(
44
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
45
+ Text.assemble(
46
+ ("Load memory ", ThemeKey.REMINDER),
47
+ Text(", ", ThemeKey.REMINDER).join(
48
+ render_path(mem.path, ThemeKey.REMINDER_BOLD) for mem in item.files
49
+ ),
50
+ ),
51
+ )
52
+ parts.append(grid)
53
+ case model.ExternalFileChangesUIItem() as item:
54
+ grid = create_grid()
55
+ for file_path in item.paths:
56
+ grid.add_row(
57
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
58
+ Text.assemble(
59
+ ("Read ", ThemeKey.REMINDER),
60
+ render_path(file_path, ThemeKey.REMINDER_BOLD),
61
+ (" after external changes", ThemeKey.REMINDER),
62
+ ),
63
+ )
64
+ parts.append(grid)
65
+ case model.TodoReminderUIItem() as item:
66
+ match item.reason:
67
+ case "not_used_recently":
68
+ text = "Todo hasn't been updated recently"
69
+ case "empty":
70
+ text = "Todo list is empty"
71
+ case _:
72
+ text = "Todo reminder"
73
+ grid = create_grid()
74
+ grid.add_row(
75
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
76
+ Text(text, ThemeKey.REMINDER),
77
+ )
78
+ parts.append(grid)
79
+ case model.AtFileOpsUIItem() as item:
80
+ grid = create_grid()
81
+ grouped: dict[tuple[str, str | None], list[str]] = {}
82
+ for op in item.ops:
83
+ key = (op.operation, op.mentioned_in)
84
+ grouped.setdefault(key, []).append(op.path)
85
+
86
+ for (operation, mentioned_in), paths in grouped.items():
87
+ path_texts = Text(", ", ThemeKey.REMINDER).join(
88
+ render_path(p, ThemeKey.REMINDER_BOLD) for p in paths
89
+ )
90
+ if mentioned_in:
91
+ grid.add_row(
92
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
93
+ Text.assemble(
94
+ (f"{operation} ", ThemeKey.REMINDER),
95
+ path_texts,
96
+ (" mentioned in ", ThemeKey.REMINDER),
97
+ render_path(mentioned_in, ThemeKey.REMINDER_BOLD),
98
+ ),
99
+ )
100
+ else:
101
+ grid.add_row(
102
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
103
+ Text.assemble(
104
+ (f"{operation} ", ThemeKey.REMINDER),
105
+ path_texts,
106
+ ),
107
+ )
108
+ parts.append(grid)
109
+ case model.UserImagesUIItem() as item:
110
+ grid = create_grid()
111
+ count = item.count
112
+ grid.add_row(
113
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
114
+ Text(
115
+ f"Attached {count} image{'s' if count > 1 else ''}",
116
+ style=ThemeKey.REMINDER,
117
+ ),
118
+ )
119
+ parts.append(grid)
120
+ case model.SkillActivatedUIItem() as item:
121
+ grid = create_grid()
122
+ grid.add_row(
123
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
124
+ Text.assemble(
125
+ ("Activated skill ", ThemeKey.REMINDER),
126
+ (item.name, ThemeKey.REMINDER_BOLD),
127
+ ),
128
+ )
129
+ parts.append(grid)
130
+ case model.CommandOutputUIItem():
131
+ # Rendered via render_command_output
132
+ pass
118
133
 
119
134
  return Group(*parts) if parts else Text("")
120
135
 
121
136
 
122
137
  def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
123
138
  """Render developer command output content."""
124
- if not e.item.command_output:
139
+ command_output = get_command_output(e.item)
140
+ if not command_output:
125
141
  return Text("")
126
142
 
127
143
  content = message.join_text_parts(e.item.parts)
128
- match e.item.command_output.command_name:
144
+ match command_output.command_name:
129
145
  case commands.CommandName.HELP:
130
146
  return Padding.indent(Text.from_markup(content or ""), level=2)
131
147
  case commands.CommandName.STATUS:
132
- return _render_status_output(e.item.command_output)
148
+ return _render_status_output(command_output)
133
149
  case commands.CommandName.RELEASE_NOTES:
134
150
  return Padding.indent(NoInsetMarkdown(content or ""), level=2)
135
151
  case commands.CommandName.FORK_SESSION:
136
- return _render_fork_session_output(e.item.command_output)
152
+ return _render_fork_session_output(command_output)
137
153
  case _:
138
154
  content = content or "(no content)"
139
- style = ThemeKey.TOOL_RESULT if not e.item.command_output.is_error else ThemeKey.ERROR
155
+ style = ThemeKey.TOOL_RESULT if not command_output.is_error else ThemeKey.ERROR
140
156
  return Padding.indent(truncate_middle(content, base_style=style), level=2)
141
157
 
142
158
 
@@ -1,15 +1,14 @@
1
1
  from importlib.metadata import PackageNotFoundError, version
2
2
 
3
- from rich import box
4
3
  from rich.console import Group, RenderableType
5
4
  from rich.padding import Padding
6
- from rich.panel import Panel
7
5
  from rich.text import Text
8
6
 
9
7
  from klaude_code.const import DEFAULT_MAX_TOKENS
10
8
  from klaude_code.protocol import events, model
11
9
  from klaude_code.trace import is_debug_enabled
12
10
  from klaude_code.ui.renderers.common import create_grid
11
+ from klaude_code.ui.rich.quote import Quote
13
12
  from klaude_code.ui.rich.theme import ThemeKey
14
13
  from klaude_code.ui.utils.common import format_model_params, format_number
15
14
 
@@ -199,17 +198,29 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
199
198
 
200
199
 
201
200
  def render_welcome(e: events.WelcomeEvent) -> RenderableType:
202
- """Render the welcome panel with model info and settings."""
201
+ """Render the welcome panel with model info and settings.
202
+
203
+ Args:
204
+ e: The welcome event.
205
+ """
203
206
  debug_mode = is_debug_enabled()
204
207
 
205
- # First line: Klaude Code version
206
- klaude_code_style = ThemeKey.WELCOME_DEBUG_TITLE if debug_mode else ThemeKey.WELCOME_HIGHLIGHT_BOLD
207
- panel_content = Text.assemble(
208
- ("Klaude Code", klaude_code_style),
209
- (f" v{_get_version()}\n", ThemeKey.WELCOME_INFO),
210
- (str(e.llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
211
- (" @ ", ThemeKey.WELCOME_INFO),
212
- (e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
208
+ panel_content = Text()
209
+
210
+ if e.show_klaude_code_info:
211
+ # First line: Klaude Code version
212
+ klaude_code_style = ThemeKey.WELCOME_DEBUG_TITLE if debug_mode else ThemeKey.WELCOME_HIGHLIGHT_BOLD
213
+ panel_content.append_text(Text("Klaude Code", style=klaude_code_style))
214
+ panel_content.append_text(Text(f" v{_get_version()}", style=ThemeKey.WELCOME_INFO))
215
+ panel_content.append_text(Text("\n"))
216
+
217
+ # Model line: model @ provider · params...
218
+ panel_content.append_text(
219
+ Text.assemble(
220
+ (str(e.llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
221
+ (" @ ", ThemeKey.WELCOME_INFO),
222
+ (e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
223
+ )
213
224
  )
214
225
 
215
226
  # Use format_model_params for consistent formatting
@@ -228,7 +239,9 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
228
239
  )
229
240
 
230
241
  border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
231
- return Group(
232
- Panel.fit(panel_content, border_style=border_style, box=box.ROUNDED),
233
- "", # empty line
234
- )
242
+
243
+ if e.show_klaude_code_info:
244
+ groups = ["", Quote(panel_content, style=border_style, prefix="▌ "), ""]
245
+ else:
246
+ groups = [Quote(panel_content, style=border_style, prefix="▌ "), ""]
247
+ return Group(*groups)