klaude-code 1.2.26__py3-none-any.whl → 1.2.27__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.
- klaude_code/cli/config_cmd.py +1 -5
- klaude_code/cli/list_model.py +170 -129
- klaude_code/cli/main.py +37 -5
- klaude_code/cli/runtime.py +4 -6
- klaude_code/cli/self_update.py +2 -1
- klaude_code/cli/session_cmd.py +1 -1
- klaude_code/config/__init__.py +3 -1
- klaude_code/config/assets/__init__.py +1 -0
- klaude_code/config/assets/builtin_config.yaml +233 -0
- klaude_code/config/builtin_config.py +37 -0
- klaude_code/config/config.py +332 -112
- klaude_code/config/select_model.py +45 -8
- klaude_code/core/executor.py +4 -2
- klaude_code/core/manager/llm_clients_builder.py +4 -1
- klaude_code/core/tool/file/edit_tool.py +4 -4
- klaude_code/core/tool/file/write_tool.py +4 -4
- klaude_code/core/tool/shell/bash_tool.py +2 -2
- klaude_code/llm/openai_compatible/stream.py +2 -1
- klaude_code/session/export.py +1 -1
- klaude_code/session/selector.py +2 -2
- klaude_code/session/session.py +4 -4
- klaude_code/ui/modes/repl/completers.py +4 -4
- klaude_code/ui/modes/repl/event_handler.py +1 -1
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +4 -4
- klaude_code/ui/modes/repl/key_bindings.py +4 -4
- klaude_code/ui/renderers/diffs.py +1 -1
- klaude_code/ui/renderers/metadata.py +2 -2
- klaude_code/ui/renderers/tools.py +1 -1
- klaude_code/ui/rich/markdown.py +1 -1
- klaude_code/ui/rich/theme.py +1 -1
- klaude_code/ui/terminal/color.py +1 -1
- klaude_code/ui/terminal/control.py +4 -4
- {klaude_code-1.2.26.dist-info → klaude_code-1.2.27.dist-info}/METADATA +121 -127
- {klaude_code-1.2.26.dist-info → klaude_code-1.2.27.dist-info}/RECORD +36 -33
- {klaude_code-1.2.26.dist-info → klaude_code-1.2.27.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.26.dist-info → klaude_code-1.2.27.dist-info}/entry_points.txt +0 -0
klaude_code/cli/config_cmd.py
CHANGED
|
@@ -16,8 +16,6 @@ def list_models() -> None:
|
|
|
16
16
|
from klaude_code.ui.terminal.color import is_light_terminal_background
|
|
17
17
|
|
|
18
18
|
config = load_config()
|
|
19
|
-
if config is None:
|
|
20
|
-
raise typer.Exit(1)
|
|
21
19
|
|
|
22
20
|
# Auto-detect theme when not explicitly set in config, to match other CLI entrypoints.
|
|
23
21
|
if config.theme is None:
|
|
@@ -60,9 +58,7 @@ def edit_config() -> None:
|
|
|
60
58
|
editor = "xdg-open"
|
|
61
59
|
|
|
62
60
|
# Ensure config file exists
|
|
63
|
-
|
|
64
|
-
if config is None:
|
|
65
|
-
raise typer.Exit(1)
|
|
61
|
+
load_config()
|
|
66
62
|
|
|
67
63
|
try:
|
|
68
64
|
if editor == "open -a TextEdit":
|
klaude_code/cli/list_model.py
CHANGED
|
@@ -6,44 +6,47 @@ from rich.table import Table
|
|
|
6
6
|
from rich.text import Text
|
|
7
7
|
|
|
8
8
|
from klaude_code.config import Config
|
|
9
|
+
from klaude_code.config.config import ModelConfig, ProviderConfig
|
|
10
|
+
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
9
11
|
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
10
12
|
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def
|
|
14
|
-
"""
|
|
15
|
+
def _get_codex_status_elements() -> list[Text]:
|
|
16
|
+
"""Get Codex OAuth login status as Text elements for panel display."""
|
|
15
17
|
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
16
18
|
|
|
19
|
+
elements: list[Text] = []
|
|
17
20
|
token_manager = CodexTokenManager()
|
|
18
21
|
state = token_manager.get_state()
|
|
19
22
|
|
|
20
23
|
if state is None:
|
|
21
|
-
|
|
24
|
+
elements.append(
|
|
22
25
|
Text.assemble(
|
|
23
|
-
("
|
|
26
|
+
("Status: ", "bold"),
|
|
24
27
|
("Not logged in", ThemeKey.CONFIG_STATUS_ERROR),
|
|
25
28
|
(" (run 'klaude login codex' to authenticate)", "dim"),
|
|
26
29
|
)
|
|
27
30
|
)
|
|
28
31
|
elif state.is_expired():
|
|
29
|
-
|
|
32
|
+
elements.append(
|
|
30
33
|
Text.assemble(
|
|
31
|
-
("
|
|
34
|
+
("Status: ", "bold"),
|
|
32
35
|
("Token expired", ThemeKey.CONFIG_STATUS_ERROR),
|
|
33
36
|
(" (run 'klaude login codex' to re-authenticate)", "dim"),
|
|
34
37
|
)
|
|
35
38
|
)
|
|
36
39
|
else:
|
|
37
40
|
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
38
|
-
|
|
41
|
+
elements.append(
|
|
39
42
|
Text.assemble(
|
|
40
|
-
("
|
|
43
|
+
("Status: ", "bold"),
|
|
41
44
|
("Logged in", ThemeKey.CONFIG_STATUS_OK),
|
|
42
45
|
(f" (account: {state.account_id[:8]}..., expires: {expires_dt.strftime('%Y-%m-%d %H:%M UTC')})", "dim"),
|
|
43
46
|
)
|
|
44
47
|
)
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
elements.append(
|
|
47
50
|
Text.assemble(
|
|
48
51
|
("Visit ", "dim"),
|
|
49
52
|
(
|
|
@@ -53,6 +56,7 @@ def _display_codex_status(console: Console) -> None:
|
|
|
53
56
|
(" for up-to-date information on rate limits and credits", "dim"),
|
|
54
57
|
)
|
|
55
58
|
)
|
|
59
|
+
return elements
|
|
56
60
|
|
|
57
61
|
|
|
58
62
|
def mask_api_key(api_key: str | None) -> str:
|
|
@@ -66,136 +70,179 @@ def mask_api_key(api_key: str | None) -> str:
|
|
|
66
70
|
return f"{api_key[:6]} … {api_key[-6:]}"
|
|
67
71
|
|
|
68
72
|
|
|
69
|
-
def
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
def format_api_key_display(provider: ProviderConfig) -> Text:
|
|
74
|
+
"""Format API key display with warning if env var is not set."""
|
|
75
|
+
env_var = provider.get_api_key_env_var()
|
|
76
|
+
resolved_key = provider.get_resolved_api_key()
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Text(
|
|
89
|
-
|
|
78
|
+
if env_var:
|
|
79
|
+
# Using ${ENV_VAR} syntax
|
|
80
|
+
if resolved_key:
|
|
81
|
+
return Text.assemble(
|
|
82
|
+
(f"${{{env_var}}} = ", "dim"),
|
|
83
|
+
(mask_api_key(resolved_key), ""),
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
return Text.assemble(
|
|
87
|
+
(f"${{{env_var}}} ", ""),
|
|
88
|
+
("(not set)", ThemeKey.CONFIG_STATUS_ERROR),
|
|
89
|
+
)
|
|
90
|
+
elif provider.api_key:
|
|
91
|
+
# Plain API key
|
|
92
|
+
return Text(mask_api_key(provider.api_key))
|
|
93
|
+
else:
|
|
94
|
+
return Text("N/A")
|
|
90
95
|
|
|
91
|
-
# Add providers
|
|
92
|
-
for provider in config.provider_list:
|
|
93
|
-
status = Text("✔", style=f"bold {ThemeKey.CONFIG_STATUS_OK}")
|
|
94
|
-
name = Text(provider.provider_name, style=ThemeKey.CONFIG_ITEM_NAME)
|
|
95
|
-
protocol = Text(str(provider.protocol.value), style="")
|
|
96
|
-
base_url = Text(provider.base_url or "N/A", style="")
|
|
97
|
-
api_key = Text(mask_api_key(provider.api_key), style="")
|
|
98
|
-
|
|
99
|
-
providers_table.add_row(status, name, protocol, base_url, api_key)
|
|
100
|
-
|
|
101
|
-
# Display models section
|
|
102
|
-
models_table = Table.grid(padding=(0, 1), expand=True)
|
|
103
|
-
models_table.add_column(width=2, no_wrap=True) # Status
|
|
104
|
-
models_table.add_column(overflow="fold", ratio=1) # Name
|
|
105
|
-
models_table.add_column(overflow="fold", ratio=2) # Model
|
|
106
|
-
models_table.add_column(overflow="fold", ratio=2) # Provider
|
|
107
|
-
models_table.add_column(overflow="fold", ratio=3) # Params
|
|
108
|
-
|
|
109
|
-
# Add header
|
|
110
|
-
models_table.add_row(
|
|
111
|
-
Text("", style="bold"),
|
|
112
|
-
Text("Name", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
113
|
-
Text("Model", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
114
|
-
Text("Provider", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
115
|
-
Text("Params", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
116
|
-
)
|
|
117
96
|
|
|
118
|
-
|
|
119
|
-
for model
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
else ThemeKey.CONFIG_ITEM_NAME,
|
|
129
|
-
)
|
|
130
|
-
model_name = Text(model.model_params.model or "N/A", style="")
|
|
131
|
-
provider = Text(model.provider, style="")
|
|
132
|
-
params: list[Text] = []
|
|
133
|
-
if model.model_params.thinking:
|
|
134
|
-
if model.model_params.thinking.reasoning_effort is not None:
|
|
135
|
-
params.append(
|
|
136
|
-
Text.assemble(
|
|
137
|
-
("reason-effort", ThemeKey.CONFIG_PARAM_LABEL),
|
|
138
|
-
": ",
|
|
139
|
-
model.model_params.thinking.reasoning_effort,
|
|
140
|
-
)
|
|
141
|
-
)
|
|
142
|
-
if model.model_params.thinking.reasoning_summary is not None:
|
|
143
|
-
params.append(
|
|
144
|
-
Text.assemble(
|
|
145
|
-
("reason-summary", ThemeKey.CONFIG_PARAM_LABEL),
|
|
146
|
-
": ",
|
|
147
|
-
model.model_params.thinking.reasoning_summary,
|
|
148
|
-
)
|
|
97
|
+
def _get_model_params_display(model: ModelConfig) -> list[Text]:
|
|
98
|
+
"""Get display elements for model parameters."""
|
|
99
|
+
params: list[Text] = []
|
|
100
|
+
if model.model_params.thinking:
|
|
101
|
+
if model.model_params.thinking.reasoning_effort is not None:
|
|
102
|
+
params.append(
|
|
103
|
+
Text.assemble(
|
|
104
|
+
("reason-effort", ThemeKey.CONFIG_PARAM_LABEL),
|
|
105
|
+
": ",
|
|
106
|
+
model.model_params.thinking.reasoning_effort,
|
|
149
107
|
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
108
|
+
)
|
|
109
|
+
if model.model_params.thinking.reasoning_summary is not None:
|
|
110
|
+
params.append(
|
|
111
|
+
Text.assemble(
|
|
112
|
+
("reason-summary", ThemeKey.CONFIG_PARAM_LABEL),
|
|
113
|
+
": ",
|
|
114
|
+
model.model_params.thinking.reasoning_summary,
|
|
157
115
|
)
|
|
158
|
-
|
|
116
|
+
)
|
|
117
|
+
if model.model_params.thinking.budget_tokens is not None:
|
|
159
118
|
params.append(
|
|
160
119
|
Text.assemble(
|
|
161
|
-
("
|
|
120
|
+
("thinking-budget-tokens", ThemeKey.CONFIG_PARAM_LABEL),
|
|
162
121
|
": ",
|
|
163
|
-
model.model_params.
|
|
122
|
+
str(model.model_params.thinking.budget_tokens),
|
|
164
123
|
)
|
|
165
124
|
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)
|
|
125
|
+
if model.model_params.provider_routing:
|
|
126
|
+
params.append(
|
|
127
|
+
Text.assemble(
|
|
128
|
+
("provider-routing", ThemeKey.CONFIG_PARAM_LABEL),
|
|
129
|
+
": ",
|
|
130
|
+
model.model_params.provider_routing.model_dump_json(exclude_none=True),
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
if len(params) == 0:
|
|
134
|
+
params.append(Text("N/A", style=ThemeKey.CONFIG_PARAM_LABEL))
|
|
135
|
+
return params
|
|
178
136
|
|
|
179
|
-
models_panel = Panel(
|
|
180
|
-
models_table,
|
|
181
|
-
title=Text("Models Configuration", style="white bold"),
|
|
182
|
-
border_style=ThemeKey.CONFIG_PANEL_BORDER,
|
|
183
|
-
padding=(0, 1),
|
|
184
|
-
title_align="left",
|
|
185
|
-
)
|
|
186
137
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
138
|
+
def display_models_and_providers(config: Config):
|
|
139
|
+
"""Display models and providers configuration using rich formatting"""
|
|
140
|
+
themes = get_theme(config.theme)
|
|
141
|
+
console = Console(theme=themes.app_theme)
|
|
142
|
+
|
|
143
|
+
# Display each provider as a separate panel
|
|
144
|
+
for provider in config.provider_list:
|
|
145
|
+
# Provider info section
|
|
146
|
+
provider_info = Table.grid(padding=(0, 1))
|
|
147
|
+
provider_info.add_column(width=12)
|
|
148
|
+
provider_info.add_column()
|
|
149
|
+
|
|
150
|
+
provider_info.add_row(
|
|
151
|
+
Text("Protocol:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
152
|
+
Text(provider.protocol.value),
|
|
153
|
+
)
|
|
154
|
+
if provider.base_url:
|
|
155
|
+
provider_info.add_row(
|
|
156
|
+
Text("Base URL:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
157
|
+
Text(provider.base_url or "N/A"),
|
|
158
|
+
)
|
|
159
|
+
if provider.api_key:
|
|
160
|
+
provider_info.add_row(
|
|
161
|
+
Text("API Key:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
162
|
+
format_api_key_display(provider),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Check if provider has valid API key
|
|
166
|
+
provider_available = not provider.is_api_key_missing()
|
|
167
|
+
|
|
168
|
+
# Models table for this provider
|
|
169
|
+
models_table = Table.grid(padding=(0, 1), expand=True)
|
|
170
|
+
models_table.add_column(width=2, no_wrap=True) # Status
|
|
171
|
+
models_table.add_column(overflow="fold", ratio=1) # Name
|
|
172
|
+
models_table.add_column(overflow="fold", ratio=2) # Model
|
|
173
|
+
models_table.add_column(overflow="fold", ratio=3) # Params
|
|
174
|
+
|
|
175
|
+
# Add header
|
|
176
|
+
models_table.add_row(
|
|
177
|
+
Text("", style="bold"),
|
|
178
|
+
Text("Name", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
179
|
+
Text("Model", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
180
|
+
Text("Params", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Add models for this provider
|
|
184
|
+
for model in provider.model_list:
|
|
185
|
+
if not provider_available:
|
|
186
|
+
# Provider API key not set - show as unavailable
|
|
187
|
+
status = Text("-", style="dim")
|
|
188
|
+
name = Text(model.model_name, style="dim")
|
|
189
|
+
model_id = Text(model.model_params.model or "N/A", style="dim")
|
|
190
|
+
params = [Text("(unavailable)", style="dim")]
|
|
191
|
+
elif model.model_name == config.main_model:
|
|
192
|
+
status = Text("★", style=ThemeKey.CONFIG_STATUS_PRIMARY)
|
|
193
|
+
name = Text(model.model_name, style=ThemeKey.CONFIG_STATUS_PRIMARY)
|
|
194
|
+
model_id = Text(model.model_params.model or "N/A", style="")
|
|
195
|
+
params = _get_model_params_display(model)
|
|
196
|
+
else:
|
|
197
|
+
status = Text("✔", style=ThemeKey.CONFIG_STATUS_OK)
|
|
198
|
+
name = Text(model.model_name, style=ThemeKey.CONFIG_ITEM_NAME)
|
|
199
|
+
model_id = Text(model.model_params.model or "N/A", style="")
|
|
200
|
+
params = _get_model_params_display(model)
|
|
201
|
+
|
|
202
|
+
models_table.add_row(status, name, model_id, Group(*params))
|
|
203
|
+
|
|
204
|
+
# Create panel content with provider info and models
|
|
205
|
+
panel_elements = [
|
|
206
|
+
provider_info,
|
|
207
|
+
Text(""), # Spacer
|
|
208
|
+
Text("Models:", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
209
|
+
models_table,
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
# Add Codex status if this is a codex provider
|
|
213
|
+
if provider.protocol == LLMClientProtocol.CODEX:
|
|
214
|
+
panel_elements.append(Text("")) # Spacer
|
|
215
|
+
panel_elements.extend(_get_codex_status_elements())
|
|
216
|
+
|
|
217
|
+
panel_content = Group(*panel_elements)
|
|
218
|
+
|
|
219
|
+
panel = Panel(
|
|
220
|
+
panel_content,
|
|
221
|
+
title=Text(f"Provider: {provider.provider_name}", style="white bold"),
|
|
222
|
+
border_style=ThemeKey.CONFIG_PANEL_BORDER,
|
|
223
|
+
padding=(0, 1),
|
|
224
|
+
title_align="left",
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
console.print(panel)
|
|
228
|
+
console.print()
|
|
190
229
|
|
|
191
230
|
# Display main model info
|
|
192
231
|
console.print()
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
(
|
|
196
|
-
|
|
232
|
+
if config.main_model:
|
|
233
|
+
console.print(
|
|
234
|
+
Text.assemble(
|
|
235
|
+
("Default Model: ", "bold"),
|
|
236
|
+
(config.main_model, ThemeKey.CONFIG_STATUS_PRIMARY),
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
console.print(
|
|
241
|
+
Text.assemble(
|
|
242
|
+
("Default Model: ", "bold"),
|
|
243
|
+
("(not set)", ThemeKey.CONFIG_STATUS_ERROR),
|
|
244
|
+
)
|
|
197
245
|
)
|
|
198
|
-
)
|
|
199
246
|
|
|
200
247
|
for profile in iter_sub_agent_profiles():
|
|
201
248
|
sub_model_name = config.sub_agent_models.get(profile.name)
|
|
@@ -207,9 +254,3 @@ def display_models_and_providers(config: Config):
|
|
|
207
254
|
(sub_model_name, ThemeKey.CONFIG_STATUS_PRIMARY),
|
|
208
255
|
)
|
|
209
256
|
)
|
|
210
|
-
|
|
211
|
-
# Display Codex login status if any codex provider is configured
|
|
212
|
-
has_codex_provider = any(p.protocol.value == "codex" for p in config.provider_list)
|
|
213
|
-
if has_codex_provider:
|
|
214
|
-
console.print()
|
|
215
|
-
_display_codex_status(console)
|
klaude_code/cli/main.py
CHANGED
|
@@ -143,13 +143,28 @@ def exec_command(
|
|
|
143
143
|
raise typer.Exit(1)
|
|
144
144
|
|
|
145
145
|
from klaude_code.cli.runtime import AppInitConfig, run_exec
|
|
146
|
+
from klaude_code.config import load_config
|
|
146
147
|
from klaude_code.config.select_model import select_model_from_config
|
|
147
148
|
|
|
148
149
|
chosen_model = model
|
|
149
150
|
if model or select_model:
|
|
150
151
|
chosen_model = select_model_from_config(preferred=model)
|
|
151
152
|
if chosen_model is None:
|
|
152
|
-
|
|
153
|
+
raise typer.Exit(1)
|
|
154
|
+
else:
|
|
155
|
+
# Check if main_model is configured; if not, trigger interactive selection
|
|
156
|
+
config = load_config()
|
|
157
|
+
if config.main_model is None:
|
|
158
|
+
chosen_model = select_model_from_config()
|
|
159
|
+
if chosen_model is None:
|
|
160
|
+
raise typer.Exit(1)
|
|
161
|
+
# Save the selection as default
|
|
162
|
+
config.main_model = chosen_model
|
|
163
|
+
from klaude_code.config.config import config_path
|
|
164
|
+
from klaude_code.trace import log
|
|
165
|
+
|
|
166
|
+
asyncio.run(config.save())
|
|
167
|
+
log(f"Saved main_model={chosen_model} to {config_path}", style="cyan")
|
|
153
168
|
|
|
154
169
|
debug_enabled, debug_filters, log_path = prepare_debug_logging(debug, debug_filter)
|
|
155
170
|
|
|
@@ -275,8 +290,8 @@ def main_callback(
|
|
|
275
290
|
session_meta = Session.load_meta(session_id)
|
|
276
291
|
cfg = load_config()
|
|
277
292
|
|
|
278
|
-
if
|
|
279
|
-
if any(m.model_name == session_meta.model_config_name for m in cfg.
|
|
293
|
+
if session_meta.model_config_name:
|
|
294
|
+
if any(m.model_name == session_meta.model_config_name for m in cfg.iter_model_entries()):
|
|
280
295
|
chosen_model = session_meta.model_config_name
|
|
281
296
|
else:
|
|
282
297
|
log(
|
|
@@ -286,17 +301,34 @@ def main_callback(
|
|
|
286
301
|
)
|
|
287
302
|
)
|
|
288
303
|
|
|
289
|
-
if
|
|
304
|
+
if chosen_model is None and session_meta.model_name:
|
|
290
305
|
raw_model = session_meta.model_name.strip()
|
|
291
306
|
if raw_model:
|
|
292
307
|
matches = [
|
|
293
308
|
m.model_name
|
|
294
|
-
for m in cfg.
|
|
309
|
+
for m in cfg.iter_model_entries()
|
|
295
310
|
if (m.model_params.model or "").strip().lower() == raw_model.lower()
|
|
296
311
|
]
|
|
297
312
|
if len(matches) == 1:
|
|
298
313
|
chosen_model = matches[0]
|
|
299
314
|
|
|
315
|
+
# If still no model, check main_model; if not configured, trigger interactive selection
|
|
316
|
+
if chosen_model is None:
|
|
317
|
+
from klaude_code.config import load_config
|
|
318
|
+
|
|
319
|
+
cfg = load_config()
|
|
320
|
+
if cfg.main_model is None:
|
|
321
|
+
chosen_model = select_model_from_config()
|
|
322
|
+
if chosen_model is None:
|
|
323
|
+
raise typer.Exit(1)
|
|
324
|
+
# Save the selection as default
|
|
325
|
+
cfg.main_model = chosen_model
|
|
326
|
+
from klaude_code.config.config import config_path
|
|
327
|
+
from klaude_code.trace import log
|
|
328
|
+
|
|
329
|
+
asyncio.run(cfg.save())
|
|
330
|
+
log(f"Saved main_model={chosen_model} to {config_path}", style="dim")
|
|
331
|
+
|
|
300
332
|
debug_enabled, debug_filters, log_path = prepare_debug_logging(debug, debug_filter)
|
|
301
333
|
|
|
302
334
|
init_config = AppInitConfig(
|
klaude_code/cli/runtime.py
CHANGED
|
@@ -62,8 +62,6 @@ async def initialize_app_components(init_config: AppInitConfig) -> AppComponents
|
|
|
62
62
|
set_debug_logging(init_config.debug, filters=init_config.debug_filters)
|
|
63
63
|
|
|
64
64
|
config = load_config()
|
|
65
|
-
if config is None:
|
|
66
|
-
raise typer.Exit(1)
|
|
67
65
|
|
|
68
66
|
# Initialize LLM clients
|
|
69
67
|
try:
|
|
@@ -160,7 +158,7 @@ async def initialize_session(
|
|
|
160
158
|
def _backfill_session_model_config(
|
|
161
159
|
agent: Agent | None,
|
|
162
160
|
model_override: str | None,
|
|
163
|
-
default_model: str,
|
|
161
|
+
default_model: str | None,
|
|
164
162
|
is_new_session: bool,
|
|
165
163
|
) -> None:
|
|
166
164
|
"""Backfill model_config_name and model_thinking on newly created sessions."""
|
|
@@ -169,7 +167,7 @@ def _backfill_session_model_config(
|
|
|
169
167
|
|
|
170
168
|
if model_override is not None:
|
|
171
169
|
agent.session.model_config_name = model_override
|
|
172
|
-
elif is_new_session:
|
|
170
|
+
elif is_new_session and default_model is not None:
|
|
173
171
|
agent.session.model_config_name = default_model
|
|
174
172
|
else:
|
|
175
173
|
return
|
|
@@ -305,8 +303,8 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
305
303
|
printer.print(Text(f" {MSG} ", style="bold yellow reverse"))
|
|
306
304
|
else:
|
|
307
305
|
print(MSG, file=sys.stderr)
|
|
308
|
-
except
|
|
309
|
-
# Fallback if themed print is unavailable
|
|
306
|
+
except (AttributeError, TypeError, RuntimeError):
|
|
307
|
+
# Fallback if themed print is unavailable (e.g., display not ready or Rich internal error)
|
|
310
308
|
print(MSG, file=sys.stderr)
|
|
311
309
|
|
|
312
310
|
def _hide_progress() -> None:
|
klaude_code/cli/self_update.py
CHANGED
|
@@ -174,7 +174,8 @@ def _print_version() -> None:
|
|
|
174
174
|
ver = pkg_version(PACKAGE_NAME)
|
|
175
175
|
except PackageNotFoundError:
|
|
176
176
|
ver = "unknown"
|
|
177
|
-
except
|
|
177
|
+
except (ValueError, TypeError):
|
|
178
|
+
# Catch invalid package name format or type errors
|
|
178
179
|
ver = "unknown"
|
|
179
180
|
print(f"{PACKAGE_NAME} {ver}")
|
|
180
181
|
|
klaude_code/cli/session_cmd.py
CHANGED
|
@@ -13,7 +13,7 @@ def _session_confirm(sessions: list[Session.SessionMetaBrief], message: str) ->
|
|
|
13
13
|
def _fmt(ts: float) -> str:
|
|
14
14
|
try:
|
|
15
15
|
return time.strftime("%m-%d %H:%M:%S", time.localtime(ts))
|
|
16
|
-
except
|
|
16
|
+
except (OSError, OverflowError, ValueError):
|
|
17
17
|
return str(ts)
|
|
18
18
|
|
|
19
19
|
log(f"Sessions to delete ({len(sessions)}):")
|
klaude_code/config/__init__.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from .config import Config, config_path, load_config
|
|
1
|
+
from .config import Config, UserConfig, config_path, load_config, print_no_available_models_hint
|
|
2
2
|
|
|
3
3
|
__all__ = [
|
|
4
4
|
"Config",
|
|
5
|
+
"UserConfig",
|
|
5
6
|
"config_path",
|
|
6
7
|
"load_config",
|
|
8
|
+
"print_no_available_models_hint",
|
|
7
9
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Asset files for config module
|