klaude-code 2.8.1__py3-none-any.whl → 2.9.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 (93) hide show
  1. klaude_code/app/runtime.py +2 -1
  2. klaude_code/auth/antigravity/oauth.py +0 -9
  3. klaude_code/auth/antigravity/token_manager.py +0 -18
  4. klaude_code/auth/base.py +53 -0
  5. klaude_code/auth/codex/exceptions.py +0 -4
  6. klaude_code/auth/codex/oauth.py +32 -28
  7. klaude_code/auth/codex/token_manager.py +0 -18
  8. klaude_code/cli/cost_cmd.py +128 -39
  9. klaude_code/cli/list_model.py +27 -10
  10. klaude_code/cli/main.py +14 -3
  11. klaude_code/config/assets/builtin_config.yaml +8 -24
  12. klaude_code/config/config.py +47 -25
  13. klaude_code/config/sub_agent_model_helper.py +18 -13
  14. klaude_code/config/thinking.py +0 -8
  15. klaude_code/const.py +1 -1
  16. klaude_code/core/agent_profile.py +10 -52
  17. klaude_code/core/compaction/overflow.py +0 -4
  18. klaude_code/core/executor.py +33 -5
  19. klaude_code/core/manager/llm_clients.py +9 -1
  20. klaude_code/core/prompts/prompt-claude-code.md +4 -4
  21. klaude_code/core/reminders.py +21 -23
  22. klaude_code/core/task.py +0 -4
  23. klaude_code/core/tool/__init__.py +3 -2
  24. klaude_code/core/tool/file/apply_patch.py +0 -27
  25. klaude_code/core/tool/file/read_tool.md +3 -2
  26. klaude_code/core/tool/file/read_tool.py +15 -2
  27. klaude_code/core/tool/offload.py +0 -35
  28. klaude_code/core/tool/sub_agent/__init__.py +6 -0
  29. klaude_code/core/tool/sub_agent/image_gen.md +16 -0
  30. klaude_code/core/tool/sub_agent/image_gen.py +146 -0
  31. klaude_code/core/tool/sub_agent/task.md +20 -0
  32. klaude_code/core/tool/sub_agent/task.py +205 -0
  33. klaude_code/core/tool/tool_registry.py +0 -16
  34. klaude_code/core/turn.py +1 -1
  35. klaude_code/llm/anthropic/input.py +6 -5
  36. klaude_code/llm/antigravity/input.py +14 -7
  37. klaude_code/llm/codex/client.py +22 -0
  38. klaude_code/llm/codex/prompt_sync.py +237 -0
  39. klaude_code/llm/google/client.py +8 -6
  40. klaude_code/llm/google/input.py +20 -12
  41. klaude_code/llm/image.py +18 -11
  42. klaude_code/llm/input_common.py +14 -6
  43. klaude_code/llm/json_stable.py +37 -0
  44. klaude_code/llm/openai_compatible/input.py +0 -10
  45. klaude_code/llm/openai_compatible/stream.py +16 -1
  46. klaude_code/llm/registry.py +0 -5
  47. klaude_code/llm/responses/input.py +15 -5
  48. klaude_code/llm/usage.py +0 -8
  49. klaude_code/protocol/events.py +2 -1
  50. klaude_code/protocol/message.py +2 -2
  51. klaude_code/protocol/model.py +20 -1
  52. klaude_code/protocol/op.py +13 -0
  53. klaude_code/protocol/op_handler.py +5 -0
  54. klaude_code/protocol/sub_agent/AGENTS.md +5 -5
  55. klaude_code/protocol/sub_agent/__init__.py +13 -34
  56. klaude_code/protocol/sub_agent/explore.py +7 -34
  57. klaude_code/protocol/sub_agent/image_gen.py +3 -74
  58. klaude_code/protocol/sub_agent/task.py +3 -47
  59. klaude_code/protocol/sub_agent/web.py +8 -52
  60. klaude_code/protocol/tools.py +2 -0
  61. klaude_code/session/session.py +58 -21
  62. klaude_code/session/store.py +0 -4
  63. klaude_code/skill/assets/deslop/SKILL.md +9 -0
  64. klaude_code/skill/system_skills.py +0 -20
  65. klaude_code/tui/command/fork_session_cmd.py +5 -2
  66. klaude_code/tui/command/resume_cmd.py +9 -2
  67. klaude_code/tui/command/sub_agent_model_cmd.py +85 -18
  68. klaude_code/tui/components/assistant.py +0 -26
  69. klaude_code/tui/components/command_output.py +3 -1
  70. klaude_code/tui/components/developer.py +3 -0
  71. klaude_code/tui/components/diffs.py +2 -208
  72. klaude_code/tui/components/errors.py +4 -0
  73. klaude_code/tui/components/mermaid_viewer.py +2 -2
  74. klaude_code/tui/components/rich/markdown.py +0 -54
  75. klaude_code/tui/components/rich/theme.py +2 -0
  76. klaude_code/tui/components/sub_agent.py +2 -46
  77. klaude_code/tui/components/thinking.py +0 -33
  78. klaude_code/tui/components/tools.py +43 -21
  79. klaude_code/tui/input/images.py +21 -18
  80. klaude_code/tui/input/key_bindings.py +2 -2
  81. klaude_code/tui/input/prompt_toolkit.py +49 -49
  82. klaude_code/tui/machine.py +15 -11
  83. klaude_code/tui/renderer.py +11 -20
  84. klaude_code/tui/runner.py +2 -1
  85. klaude_code/tui/terminal/image.py +6 -34
  86. klaude_code/ui/common.py +0 -70
  87. {klaude_code-2.8.1.dist-info → klaude_code-2.9.0.dist-info}/METADATA +3 -6
  88. {klaude_code-2.8.1.dist-info → klaude_code-2.9.0.dist-info}/RECORD +90 -86
  89. klaude_code/core/tool/sub_agent_tool.py +0 -126
  90. klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -108
  91. klaude_code/tui/components/rich/searchable_text.py +0 -68
  92. {klaude_code-2.8.1.dist-info → klaude_code-2.9.0.dist-info}/WHEEL +0 -0
  93. {klaude_code-2.8.1.dist-info → klaude_code-2.9.0.dist-info}/entry_points.txt +0 -0
@@ -49,7 +49,7 @@ from klaude_code.tui.commands import (
49
49
  from klaude_code.tui.components.rich import status as r_status
50
50
  from klaude_code.tui.components.rich.theme import ThemeKey
51
51
  from klaude_code.tui.components.thinking import extract_last_bold_header, normalize_thinking_content
52
- from klaude_code.tui.components.tools import get_tool_active_form, is_sub_agent_tool
52
+ from klaude_code.tui.components.tools import get_task_active_form, get_tool_active_form, is_sub_agent_tool
53
53
 
54
54
  # Tools that complete quickly and don't benefit from streaming activity display.
55
55
  # For models without fine-grained tool JSON streaming (e.g., Gemini), showing these
@@ -97,10 +97,6 @@ class ActivityState:
97
97
  self._sub_agent_tool_calls: dict[str, int] = {}
98
98
  self._sub_agent_tool_calls_by_id: dict[str, str] = {}
99
99
 
100
- @property
101
- def is_composing(self) -> bool:
102
- return self._composing and not self._tool_calls and not self._sub_agent_tool_calls
103
-
104
100
  def set_composing(self, composing: bool) -> None:
105
101
  self._composing = composing
106
102
  if not composing:
@@ -321,7 +317,7 @@ class _SessionState:
321
317
 
322
318
  @property
323
319
  def should_show_sub_agent_thinking_header(self) -> bool:
324
- return bool(self.sub_agent_state and self.sub_agent_state.sub_agent_type == "ImageGen")
320
+ return bool(self.sub_agent_state and self.sub_agent_state.sub_agent_type == tools.IMAGE_GEN)
325
321
 
326
322
  @property
327
323
  def should_extract_reasoning_header(self) -> bool:
@@ -607,11 +603,14 @@ class DisplayStateMachine:
607
603
  # Skip activity state for fast tools on non-streaming models (e.g., Gemini)
608
604
  # to avoid flash-and-disappear effect
609
605
  if not is_replay and not s.should_skip_tool_activity(e.tool_name):
610
- tool_active_form = get_tool_active_form(e.tool_name)
611
- if is_sub_agent_tool(e.tool_name):
612
- self._spinner.add_sub_agent_tool_call(e.tool_call_id, tool_active_form)
606
+ if e.tool_name == tools.TASK:
607
+ pass
613
608
  else:
614
- self._spinner.add_tool_call(tool_active_form)
609
+ tool_active_form = get_tool_active_form(e.tool_name)
610
+ if is_sub_agent_tool(e.tool_name):
611
+ self._spinner.add_sub_agent_tool_call(e.tool_call_id, tool_active_form)
612
+ else:
613
+ self._spinner.add_tool_call(tool_active_form)
615
614
 
616
615
  if not is_replay:
617
616
  cmds.extend(self._spinner_update_commands())
@@ -629,12 +628,17 @@ class DisplayStateMachine:
629
628
  primary.thinking_stream_active = False
630
629
  cmds.append(EndThinkingStream(session_id=primary.session_id))
631
630
 
631
+ if not is_replay and e.tool_name == tools.TASK and not s.should_skip_tool_activity(e.tool_name):
632
+ tool_active_form = get_task_active_form(e.arguments)
633
+ self._spinner.add_sub_agent_tool_call(e.tool_call_id, tool_active_form)
634
+ cmds.extend(self._spinner_update_commands())
635
+
632
636
  cmds.append(RenderToolCall(e))
633
637
  return cmds
634
638
 
635
639
  case events.ToolResultEvent() as e:
636
640
  if not is_replay and is_sub_agent_tool(e.tool_name):
637
- self._spinner.finish_sub_agent_tool_call(e.tool_call_id, get_tool_active_form(e.tool_name))
641
+ self._spinner.finish_sub_agent_tool_call(e.tool_call_id)
638
642
  cmds.extend(self._spinner_update_commands())
639
643
 
640
644
  if s.is_sub_agent and not e.is_error:
@@ -184,13 +184,6 @@ class TUICommandRenderer:
184
184
  def is_sub_agent_session(self, session_id: str) -> bool:
185
185
  return session_id in self._sessions and self._sessions[session_id].sub_agent_state is not None
186
186
 
187
- def _should_display_sub_agent_thinking_header(self, session_id: str) -> bool:
188
- # Hardcoded: only show sub-agent thinking headers for ImageGen.
189
- st = self._sessions.get(session_id)
190
- if st is None or st.sub_agent_state is None:
191
- return False
192
- return st.sub_agent_state.sub_agent_type == "ImageGen"
193
-
194
187
  def _advance_sub_agent_color_index(self) -> None:
195
188
  palette_size = len(self.themes.sub_agent_colors)
196
189
  if palette_size == 0:
@@ -417,21 +410,12 @@ class TUICommandRenderer:
417
410
  if image_path is not None:
418
411
  self.display_image(str(image_path))
419
412
 
420
- renderable = c_tools.render_tool_result(e, code_theme=self.themes.code_theme, session_id=e.session_id)
421
- if renderable is not None:
422
- self.print(renderable)
413
+ if not is_sub_agent and isinstance(e.ui_extra, model.ImageUIExtra):
414
+ self.display_image(e.ui_extra.file_path)
423
415
 
424
- def display_thinking(self, content: str) -> None:
425
- renderable = c_thinking.render_thinking(
426
- content,
427
- code_theme=self.themes.code_theme,
428
- style=ThemeKey.THINKING,
429
- )
416
+ renderable = c_tools.render_tool_result(e, code_theme=self.themes.code_theme, session_id=e.session_id)
430
417
  if renderable is not None:
431
- self.console.push_theme(theme=self.themes.thinking_markdown_theme)
432
418
  self.print(renderable)
433
- self.console.pop_theme()
434
- self.print()
435
419
 
436
420
  def display_thinking_header(self, header: str) -> None:
437
421
  stripped = header.strip()
@@ -451,6 +435,13 @@ class TUICommandRenderer:
451
435
  with self.session_print_context(e.session_id):
452
436
  self.print(c_developer.render_developer_message(e))
453
437
 
438
+ # Display images from @ file references and user attachments
439
+ if e.item.ui_extra:
440
+ for ui_item in e.item.ui_extra.items:
441
+ if isinstance(ui_item, (model.AtFileImagesUIItem, model.UserImagesUIItem)):
442
+ for image_path in ui_item.paths:
443
+ self.display_image(image_path)
444
+
454
445
  def display_command_output(self, e: events.CommandOutputEvent) -> None:
455
446
  with self.session_print_context(e.session_id):
456
447
  self.print(c_command_output.render_command_output(e))
@@ -690,7 +681,7 @@ class TUICommandRenderer:
690
681
  case PrintBlankLine():
691
682
  self.print()
692
683
  case PrintRuleLine():
693
- self.console.print(Rule(characters="─", style=ThemeKey.LINES))
684
+ self.console.print(Rule(characters="─", style=ThemeKey.LINES_DIM))
694
685
  case EmitOsc94Error():
695
686
  emit_osc94(OSC94States.ERROR)
696
687
  case EmitTmuxSignal():
klaude_code/tui/runner.py CHANGED
@@ -327,5 +327,6 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
327
327
  if not exit_hint_printed:
328
328
  active_session_id = components.executor.context.current_session_id()
329
329
  if active_session_id and Session.exists(active_session_id):
330
+ short_id = Session.shortest_unique_prefix(active_session_id)
330
331
  log(f"Session ID: {active_session_id}")
331
- log(f"Resume with: klaude --resume {active_session_id}")
332
+ log(f"Resume with: klaude -r {short_id}")
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import base64
4
4
  import shutil
5
- import struct
6
5
  import subprocess
7
6
  import sys
8
7
  import tempfile
@@ -12,8 +11,8 @@ from typing import IO
12
11
  # Kitty graphics protocol chunk size (4096 is the recommended max)
13
12
  _CHUNK_SIZE = 4096
14
13
 
15
- # Max columns for non-wide images
16
- _MAX_COLS = 120
14
+ # Max columns for image display
15
+ _MAX_COLS = 80
17
16
 
18
17
  # Image formats that need conversion to PNG
19
18
  _NEEDS_CONVERSION = {".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff", ".tif"}
@@ -40,26 +39,10 @@ def _convert_to_png(path: Path) -> bytes | None:
40
39
  return None
41
40
 
42
41
 
43
- def _get_png_dimensions(data: bytes) -> tuple[int, int] | None:
44
- """Extract width and height from PNG file header."""
45
- # PNG: 8-byte signature + IHDR chunk (4 len + 4 type + 4 width + 4 height)
46
- if len(data) < 24 or data[:8] != b"\x89PNG\r\n\x1a\n":
47
- return None
48
- width, height = struct.unpack(">II", data[16:24])
49
- return width, height
50
-
51
-
52
42
  def print_kitty_image(file_path: str | Path, *, file: IO[str] | None = None) -> None:
53
43
  """Print an image to the terminal using Kitty graphics protocol.
54
44
 
55
- This intentionally bypasses Rich rendering to avoid interleaving Live refreshes
56
- with raw escape sequences. Image size adapts based on aspect ratio:
57
- - Landscape images: fill terminal width
58
- - Portrait images: limit height to avoid oversized display
59
-
60
- Args:
61
- file_path: Path to the image file (PNG recommended).
62
- file: Output file stream. Defaults to stdout.
45
+ Only specifies column width; Kitty auto-scales height to preserve aspect ratio.
63
46
  """
64
47
  path = Path(file_path) if isinstance(file_path, str) else file_path
65
48
  if not path.exists():
@@ -80,20 +63,9 @@ def print_kitty_image(file_path: str | Path, *, file: IO[str] | None = None) ->
80
63
  out = file or sys.stdout
81
64
 
82
65
  term_size = shutil.get_terminal_size()
83
- dimensions = _get_png_dimensions(data)
84
-
85
- # Determine sizing strategy based on aspect ratio
86
- if dimensions is not None:
87
- img_width, img_height = dimensions
88
- if img_width > 2 * img_height:
89
- # Wide landscape (width > 2x height): fill terminal width
90
- size_param = f"c={term_size.columns}"
91
- else:
92
- # Other images: limit width to 80% of terminal
93
- size_param = f"c={min(_MAX_COLS, term_size.columns * 4 // 5)}"
94
- else:
95
- # Fallback: limit width to 80% of terminal
96
- size_param = f"c={min(_MAX_COLS, term_size.columns * 4 // 5)}"
66
+ # Only specify columns, let Kitty auto-scale height to preserve aspect ratio
67
+ target_cols = min(_MAX_COLS, term_size.columns)
68
+ size_param = f"c={target_cols}"
97
69
  print("", file=out)
98
70
  _write_kitty_graphics(out, encoded, size_param=size_param)
99
71
  print("", file=out)
klaude_code/ui/common.py CHANGED
@@ -1,17 +1,8 @@
1
- import re
2
- import subprocess
3
- from pathlib import Path
4
1
  from typing import TYPE_CHECKING
5
2
 
6
3
  if TYPE_CHECKING:
7
4
  from klaude_code.protocol.llm_param import LLMConfigModelParameter, OpenRouterProviderRouting
8
5
 
9
- LEADING_NEWLINES_REGEX = re.compile(r"^\n{2,}")
10
-
11
-
12
- def remove_leading_newlines(text: str) -> str:
13
- return text.lstrip("\n")
14
-
15
6
 
16
7
  def format_number(tokens: int) -> str:
17
8
  if tokens < 1000:
@@ -33,67 +24,6 @@ def format_number(tokens: int) -> str:
33
24
  return f"{m}M{remaining}k"
34
25
 
35
26
 
36
- def get_current_git_branch(path: Path | None = None) -> str | None:
37
- """Get current git branch name, return None if not in a git repository"""
38
- if path is None:
39
- path = Path.cwd()
40
-
41
- try:
42
- # Check if in git repository
43
- git_dir = subprocess.run(
44
- ["git", "rev-parse", "--git-dir"],
45
- cwd=path,
46
- capture_output=True,
47
- text=True,
48
- timeout=2,
49
- )
50
-
51
- if git_dir.returncode != 0:
52
- return None
53
-
54
- # Get current branch name
55
- result = subprocess.run(
56
- ["git", "branch", "--show-current"],
57
- cwd=path,
58
- capture_output=True,
59
- text=True,
60
- timeout=2,
61
- )
62
-
63
- if result.returncode == 0:
64
- branch = result.stdout.strip()
65
- return branch if branch else None
66
-
67
- # Fallback: get HEAD reference
68
- head_file = subprocess.run(
69
- ["git", "rev-parse", "--abbrev-ref", "HEAD"],
70
- cwd=path,
71
- capture_output=True,
72
- text=True,
73
- timeout=2,
74
- )
75
-
76
- if head_file.returncode == 0:
77
- branch = head_file.stdout.strip()
78
- return branch if branch and branch != "HEAD" else None
79
-
80
- except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
81
- pass
82
-
83
- return None
84
-
85
-
86
- def show_path_with_tilde(path: Path | None = None):
87
- if path is None:
88
- path = Path.cwd()
89
-
90
- try:
91
- relative_path = path.relative_to(Path.home())
92
- return f"~/{relative_path}"
93
- except ValueError:
94
- return str(path)
95
-
96
-
97
27
  def format_model_params(model_params: "LLMConfigModelParameter") -> list[str]:
98
28
  """Format model parameters in a concise style.
99
29
 
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 2.8.1
3
+ Version: 2.9.0
4
4
  Summary: Minimal code agent CLI
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: chardet>=5.2.0
7
7
  Requires-Dist: ddgs>=9.9.3
8
8
  Requires-Dist: diff-match-patch>=20241021
9
+ Requires-Dist: filelock>=3.20.3
9
10
  Requires-Dist: google-genai>=1.56.0
10
11
  Requires-Dist: markdown-it-py>=4.0.0
11
12
  Requires-Dist: openai>=1.102.0
@@ -23,7 +24,7 @@ Description-Content-Type: text/markdown
23
24
  Minimal code agent CLI.
24
25
 
25
26
  ## Features
26
- - **Multi-provider**: Anthropic Message API, OpenAI Responses API, OpenRouter, Claude Max OAuth and ChatGPT Codex OAuth etc.
27
+ - **Multi-provider**: Anthropic Message API, OpenAI Responses API, OpenRouter, ChatGPT Codex OAuth etc.
27
28
  - **Keep reasoning item in context**: Interleaved thinking support
28
29
  - **Model-aware tools**: Claude Code tool set for Opus, `apply_patch` for GPT-5/Codex
29
30
  - **Reminders**: Cooldown-based todo tracking, instruction reinforcement and external file change reminder
@@ -107,7 +108,6 @@ On first run, you'll be prompted to select a model. Your choice is saved as `mai
107
108
  | Provider | Env Variable | Models |
108
109
  |-------------|-----------------------|-------------------------------------------------------------------------------|
109
110
  | anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
110
- | claude | N/A (OAuth) | sonnet@claude, opus@claude (requires Claude Pro/Max subscription) |
111
111
  | openai | `OPENAI_API_KEY` | gpt-5.2 |
112
112
  | openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
113
113
  | deepseek | `DEEPSEEK_API_KEY` | deepseek |
@@ -139,7 +139,6 @@ klaude auth login deepseek # Set DEEPSEEK_API_KEY
139
139
  klaude auth login moonshot # Set MOONSHOT_API_KEY
140
140
 
141
141
  # OAuth login for subscription-based providers
142
- klaude auth login claude # Claude Pro/Max subscription
143
142
  klaude auth login codex # ChatGPT Pro subscription
144
143
  ```
145
144
 
@@ -148,7 +147,6 @@ API keys are stored in `~/.klaude/klaude-auth.json` and used as fallback when en
148
147
  To logout from OAuth providers:
149
148
 
150
149
  ```bash
151
- klaude auth logout claude
152
150
  klaude auth logout codex
153
151
  ```
154
152
 
@@ -201,7 +199,6 @@ provider_list:
201
199
  ##### Supported Protocols
202
200
 
203
201
  - `anthropic` - Anthropic Messages API
204
- - `claude_oauth` - Claude OAuth (for Claude Pro/Max subscribers)
205
202
  - `openai` - OpenAI Chat Completion API
206
203
  - `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
207
204
  - `codex_oauth` - OpenAI Codex CLI (OAuth-based, for ChatGPT Pro subscribers)