klaude-code 1.2.13__py3-none-any.whl → 1.2.14__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.
@@ -0,0 +1,73 @@
1
+ """Authentication commands for CLI."""
2
+
3
+ import datetime
4
+
5
+ import typer
6
+
7
+ from klaude_code.trace import log
8
+
9
+
10
+ def login_command(
11
+ provider: str = typer.Argument("codex", help="Provider to login (codex)"),
12
+ ) -> None:
13
+ """Login to a provider using OAuth."""
14
+ if provider.lower() != "codex":
15
+ log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
16
+ raise typer.Exit(1)
17
+
18
+ from klaude_code.auth.codex.oauth import CodexOAuth
19
+ from klaude_code.auth.codex.token_manager import CodexTokenManager
20
+
21
+ token_manager = CodexTokenManager()
22
+
23
+ # Check if already logged in
24
+ if token_manager.is_logged_in():
25
+ state = token_manager.get_state()
26
+ if state and not state.is_expired():
27
+ log(("You are already logged in to Codex.", "green"))
28
+ log(f" Account ID: {state.account_id[:8]}...")
29
+ expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
30
+ log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
31
+ if not typer.confirm("Do you want to re-login?"):
32
+ return
33
+
34
+ log("Starting Codex OAuth login flow...")
35
+ log("A browser window will open for authentication.")
36
+
37
+ try:
38
+ oauth = CodexOAuth(token_manager)
39
+ state = oauth.login()
40
+ log(("Login successful!", "green"))
41
+ log(f" Account ID: {state.account_id[:8]}...")
42
+ expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
43
+ log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
44
+ except Exception as e:
45
+ log((f"Login failed: {e}", "red"))
46
+ raise typer.Exit(1) from None
47
+
48
+
49
+ def logout_command(
50
+ provider: str = typer.Argument("codex", help="Provider to logout (codex)"),
51
+ ) -> None:
52
+ """Logout from a provider."""
53
+ if provider.lower() != "codex":
54
+ log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
55
+ raise typer.Exit(1)
56
+
57
+ from klaude_code.auth.codex.token_manager import CodexTokenManager
58
+
59
+ token_manager = CodexTokenManager()
60
+
61
+ if not token_manager.is_logged_in():
62
+ log("You are not logged in to Codex.")
63
+ return
64
+
65
+ if typer.confirm("Are you sure you want to logout from Codex?"):
66
+ token_manager.delete()
67
+ log(("Logged out from Codex.", "green"))
68
+
69
+
70
+ def register_auth_commands(app: typer.Typer) -> None:
71
+ """Register auth commands to the given Typer app."""
72
+ app.command("login")(login_command)
73
+ app.command("logout")(logout_command)
@@ -0,0 +1,88 @@
1
+ """Configuration commands for CLI."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+
7
+ import typer
8
+
9
+ from klaude_code.config import config_path, load_config
10
+ from klaude_code.trace import log
11
+
12
+
13
+ def list_models() -> None:
14
+ """List all models and providers configuration"""
15
+ from klaude_code.config.list_model import display_models_and_providers
16
+ from klaude_code.ui.terminal.color import is_light_terminal_background
17
+
18
+ config = load_config()
19
+ if config is None:
20
+ raise typer.Exit(1)
21
+
22
+ # Auto-detect theme when not explicitly set in config, to match other CLI entrypoints.
23
+ if config.theme is None:
24
+ detected = is_light_terminal_background()
25
+ if detected is True:
26
+ config.theme = "light"
27
+ elif detected is False:
28
+ config.theme = "dark"
29
+
30
+ display_models_and_providers(config)
31
+
32
+
33
+ def edit_config() -> None:
34
+ """Open the configuration file in $EDITOR or default system editor"""
35
+ editor = os.environ.get("EDITOR")
36
+
37
+ # If no EDITOR is set, prioritize TextEdit on macOS
38
+ if not editor:
39
+ # Try common editors in order of preference on other platforms
40
+ for cmd in [
41
+ "code",
42
+ "nvim",
43
+ "vim",
44
+ "nano",
45
+ ]:
46
+ try:
47
+ subprocess.run(["which", cmd], check=True, capture_output=True)
48
+ editor = cmd
49
+ break
50
+ except (subprocess.CalledProcessError, FileNotFoundError):
51
+ continue
52
+
53
+ # If no editor found, try platform-specific defaults
54
+ if not editor:
55
+ if sys.platform == "darwin": # macOS
56
+ editor = "open"
57
+ elif sys.platform == "win32": # Windows
58
+ editor = "notepad"
59
+ else: # Linux and other Unix systems
60
+ editor = "xdg-open"
61
+
62
+ # Ensure config file exists
63
+ config = load_config()
64
+ if config is None:
65
+ raise typer.Exit(1)
66
+
67
+ try:
68
+ if editor == "open -a TextEdit":
69
+ subprocess.run(["open", "-a", "TextEdit", str(config_path)], check=True)
70
+ elif editor in ["open", "xdg-open"]:
71
+ # For open/xdg-open, we need to pass the file directly
72
+ subprocess.run([editor, str(config_path)], check=True)
73
+ else:
74
+ subprocess.run([editor, str(config_path)], check=True)
75
+ except subprocess.CalledProcessError as e:
76
+ log((f"Error: Failed to open editor: {e}", "red"))
77
+ raise typer.Exit(1) from None
78
+ except FileNotFoundError:
79
+ log((f"Error: Editor '{editor}' not found", "red"))
80
+ log("Please install a text editor or set your $EDITOR environment variable")
81
+ raise typer.Exit(1) from None
82
+
83
+
84
+ def register_config_commands(app: typer.Typer) -> None:
85
+ """Register config commands to the given Typer app."""
86
+ app.command("list")(list_models)
87
+ app.command("config")(edit_config)
88
+ app.command("conf", hidden=True)(edit_config)
@@ -0,0 +1,72 @@
1
+ """Debug utilities for CLI."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import typer
9
+
10
+ from klaude_code.trace import DebugType, log
11
+
12
+ DEBUG_FILTER_HELP = "Comma-separated debug types: " + ", ".join(dt.value for dt in DebugType)
13
+
14
+
15
+ def parse_debug_filters(raw: str | None) -> set[DebugType] | None:
16
+ """Parse comma-separated debug filter string into a set of DebugType."""
17
+ if raw is None:
18
+ return None
19
+ filters: set[DebugType] = set()
20
+ for chunk in raw.split(","):
21
+ normalized = chunk.strip().lower().replace("-", "_")
22
+ if not normalized:
23
+ continue
24
+ try:
25
+ filters.add(DebugType(normalized))
26
+ except ValueError: # pragma: no cover - user input validation
27
+ valid_options = ", ".join(dt.value for dt in DebugType)
28
+ log(
29
+ (
30
+ f"Invalid debug filter '{normalized}'. Valid options: {valid_options}",
31
+ "red",
32
+ )
33
+ )
34
+ raise typer.Exit(2) from None
35
+ return filters or None
36
+
37
+
38
+ def resolve_debug_settings(flag: bool, raw_filters: str | None) -> tuple[bool, set[DebugType] | None]:
39
+ """Resolve debug flag and filters into effective settings."""
40
+ filters = parse_debug_filters(raw_filters)
41
+ effective_flag = flag or (filters is not None)
42
+ return effective_flag, filters
43
+
44
+
45
+ def open_log_file_in_editor(path: Path) -> None:
46
+ """Open the given log file in a text editor without blocking the CLI."""
47
+
48
+ editor = os.environ.get("EDITOR")
49
+
50
+ if not editor:
51
+ for cmd in ["open", "xdg-open", "code", "nvim", "vim", "nano"]:
52
+ try:
53
+ subprocess.run(["which", cmd], check=True, capture_output=True)
54
+ editor = cmd
55
+ break
56
+ except (subprocess.CalledProcessError, FileNotFoundError):
57
+ continue
58
+
59
+ if not editor:
60
+ if sys.platform == "darwin":
61
+ editor = "open"
62
+ elif sys.platform == "win32":
63
+ editor = "notepad"
64
+ else:
65
+ editor = "xdg-open"
66
+
67
+ try:
68
+ subprocess.Popen([editor, str(path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
69
+ except FileNotFoundError:
70
+ log((f"Error: Editor '{editor}' not found", "red"))
71
+ except Exception as exc: # pragma: no cover - best effort
72
+ log((f"Warning: failed to open log file in editor: {exc}", "yellow"))
klaude_code/cli/main.py CHANGED
@@ -1,19 +1,19 @@
1
1
  import asyncio
2
- import datetime
3
2
  import os
4
- import subprocess
5
3
  import sys
6
4
  from importlib.metadata import PackageNotFoundError
7
5
  from importlib.metadata import version as pkg_version
6
+ from pathlib import Path
8
7
 
9
8
  import typer
10
9
 
11
- from klaude_code.cli.runtime import DEBUG_FILTER_HELP, AppInitConfig, resolve_debug_settings, run_exec, run_interactive
10
+ from klaude_code.cli.auth_cmd import register_auth_commands
11
+ from klaude_code.cli.config_cmd import register_config_commands
12
+ from klaude_code.cli.debug import DEBUG_FILTER_HELP, open_log_file_in_editor, resolve_debug_settings
12
13
  from klaude_code.cli.session_cmd import register_session_commands
13
- from klaude_code.config import config_path, display_models_and_providers, load_config, select_model_from_config
14
+ from klaude_code.config import load_config
14
15
  from klaude_code.session import Session, resume_select_session
15
- from klaude_code.trace import log
16
- from klaude_code.ui.terminal.color import is_light_terminal_background
16
+ from klaude_code.trace import prepare_debug_log_file
17
17
 
18
18
 
19
19
  def set_terminal_title(title: str) -> None:
@@ -42,142 +42,10 @@ app = typer.Typer(
42
42
  no_args_is_help=False,
43
43
  )
44
44
 
45
- session_app = typer.Typer(help="Manage sessions for the current project")
46
- register_session_commands(session_app)
47
- app.add_typer(session_app, name="session")
48
-
49
-
50
- @app.command("login")
51
- def login_command(
52
- provider: str = typer.Argument("codex", help="Provider to login (codex)"),
53
- ) -> None:
54
- """Login to a provider using OAuth."""
55
- if provider.lower() != "codex":
56
- log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
57
- raise typer.Exit(1)
58
-
59
- from klaude_code.auth.codex.oauth import CodexOAuth
60
- from klaude_code.auth.codex.token_manager import CodexTokenManager
61
-
62
- token_manager = CodexTokenManager()
63
-
64
- # Check if already logged in
65
- if token_manager.is_logged_in():
66
- state = token_manager.get_state()
67
- if state and not state.is_expired():
68
- log(("You are already logged in to Codex.", "green"))
69
- log(f" Account ID: {state.account_id[:8]}...")
70
- expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
71
- log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
72
- if not typer.confirm("Do you want to re-login?"):
73
- return
74
-
75
- log("Starting Codex OAuth login flow...")
76
- log("A browser window will open for authentication.")
77
-
78
- try:
79
- oauth = CodexOAuth(token_manager)
80
- state = oauth.login()
81
- log(("Login successful!", "green"))
82
- log(f" Account ID: {state.account_id[:8]}...")
83
- expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
84
- log(f" Expires: {expires_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
85
- except Exception as e:
86
- log((f"Login failed: {e}", "red"))
87
- raise typer.Exit(1) from None
88
-
89
-
90
- @app.command("logout")
91
- def logout_command(
92
- provider: str = typer.Argument("codex", help="Provider to logout (codex)"),
93
- ) -> None:
94
- """Logout from a provider."""
95
- if provider.lower() != "codex":
96
- log((f"Error: Unknown provider '{provider}'. Currently only 'codex' is supported.", "red"))
97
- raise typer.Exit(1)
98
-
99
- from klaude_code.auth.codex.token_manager import CodexTokenManager
100
-
101
- token_manager = CodexTokenManager()
102
-
103
- if not token_manager.is_logged_in():
104
- log("You are not logged in to Codex.")
105
- return
106
-
107
- if typer.confirm("Are you sure you want to logout from Codex?"):
108
- token_manager.delete()
109
- log(("Logged out from Codex.", "green"))
110
-
111
-
112
- @app.command("list")
113
- def list_models() -> None:
114
- """List all models and providers configuration"""
115
- config = load_config()
116
- if config is None:
117
- raise typer.Exit(1)
118
-
119
- # Auto-detect theme when not explicitly set in config, to match other CLI entrypoints.
120
- if config.theme is None:
121
- detected = is_light_terminal_background()
122
- if detected is True:
123
- config.theme = "light"
124
- elif detected is False:
125
- config.theme = "dark"
126
-
127
- display_models_and_providers(config)
128
-
129
-
130
- @app.command("config")
131
- @app.command("conf", hidden=True)
132
- def edit_config() -> None:
133
- """Open the configuration file in $EDITOR or default system editor"""
134
- editor = os.environ.get("EDITOR")
135
-
136
- # If no EDITOR is set, prioritize TextEdit on macOS
137
- if not editor:
138
- # Try common editors in order of preference on other platforms
139
- for cmd in [
140
- "code",
141
- "nvim",
142
- "vim",
143
- "nano",
144
- ]:
145
- try:
146
- subprocess.run(["which", cmd], check=True, capture_output=True)
147
- editor = cmd
148
- break
149
- except (subprocess.CalledProcessError, FileNotFoundError):
150
- continue
151
-
152
- # If no editor found, try platform-specific defaults
153
- if not editor:
154
- if sys.platform == "darwin": # macOS
155
- editor = "open"
156
- elif sys.platform == "win32": # Windows
157
- editor = "notepad"
158
- else: # Linux and other Unix systems
159
- editor = "xdg-open"
160
-
161
- # Ensure config file exists
162
- config = load_config()
163
- if config is None:
164
- raise typer.Exit(1)
165
-
166
- try:
167
- if editor == "open -a TextEdit":
168
- subprocess.run(["open", "-a", "TextEdit", str(config_path)], check=True)
169
- elif editor in ["open", "xdg-open"]:
170
- # For open/xdg-open, we need to pass the file directly
171
- subprocess.run([editor, str(config_path)], check=True)
172
- else:
173
- subprocess.run([editor, str(config_path)], check=True)
174
- except subprocess.CalledProcessError as e:
175
- log((f"Error: Failed to open editor: {e}", "red"))
176
- raise typer.Exit(1) from None
177
- except FileNotFoundError:
178
- log((f"Error: Editor '{editor}' not found", "red"))
179
- log("Please install a text editor or set your $EDITOR environment variable")
180
- raise typer.Exit(1) from None
45
+ # Register subcommands from modules
46
+ register_session_commands(app)
47
+ register_auth_commands(app)
48
+ register_config_commands(app)
181
49
 
182
50
 
183
51
  @app.command("exec")
@@ -222,6 +90,7 @@ def exec_command(
222
90
  ),
223
91
  ) -> None:
224
92
  """Execute non-interactively with provided input."""
93
+ from klaude_code.trace import log
225
94
 
226
95
  # Set terminal title with current folder name
227
96
  folder_name = os.path.basename(os.getcwd())
@@ -250,6 +119,9 @@ def exec_command(
250
119
  log(("Error: No input content provided", "red"))
251
120
  raise typer.Exit(1)
252
121
 
122
+ from klaude_code.cli.runtime import AppInitConfig, run_exec
123
+ from klaude_code.config.select_model import select_model_from_config
124
+
253
125
  chosen_model = model
254
126
  if select_model:
255
127
  # Prefer the explicitly provided model as default; otherwise main model
@@ -263,6 +135,10 @@ def exec_command(
263
135
 
264
136
  debug_enabled, debug_filters = resolve_debug_settings(debug, debug_filter)
265
137
 
138
+ log_path: Path | None = None
139
+ if debug_enabled:
140
+ log_path = prepare_debug_log_file()
141
+
266
142
  init_config = AppInitConfig(
267
143
  model=chosen_model,
268
144
  debug=debug_enabled,
@@ -272,6 +148,9 @@ def exec_command(
272
148
  stream_json=stream_json,
273
149
  )
274
150
 
151
+ if log_path:
152
+ open_log_file_in_editor(log_path)
153
+
275
154
  asyncio.run(
276
155
  run_exec(
277
156
  init_config=init_config,
@@ -328,6 +207,9 @@ def main_callback(
328
207
  ) -> None:
329
208
  # Only run interactive mode when no subcommand is invoked
330
209
  if ctx.invoked_subcommand is None:
210
+ from klaude_code.cli.runtime import AppInitConfig, run_interactive
211
+ from klaude_code.config.select_model import select_model_from_config
212
+
331
213
  # Set terminal title with current folder name
332
214
  folder_name = os.path.basename(os.getcwd())
333
215
  set_terminal_title(f"{folder_name}: klaude")
@@ -352,6 +234,10 @@ def main_callback(
352
234
 
353
235
  debug_enabled, debug_filters = resolve_debug_settings(debug, debug_filter)
354
236
 
237
+ log_path: Path | None = None
238
+ if debug_enabled:
239
+ log_path = prepare_debug_log_file()
240
+
355
241
  init_config = AppInitConfig(
356
242
  model=chosen_model,
357
243
  debug=debug_enabled,
@@ -359,6 +245,9 @@ def main_callback(
359
245
  debug_filters=debug_filters,
360
246
  )
361
247
 
248
+ if log_path:
249
+ open_log_file_in_editor(log_path)
250
+
362
251
  asyncio.run(
363
252
  run_interactive(
364
253
  init_config=init_config,
@@ -30,37 +30,6 @@ class PrintCapable(Protocol):
30
30
  def print(self, *objects: Any, style: Any | None = None, end: str = "\n") -> None: ...
31
31
 
32
32
 
33
- DEBUG_FILTER_HELP = "Comma-separated debug types: " + ", ".join(dt.value for dt in DebugType)
34
-
35
-
36
- def _parse_debug_filters(raw: str | None) -> set[DebugType] | None:
37
- if raw is None:
38
- return None
39
- filters: set[DebugType] = set()
40
- for chunk in raw.split(","):
41
- normalized = chunk.strip().lower().replace("-", "_")
42
- if not normalized:
43
- continue
44
- try:
45
- filters.add(DebugType(normalized))
46
- except ValueError: # pragma: no cover - user input validation
47
- valid_options = ", ".join(dt.value for dt in DebugType)
48
- log(
49
- (
50
- f"Invalid debug filter '{normalized}'. Valid options: {valid_options}",
51
- "red",
52
- )
53
- )
54
- raise typer.Exit(2) from None
55
- return filters or None
56
-
57
-
58
- def resolve_debug_settings(flag: bool, raw_filters: str | None) -> tuple[bool, set[DebugType] | None]:
59
- filters = _parse_debug_filters(raw_filters)
60
- effective_flag = flag or (filters is not None)
61
- return effective_flag, filters
62
-
63
-
64
33
  @dataclass
65
34
  class AppInitConfig:
66
35
  """Configuration for initializing the application components."""
@@ -72,7 +72,9 @@ def session_clean_all(
72
72
  log(f"Deleted {deleted} session(s).")
73
73
 
74
74
 
75
- def register_session_commands(session_app: typer.Typer) -> None:
75
+ def register_session_commands(app: typer.Typer) -> None:
76
76
  """Register session subcommands to the given Typer app."""
77
+ session_app = typer.Typer(help="Manage sessions for the current project")
77
78
  session_app.command("clean")(session_clean)
78
79
  session_app.command("clean-all")(session_clean_all)
80
+ app.add_typer(session_app, name="session")
@@ -2,7 +2,7 @@ import asyncio
2
2
  from typing import TYPE_CHECKING
3
3
 
4
4
  from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
5
- from klaude_code.config import select_model_from_config
5
+ from klaude_code.config.select_model import select_model_from_config
6
6
  from klaude_code.protocol import commands, events, model
7
7
 
8
8
  if TYPE_CHECKING:
@@ -1,11 +1,7 @@
1
1
  from .config import Config, config_path, load_config
2
- from .list_model import display_models_and_providers
3
- from .select_model import select_model_from_config
4
2
 
5
3
  __all__ = [
6
4
  "Config",
7
5
  "config_path",
8
- "display_models_and_providers",
9
6
  "load_config",
10
- "select_model_from_config",
11
7
  ]
@@ -71,7 +71,8 @@ class Config(BaseModel):
71
71
 
72
72
  def _save_config() -> None:
73
73
  config_path.parent.mkdir(parents=True, exist_ok=True)
74
- _ = config_path.write_text(yaml.dump(config_dict, default_flow_style=False, sort_keys=False))
74
+ yaml_content = yaml.dump(config_dict, default_flow_style=False, sort_keys=False)
75
+ _ = config_path.write_text(str(yaml_content or ""))
75
76
 
76
77
  await asyncio.to_thread(_save_config)
77
78
 
@@ -142,8 +143,7 @@ def get_example_config() -> Config:
142
143
  )
143
144
 
144
145
 
145
- @lru_cache(maxsize=1)
146
- def load_config() -> Config | None:
146
+ def _load_config_uncached() -> Config | None:
147
147
  if not config_path.exists():
148
148
  log(f"Config file not found: {config_path}")
149
149
  example_config = get_example_config()
@@ -151,7 +151,7 @@ def load_config() -> Config | None:
151
151
  config_dict = example_config.model_dump(mode="json", exclude_none=True)
152
152
 
153
153
  # Comment out all example config lines
154
- yaml_str = yaml.dump(config_dict, default_flow_style=False, sort_keys=False)
154
+ yaml_str = yaml.dump(config_dict, default_flow_style=False, sort_keys=False) or ""
155
155
  commented_yaml = "\n".join(f"# {line}" if line.strip() else "#" for line in yaml_str.splitlines())
156
156
  _ = config_path.write_text(commented_yaml)
157
157
 
@@ -175,3 +175,30 @@ def load_config() -> Config | None:
175
175
  raise ValueError(f"Invalid config file: {config_path}") from e
176
176
 
177
177
  return config
178
+
179
+
180
+ @lru_cache(maxsize=1)
181
+ def _load_config_cached() -> Config | None:
182
+ return _load_config_uncached()
183
+
184
+
185
+ def load_config() -> Config | None:
186
+ """Load config from disk, caching only successful parses.
187
+
188
+ Returns:
189
+ Config object on success, or None when the config is missing/empty/commented out.
190
+ """
191
+
192
+ try:
193
+ config = _load_config_cached()
194
+ except ValueError:
195
+ _load_config_cached.cache_clear()
196
+ raise
197
+
198
+ if config is None:
199
+ _load_config_cached.cache_clear()
200
+ return config
201
+
202
+
203
+ # Expose cache control for tests and callers that need to invalidate the cache.
204
+ load_config.cache_clear = _load_config_cached.cache_clear # type: ignore[attr-defined]
@@ -4,6 +4,8 @@ This module consolidates all magic numbers and configuration values
4
4
  that were previously scattered across the codebase.
5
5
  """
6
6
 
7
+ from pathlib import Path
8
+
7
9
  # =============================================================================
8
10
  # Agent Configuration
9
11
  # =============================================================================
@@ -116,15 +118,18 @@ STATUS_SHIMMER_ALPHA_SCALE = 0.7
116
118
  # Spinner breathing animation
117
119
  # Duration in seconds for one full breathe-in + breathe-out cycle
118
120
  # Keep in sync with STATUS_SHIMMER_SWEEP_SECONDS for visual consistency
119
- SPINNER_BREATH_PERIOD_SECONDS = 2
121
+ SPINNER_BREATH_PERIOD_SECONDS: float = 2.0
120
122
 
121
123
 
122
124
  # =============================================================================
123
125
  # Debug / Logging
124
126
  # =============================================================================
125
127
 
126
- # Default debug log file path
127
- DEFAULT_DEBUG_LOG_FILE = "debug.log"
128
+ # Default debug log directory (user cache)
129
+ DEFAULT_DEBUG_LOG_DIR = Path.home() / ".klaude" / "logs"
130
+
131
+ # Default debug log file path (symlink to latest session)
132
+ DEFAULT_DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_DIR / "debug.log"
128
133
 
129
134
  # Maximum log file size before rotation (10MB)
130
135
  LOG_MAX_BYTES = 10 * 1024 * 1024
klaude_code/core/agent.py CHANGED
@@ -46,7 +46,7 @@ class DefaultModelProfileProvider(ModelProfileProvider):
46
46
  model_name = llm_client.model_name
47
47
  return AgentProfile(
48
48
  llm_client=llm_client,
49
- system_prompt=load_system_prompt(model_name, sub_agent_type),
49
+ system_prompt=load_system_prompt(model_name, llm_client.protocol, sub_agent_type),
50
50
  tools=load_agent_tools(model_name, sub_agent_type),
51
51
  reminders=load_agent_reminders(model_name, sub_agent_type),
52
52
  )