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 +1,56 @@
1
1
  # Terminal utilities
2
+ import os
3
+ from functools import lru_cache
4
+
5
+
6
+ @lru_cache(maxsize=1)
7
+ def supports_osc8_hyperlinks() -> bool:
8
+ """Check if the current terminal supports OSC 8 hyperlinks.
9
+
10
+ Based on known terminal support. Returns False for unknown terminals.
11
+ """
12
+ term_program = os.environ.get("TERM_PROGRAM", "").lower()
13
+ term = os.environ.get("TERM", "").lower()
14
+
15
+ # Known terminals that do NOT support OSC 8
16
+ unsupported = ("warp", "apple_terminal")
17
+ if any(t in term_program for t in unsupported):
18
+ return False
19
+
20
+ # Known terminals that support OSC 8
21
+ supported = (
22
+ "iterm.app",
23
+ "ghostty",
24
+ "wezterm",
25
+ "kitty",
26
+ "alacritty",
27
+ "hyper",
28
+ "contour",
29
+ "vscode",
30
+ )
31
+ if any(t in term_program for t in supported):
32
+ return True
33
+
34
+ # Kitty sets TERM to xterm-kitty
35
+ if "kitty" in term:
36
+ return True
37
+
38
+ # Ghostty sets TERM to xterm-ghostty
39
+ if "ghostty" in term:
40
+ return True
41
+
42
+ # Windows Terminal
43
+ if os.environ.get("WT_SESSION"):
44
+ return True
45
+
46
+ # VTE-based terminals (GNOME Terminal, etc.) version 0.50+
47
+ vte_version = os.environ.get("VTE_VERSION", "")
48
+ if vte_version:
49
+ try:
50
+ if int(vte_version) >= 5000:
51
+ return True
52
+ except ValueError:
53
+ pass
54
+
55
+ # Default to False for unknown terminals
56
+ return False
@@ -182,7 +182,7 @@ def _parse_osc_color_response(data: bytes) -> tuple[int, int, int] | None:
182
182
 
183
183
  try:
184
184
  text = data.decode("ascii", errors="ignore")
185
- except Exception:
185
+ except LookupError: # encoding lookup failure (should not happen with "ascii")
186
186
  return None
187
187
 
188
188
  match = _OSC_BG_REGEX.search(text)
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import os
3
4
  import select
4
5
  import signal
@@ -38,7 +39,7 @@ def start_esc_interrupt_monitor(
38
39
  # Fallback for non-interactive or non-POSIX environments.
39
40
  if not sys.stdin.isatty() or os.name != "posix":
40
41
 
41
- async def _noop() -> None: # type: ignore[return-type]
42
+ async def _noop() -> None:
42
43
  return None
43
44
 
44
45
  return stop_event, asyncio.create_task(_noop())
@@ -47,7 +48,7 @@ def start_esc_interrupt_monitor(
47
48
  try:
48
49
  fd = sys.stdin.fileno()
49
50
  old = termios.tcgetattr(fd)
50
- except Exception as exc: # pragma: no cover - environment dependent
51
+ except OSError as exc: # pragma: no cover - environment dependent
51
52
  log((f"esc monitor init error: {exc}", "r red"))
52
53
  return
53
54
 
@@ -59,7 +60,7 @@ def start_esc_interrupt_monitor(
59
60
  continue
60
61
  try:
61
62
  ch = os.read(fd, 1).decode(errors="ignore")
62
- except Exception:
63
+ except OSError:
63
64
  continue
64
65
  if ch != "\x1b":
65
66
  continue
@@ -70,24 +71,20 @@ def start_esc_interrupt_monitor(
70
71
  while r2:
71
72
  try:
72
73
  seq += os.read(fd, 1).decode(errors="ignore")
73
- except Exception:
74
+ except OSError:
74
75
  break
75
76
  r2, _, _ = select.select([sys.stdin], [], [], 0.0)
76
77
 
77
78
  if seq == "":
78
- try:
79
+ # Best-effort only; failures here should not crash the UI.
80
+ with contextlib.suppress(Exception):
79
81
  asyncio.run_coroutine_threadsafe(on_interrupt(), loop)
80
- except Exception:
81
- # Best-effort only; failures here should not crash the UI.
82
- pass
83
82
  stop.set()
84
83
  except Exception as exc: # pragma: no cover - environment dependent
85
84
  log((f"esc monitor error: {exc}", "r red"))
86
85
  finally:
87
- try:
88
- termios.tcsetattr(fd, termios.TCSADRAIN, old) # type: ignore[name-defined]
89
- except Exception:
90
- pass
86
+ with contextlib.suppress(Exception):
87
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
91
88
 
92
89
  esc_task: asyncio.Task[None] = asyncio.create_task(asyncio.to_thread(_esc_monitor, stop_event))
93
90
  return stop_event, esc_task
@@ -119,29 +116,23 @@ def install_sigint_double_press_exit(
119
116
  now = time.monotonic()
120
117
  if now - last_sigint_time <= window_seconds:
121
118
  # Second press within window: hide progress UI and exit.
122
- try:
119
+ with contextlib.suppress(Exception):
123
120
  hide_progress()
124
- except Exception:
125
- pass
126
121
  raise KeyboardInterrupt
127
122
 
128
123
  # First press: remember timestamp and show toast.
129
124
  last_sigint_time = now
130
- try:
125
+ with contextlib.suppress(Exception):
131
126
  show_toast()
132
- except Exception:
133
- pass
134
127
 
135
128
  try:
136
129
  signal.signal(signal.SIGINT, _handler)
137
- except Exception: # pragma: no cover - platform dependent
130
+ except (OSError, ValueError): # pragma: no cover - platform dependent
138
131
  # If installing the handler fails, restore() will be a no-op.
139
132
  return lambda: None
140
133
 
141
134
  def restore() -> None:
142
- try:
135
+ with contextlib.suppress(Exception):
143
136
  signal.signal(signal.SIGINT, original_handler)
144
- except Exception:
145
- pass
146
137
 
147
138
  return restore
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ import subprocess
4
5
  import sys
5
6
  from dataclasses import dataclass
6
7
  from enum import Enum
@@ -8,6 +9,9 @@ from typing import TextIO, cast
8
9
 
9
10
  from klaude_code.trace import DebugType, log_debug
10
11
 
12
+ # Environment variable for tmux test signal channel
13
+ TMUX_SIGNAL_ENV = "KLAUDE_TEST_SIGNAL"
14
+
11
15
  ST = "\033\\"
12
16
  BEL = "\a"
13
17
 
@@ -39,7 +43,7 @@ class TerminalNotifierConfig:
39
43
  stream: TextIO | None = None
40
44
 
41
45
  @classmethod
42
- def from_env(cls) -> "TerminalNotifierConfig":
46
+ def from_env(cls) -> TerminalNotifierConfig:
43
47
  env = os.getenv("KLAUDE_NOTIFY", "").strip().lower()
44
48
  if env in {"0", "off", "false", "disable", "disabled"}:
45
49
  return cls(enabled=False)
@@ -95,9 +99,7 @@ class TerminalNotifier:
95
99
  if not getattr(stream, "isatty", lambda: False)():
96
100
  return False
97
101
  term = os.getenv("TERM", "")
98
- if term.lower() in {"", "dumb"}:
99
- return False
100
- return True
102
+ return term.lower() not in {"", "dumb"}
101
103
 
102
104
 
103
105
  def _compact(text: str, limit: int = 160) -> str:
@@ -105,3 +107,41 @@ def _compact(text: str, limit: int = 160) -> str:
105
107
  if len(squashed) > limit:
106
108
  return squashed[: limit - 3] + "…"
107
109
  return squashed
110
+
111
+
112
+ def emit_tmux_signal(channel: str | None = None) -> bool:
113
+ """Send a tmux wait-for signal when a task completes.
114
+
115
+ This enables synchronous testing by allowing external scripts to block
116
+ until a task finishes, eliminating the need for polling or sleep.
117
+
118
+ Usage:
119
+ KLAUDE_TEST_SIGNAL=done klaude # In tmux session
120
+ tmux wait-for done # Blocks until task completes
121
+
122
+ Args:
123
+ channel: Signal channel name. If None, reads from KLAUDE_TEST_SIGNAL env var.
124
+
125
+ Returns:
126
+ True if signal was sent successfully, False otherwise.
127
+ """
128
+ channel = channel or os.getenv(TMUX_SIGNAL_ENV)
129
+ if not channel:
130
+ return False
131
+
132
+ # Check if we're in a tmux session
133
+ if not os.getenv("TMUX"):
134
+ log_debug("tmux signal skipped: not in tmux session", debug_type=DebugType.TERMINAL)
135
+ return False
136
+
137
+ try:
138
+ subprocess.run(
139
+ ["tmux", "wait-for", "-S", channel],
140
+ check=True,
141
+ capture_output=True,
142
+ )
143
+ log_debug(f"tmux signal sent: {channel}", debug_type=DebugType.TERMINAL)
144
+ return True
145
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
146
+ log_debug(f"tmux signal failed: {e}", debug_type=DebugType.TERMINAL)
147
+ return False