klaude-code 1.8.0__py3-none-any.whl → 1.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.
- klaude_code/auth/base.py +101 -0
- klaude_code/auth/claude/__init__.py +6 -0
- klaude_code/auth/claude/exceptions.py +9 -0
- klaude_code/auth/claude/oauth.py +172 -0
- klaude_code/auth/claude/token_manager.py +26 -0
- klaude_code/auth/codex/token_manager.py +10 -50
- klaude_code/cli/auth_cmd.py +127 -46
- klaude_code/cli/config_cmd.py +4 -2
- klaude_code/cli/cost_cmd.py +14 -9
- klaude_code/cli/list_model.py +248 -200
- klaude_code/command/prompt-commit.md +73 -0
- klaude_code/config/assets/builtin_config.yaml +36 -3
- klaude_code/config/config.py +24 -5
- klaude_code/config/thinking.py +4 -4
- klaude_code/core/prompt.py +1 -1
- klaude_code/llm/anthropic/client.py +28 -3
- klaude_code/llm/claude/__init__.py +3 -0
- klaude_code/llm/claude/client.py +95 -0
- klaude_code/llm/codex/client.py +1 -1
- klaude_code/llm/registry.py +3 -1
- klaude_code/protocol/llm_param.py +2 -1
- klaude_code/protocol/sub_agent/__init__.py +1 -2
- klaude_code/session/session.py +4 -4
- klaude_code/ui/renderers/metadata.py +6 -26
- klaude_code/ui/rich/theme.py +6 -5
- klaude_code/ui/utils/common.py +46 -0
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/METADATA +25 -5
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/RECORD +30 -25
- klaude_code/command/prompt-jj-describe.md +0 -32
- klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
- klaude_code/protocol/sub_agent/oracle.py +0 -91
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/entry_points.txt +0 -0
klaude_code/cli/list_model.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
|
|
3
|
+
from rich.box import HORIZONTALS
|
|
3
4
|
from rich.console import Console, Group
|
|
4
|
-
from rich.panel import Panel
|
|
5
5
|
from rich.table import Table
|
|
6
6
|
from rich.text import Text
|
|
7
7
|
|
|
@@ -9,65 +9,127 @@ from klaude_code.config import Config
|
|
|
9
9
|
from klaude_code.config.config import ModelConfig, ProviderConfig, parse_env_var_syntax
|
|
10
10
|
from klaude_code.protocol.llm_param import LLMClientProtocol
|
|
11
11
|
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
12
|
+
from klaude_code.ui.rich.quote import Quote
|
|
12
13
|
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
14
|
+
from klaude_code.ui.utils.common import format_model_params
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
def
|
|
16
|
-
"""Get Codex OAuth login status as
|
|
17
|
+
def _get_codex_status_rows() -> list[tuple[Text, Text]]:
|
|
18
|
+
"""Get Codex OAuth login status as (label, value) tuples for table display."""
|
|
17
19
|
from klaude_code.auth.codex.token_manager import CodexTokenManager
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
rows: list[tuple[Text, Text]] = []
|
|
20
22
|
token_manager = CodexTokenManager()
|
|
21
23
|
state = token_manager.get_state()
|
|
22
24
|
|
|
23
25
|
if state is None:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
("Status
|
|
27
|
-
(
|
|
28
|
-
|
|
26
|
+
rows.append(
|
|
27
|
+
(
|
|
28
|
+
Text("Status", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
29
|
+
Text.assemble(
|
|
30
|
+
("Not logged in", ThemeKey.CONFIG_STATUS_ERROR),
|
|
31
|
+
(" (run 'klaude login codex' to authenticate)", "dim"),
|
|
32
|
+
),
|
|
29
33
|
)
|
|
30
34
|
)
|
|
31
35
|
elif state.is_expired():
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
("Status
|
|
35
|
-
(
|
|
36
|
-
|
|
36
|
+
rows.append(
|
|
37
|
+
(
|
|
38
|
+
Text("Status", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
39
|
+
Text.assemble(
|
|
40
|
+
("Token expired", ThemeKey.CONFIG_STATUS_ERROR),
|
|
41
|
+
(" (run 'klaude login codex' to re-authenticate)", "dim"),
|
|
42
|
+
),
|
|
37
43
|
)
|
|
38
44
|
)
|
|
39
45
|
else:
|
|
40
46
|
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
("Status
|
|
44
|
-
(
|
|
45
|
-
|
|
47
|
+
rows.append(
|
|
48
|
+
(
|
|
49
|
+
Text("Status", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
50
|
+
Text.assemble(
|
|
51
|
+
("Logged in", ThemeKey.CONFIG_STATUS_OK),
|
|
52
|
+
(
|
|
53
|
+
f" (account: {state.account_id[:8]}..., expires: {expires_dt.strftime('%Y-%m-%d %H:%M UTC')})",
|
|
54
|
+
"dim",
|
|
55
|
+
),
|
|
56
|
+
),
|
|
46
57
|
)
|
|
47
58
|
)
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
("
|
|
52
|
-
(
|
|
60
|
+
rows.append(
|
|
61
|
+
(
|
|
62
|
+
Text("Usage", style="dim"),
|
|
63
|
+
Text(
|
|
53
64
|
"https://chatgpt.com/codex/settings/usage",
|
|
54
|
-
"blue
|
|
65
|
+
style="blue link https://chatgpt.com/codex/settings/usage",
|
|
55
66
|
),
|
|
56
|
-
(" for up-to-date information on rate limits and credits", "dim"),
|
|
57
67
|
)
|
|
58
68
|
)
|
|
59
|
-
return
|
|
69
|
+
return rows
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_claude_status_rows() -> list[tuple[Text, Text]]:
|
|
73
|
+
"""Get Claude OAuth login status as (label, value) tuples for table display."""
|
|
74
|
+
from klaude_code.auth.claude.token_manager import ClaudeTokenManager
|
|
75
|
+
|
|
76
|
+
rows: list[tuple[Text, Text]] = []
|
|
77
|
+
token_manager = ClaudeTokenManager()
|
|
78
|
+
state = token_manager.get_state()
|
|
79
|
+
|
|
80
|
+
if state is None:
|
|
81
|
+
rows.append(
|
|
82
|
+
(
|
|
83
|
+
Text("Status", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
84
|
+
Text.assemble(
|
|
85
|
+
("Not logged in", ThemeKey.CONFIG_STATUS_ERROR),
|
|
86
|
+
(" (run 'klaude login claude' to authenticate)", "dim"),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
elif state.is_expired():
|
|
91
|
+
rows.append(
|
|
92
|
+
(
|
|
93
|
+
Text("Status", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
94
|
+
Text.assemble(
|
|
95
|
+
("Token expired", ThemeKey.CONFIG_STATUS_ERROR),
|
|
96
|
+
(" (will refresh automatically on use; run 'klaude login claude' if refresh fails)", "dim"),
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
expires_dt = datetime.datetime.fromtimestamp(state.expires_at, tz=datetime.UTC)
|
|
102
|
+
rows.append(
|
|
103
|
+
(
|
|
104
|
+
Text("Status", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
105
|
+
Text.assemble(
|
|
106
|
+
("Logged in", ThemeKey.CONFIG_STATUS_OK),
|
|
107
|
+
(f" (expires: {expires_dt.strftime('%Y-%m-%d %H:%M UTC')})", "dim"),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
rows.append(
|
|
113
|
+
(
|
|
114
|
+
Text("Usage", style="dim"),
|
|
115
|
+
Text(
|
|
116
|
+
"https://claude.ai/settings/usage",
|
|
117
|
+
style="blue link https://claude.ai/settings/usage",
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
return rows
|
|
60
122
|
|
|
61
123
|
|
|
62
124
|
def mask_api_key(api_key: str | None) -> str:
|
|
63
125
|
"""Mask API key to show only first 6 and last 6 characters with *** in between"""
|
|
64
|
-
if not api_key
|
|
65
|
-
return "
|
|
126
|
+
if not api_key:
|
|
127
|
+
return ""
|
|
66
128
|
|
|
67
129
|
if len(api_key) <= 12:
|
|
68
130
|
return api_key
|
|
69
131
|
|
|
70
|
-
return f"{api_key[:6]}
|
|
132
|
+
return f"{api_key[:6]}…{api_key[-6:]}"
|
|
71
133
|
|
|
72
134
|
|
|
73
135
|
def format_api_key_display(provider: ProviderConfig) -> Text:
|
|
@@ -91,7 +153,7 @@ def format_api_key_display(provider: ProviderConfig) -> Text:
|
|
|
91
153
|
# Plain API key
|
|
92
154
|
return Text(mask_api_key(provider.api_key))
|
|
93
155
|
else:
|
|
94
|
-
return Text("
|
|
156
|
+
return Text("")
|
|
95
157
|
|
|
96
158
|
|
|
97
159
|
def format_env_var_display(value: str | None) -> Text:
|
|
@@ -114,194 +176,180 @@ def format_env_var_display(value: str | None) -> Text:
|
|
|
114
176
|
# Plain value
|
|
115
177
|
return Text(mask_api_key(value))
|
|
116
178
|
else:
|
|
117
|
-
return Text("
|
|
179
|
+
return Text("")
|
|
118
180
|
|
|
119
181
|
|
|
120
182
|
def _get_model_params_display(model: ModelConfig) -> list[Text]:
|
|
121
183
|
"""Get display elements for model parameters."""
|
|
122
|
-
|
|
123
|
-
if
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
model.model_params.thinking.reasoning_summary,
|
|
138
|
-
)
|
|
139
|
-
)
|
|
140
|
-
if model.model_params.thinking.budget_tokens is not None:
|
|
141
|
-
params.append(
|
|
142
|
-
Text.assemble(
|
|
143
|
-
("thinking-budget-tokens", ThemeKey.CONFIG_PARAM_LABEL),
|
|
144
|
-
": ",
|
|
145
|
-
str(model.model_params.thinking.budget_tokens),
|
|
146
|
-
)
|
|
147
|
-
)
|
|
148
|
-
if model.model_params.provider_routing:
|
|
149
|
-
params.append(
|
|
150
|
-
Text.assemble(
|
|
151
|
-
("provider-routing", ThemeKey.CONFIG_PARAM_LABEL),
|
|
152
|
-
": ",
|
|
153
|
-
model.model_params.provider_routing.model_dump_json(exclude_none=True),
|
|
154
|
-
)
|
|
184
|
+
param_strings = format_model_params(model.model_params)
|
|
185
|
+
if param_strings:
|
|
186
|
+
return [Text(s) for s in param_strings]
|
|
187
|
+
return [Text("")]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _build_provider_info_panel(provider: ProviderConfig, available: bool) -> Quote:
|
|
191
|
+
"""Build a Quote containing provider name and information using a two-column grid."""
|
|
192
|
+
# Provider name as title
|
|
193
|
+
if available:
|
|
194
|
+
title = Text(provider.provider_name, style=ThemeKey.CONFIG_PROVIDER)
|
|
195
|
+
else:
|
|
196
|
+
title = Text.assemble(
|
|
197
|
+
(provider.provider_name, ThemeKey.CONFIG_PROVIDER),
|
|
198
|
+
(" (Unavailable)", ThemeKey.CONFIG_STATUS_ERROR),
|
|
155
199
|
)
|
|
156
|
-
if len(params) == 0:
|
|
157
|
-
params.append(Text("N/A", style=ThemeKey.CONFIG_PARAM_LABEL))
|
|
158
|
-
return params
|
|
159
200
|
|
|
201
|
+
# Build info table with two columns
|
|
202
|
+
info_table = Table.grid(padding=(0, 2))
|
|
203
|
+
info_table.add_column("Label", style=ThemeKey.CONFIG_PARAM_LABEL)
|
|
204
|
+
info_table.add_column("Value")
|
|
205
|
+
|
|
206
|
+
# Protocol
|
|
207
|
+
info_table.add_row(Text("Protocol"), Text(provider.protocol.value))
|
|
208
|
+
|
|
209
|
+
# Base URL (if set)
|
|
210
|
+
if provider.base_url:
|
|
211
|
+
info_table.add_row(Text("Base URL"), Text(provider.base_url))
|
|
212
|
+
|
|
213
|
+
# API key (if set)
|
|
214
|
+
if provider.api_key:
|
|
215
|
+
info_table.add_row(Text("API key"), format_api_key_display(provider))
|
|
216
|
+
|
|
217
|
+
# AWS Bedrock parameters
|
|
218
|
+
if provider.protocol == LLMClientProtocol.BEDROCK:
|
|
219
|
+
if provider.aws_access_key:
|
|
220
|
+
info_table.add_row(Text("AWS key"), format_env_var_display(provider.aws_access_key))
|
|
221
|
+
if provider.aws_secret_key:
|
|
222
|
+
info_table.add_row(Text("AWS secret"), format_env_var_display(provider.aws_secret_key))
|
|
223
|
+
if provider.aws_region:
|
|
224
|
+
info_table.add_row(Text("AWS region"), format_env_var_display(provider.aws_region))
|
|
225
|
+
if provider.aws_session_token:
|
|
226
|
+
info_table.add_row(Text("AWS token"), format_env_var_display(provider.aws_session_token))
|
|
227
|
+
if provider.aws_profile:
|
|
228
|
+
info_table.add_row(Text("AWS profile"), format_env_var_display(provider.aws_profile))
|
|
229
|
+
|
|
230
|
+
# OAuth status rows
|
|
231
|
+
if provider.protocol == LLMClientProtocol.CODEX_OAUTH:
|
|
232
|
+
for label, value in _get_codex_status_rows():
|
|
233
|
+
info_table.add_row(label, value)
|
|
234
|
+
if provider.protocol == LLMClientProtocol.CLAUDE_OAUTH:
|
|
235
|
+
for label, value in _get_claude_status_rows():
|
|
236
|
+
info_table.add_row(label, value)
|
|
237
|
+
|
|
238
|
+
return Quote(
|
|
239
|
+
Group(title, info_table),
|
|
240
|
+
style=ThemeKey.LINES,
|
|
241
|
+
prefix="┃ ",
|
|
242
|
+
)
|
|
160
243
|
|
|
161
|
-
def display_models_and_providers(config: Config):
|
|
162
|
-
"""Display models and providers configuration using rich formatting"""
|
|
163
|
-
themes = get_theme(config.theme)
|
|
164
|
-
console = Console(theme=themes.app_theme)
|
|
165
244
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
provider_info.add_column()
|
|
245
|
+
def _build_models_table(
|
|
246
|
+
provider: ProviderConfig, main_model: str | None, sub_agent_models: dict[str, str] | None = None
|
|
247
|
+
) -> Table:
|
|
248
|
+
"""Build a table for models under a provider."""
|
|
249
|
+
provider_available = not provider.is_api_key_missing()
|
|
172
250
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
Text(provider.base_url or "N/A"),
|
|
181
|
-
)
|
|
182
|
-
if provider.api_key:
|
|
183
|
-
provider_info.add_row(
|
|
184
|
-
Text("API Key:", style=ThemeKey.CONFIG_PARAM_LABEL),
|
|
185
|
-
format_api_key_display(provider),
|
|
186
|
-
)
|
|
251
|
+
# Build reverse mapping: model_name -> list of agent roles using it
|
|
252
|
+
model_to_agents: dict[str, list[str]] = {}
|
|
253
|
+
if sub_agent_models:
|
|
254
|
+
for agent_role, model_name in sub_agent_models.items():
|
|
255
|
+
if model_name not in model_to_agents:
|
|
256
|
+
model_to_agents[model_name] = []
|
|
257
|
+
model_to_agents[model_name].append(agent_role)
|
|
187
258
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
259
|
+
models_table = Table.grid(
|
|
260
|
+
padding=(0, 2),
|
|
261
|
+
)
|
|
262
|
+
models_table.add_column("Model Name", min_width=12)
|
|
263
|
+
models_table.add_column("Model ID", min_width=20, style=ThemeKey.CONFIG_MODEL_ID)
|
|
264
|
+
models_table.add_column("Params", style="dim")
|
|
265
|
+
|
|
266
|
+
model_count = len(provider.model_list)
|
|
267
|
+
for i, model in enumerate(provider.model_list):
|
|
268
|
+
is_last = i == model_count - 1
|
|
269
|
+
prefix = " └─ " if is_last else " ├─ "
|
|
270
|
+
|
|
271
|
+
if not provider_available:
|
|
272
|
+
name = Text.assemble((prefix, ThemeKey.LINES), (model.model_name, "dim"))
|
|
273
|
+
model_id = Text(model.model_params.model or "", style="dim")
|
|
274
|
+
params = Text("(unavailable)", style="dim")
|
|
275
|
+
else:
|
|
276
|
+
# Build role tags for this model
|
|
277
|
+
roles: list[str] = []
|
|
278
|
+
if model.model_name == main_model:
|
|
279
|
+
roles.append("default")
|
|
280
|
+
if model.model_name in model_to_agents:
|
|
281
|
+
roles.extend(role.lower() for role in model_to_agents[model.model_name])
|
|
282
|
+
|
|
283
|
+
if roles:
|
|
284
|
+
name = Text.assemble(
|
|
285
|
+
(prefix, ThemeKey.LINES),
|
|
286
|
+
(model.model_name, ThemeKey.CONFIG_STATUS_PRIMARY),
|
|
287
|
+
(f" ({', '.join(roles)})", "dim"),
|
|
214
288
|
)
|
|
289
|
+
else:
|
|
290
|
+
name = Text.assemble((prefix, ThemeKey.LINES), (model.model_name, ThemeKey.CONFIG_ITEM_NAME))
|
|
291
|
+
model_id = Text(model.model_params.model or "")
|
|
292
|
+
params = Text(" · ").join(_get_model_params_display(model))
|
|
215
293
|
|
|
216
|
-
|
|
217
|
-
provider_available = not provider.is_api_key_missing()
|
|
294
|
+
models_table.add_row(name, model_id, params)
|
|
218
295
|
|
|
219
|
-
|
|
220
|
-
models_table = Table.grid(padding=(0, 1), expand=True)
|
|
221
|
-
models_table.add_column(width=2, no_wrap=True) # Status
|
|
222
|
-
models_table.add_column(overflow="fold", ratio=2) # Name
|
|
223
|
-
models_table.add_column(overflow="fold", ratio=3) # Model
|
|
224
|
-
models_table.add_column(overflow="fold", ratio=4) # Params
|
|
225
|
-
|
|
226
|
-
# Add header
|
|
227
|
-
models_table.add_row(
|
|
228
|
-
Text("", style="bold"),
|
|
229
|
-
Text("Name", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
230
|
-
Text("Model", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
231
|
-
Text("Params", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
232
|
-
)
|
|
296
|
+
return models_table
|
|
233
297
|
|
|
234
|
-
# Add models for this provider
|
|
235
|
-
for model in provider.model_list:
|
|
236
|
-
if not provider_available:
|
|
237
|
-
# Provider API key not set - show as unavailable
|
|
238
|
-
status = Text("-", style="dim")
|
|
239
|
-
name = Text(model.model_name, style="dim")
|
|
240
|
-
model_id = Text(model.model_params.model or "N/A", style="dim")
|
|
241
|
-
params = [Text("(unavailable)", style="dim")]
|
|
242
|
-
elif model.model_name == config.main_model:
|
|
243
|
-
status = Text("★", style=ThemeKey.CONFIG_STATUS_PRIMARY)
|
|
244
|
-
name = Text(model.model_name, style=ThemeKey.CONFIG_STATUS_PRIMARY)
|
|
245
|
-
model_id = Text(model.model_params.model or "N/A", style="")
|
|
246
|
-
params = _get_model_params_display(model)
|
|
247
|
-
else:
|
|
248
|
-
status = Text("✔", style=ThemeKey.CONFIG_STATUS_OK)
|
|
249
|
-
name = Text(model.model_name, style=ThemeKey.CONFIG_ITEM_NAME)
|
|
250
|
-
model_id = Text(model.model_params.model or "N/A", style="")
|
|
251
|
-
params = _get_model_params_display(model)
|
|
252
|
-
|
|
253
|
-
models_table.add_row(status, name, model_id, Group(*params))
|
|
254
|
-
|
|
255
|
-
# Create panel content with provider info and models
|
|
256
|
-
panel_elements = [
|
|
257
|
-
provider_info,
|
|
258
|
-
Text(""), # Spacer
|
|
259
|
-
Text("Models:", style=ThemeKey.CONFIG_TABLE_HEADER),
|
|
260
|
-
models_table,
|
|
261
|
-
]
|
|
262
|
-
|
|
263
|
-
# Add Codex status if this is a codex provider
|
|
264
|
-
if provider.protocol == LLMClientProtocol.CODEX:
|
|
265
|
-
panel_elements.append(Text("")) # Spacer
|
|
266
|
-
panel_elements.extend(_get_codex_status_elements())
|
|
267
|
-
|
|
268
|
-
panel_content = Group(*panel_elements)
|
|
269
|
-
|
|
270
|
-
panel = Panel(
|
|
271
|
-
panel_content,
|
|
272
|
-
title=Text(f"Provider: {provider.provider_name}", style=ThemeKey.CONFIG_PROVIDER),
|
|
273
|
-
border_style=ThemeKey.CONFIG_PANEL_BORDER,
|
|
274
|
-
padding=(0, 1),
|
|
275
|
-
title_align="left",
|
|
276
|
-
)
|
|
277
298
|
|
|
278
|
-
|
|
279
|
-
|
|
299
|
+
def _display_agent_models_table(config: Config, console: Console) -> None:
|
|
300
|
+
"""Display model assignments as a table."""
|
|
301
|
+
console.print(Text(" Agent Models:", style=ThemeKey.CONFIG_TABLE_HEADER))
|
|
302
|
+
agent_table = Table(
|
|
303
|
+
box=HORIZONTALS,
|
|
304
|
+
show_header=True,
|
|
305
|
+
header_style=ThemeKey.CONFIG_TABLE_HEADER,
|
|
306
|
+
padding=(0, 2),
|
|
307
|
+
border_style=ThemeKey.LINES,
|
|
308
|
+
)
|
|
309
|
+
agent_table.add_column("Role", style="bold", min_width=10)
|
|
310
|
+
agent_table.add_column("Model", style=ThemeKey.CONFIG_STATUS_PRIMARY)
|
|
280
311
|
|
|
281
|
-
#
|
|
282
|
-
console.print()
|
|
312
|
+
# Default model
|
|
283
313
|
if config.main_model:
|
|
284
|
-
|
|
285
|
-
Text.assemble(
|
|
286
|
-
("Default Model: ", "bold"),
|
|
287
|
-
(config.main_model, ThemeKey.CONFIG_STATUS_PRIMARY),
|
|
288
|
-
)
|
|
289
|
-
)
|
|
314
|
+
agent_table.add_row("Default", config.main_model)
|
|
290
315
|
else:
|
|
291
|
-
|
|
292
|
-
Text.assemble(
|
|
293
|
-
("Default Model: ", "bold"),
|
|
294
|
-
("(not set)", ThemeKey.CONFIG_STATUS_ERROR),
|
|
295
|
-
)
|
|
296
|
-
)
|
|
316
|
+
agent_table.add_row("Default", Text("(not set)", style=ThemeKey.CONFIG_STATUS_ERROR))
|
|
297
317
|
|
|
318
|
+
# Sub-agent models
|
|
298
319
|
for profile in iter_sub_agent_profiles():
|
|
299
320
|
sub_model_name = config.sub_agent_models.get(profile.name)
|
|
300
|
-
if
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
if sub_model_name:
|
|
322
|
+
agent_table.add_row(profile.name, sub_model_name)
|
|
323
|
+
|
|
324
|
+
console.print(agent_table)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def display_models_and_providers(config: Config, *, show_all: bool = False):
|
|
328
|
+
"""Display models and providers configuration using rich formatting"""
|
|
329
|
+
themes = get_theme(config.theme)
|
|
330
|
+
console = Console(theme=themes.app_theme)
|
|
331
|
+
|
|
332
|
+
# Display model assignments as a table
|
|
333
|
+
_display_agent_models_table(config, console)
|
|
334
|
+
console.print()
|
|
335
|
+
|
|
336
|
+
# Sort providers: available (api_key set) first, unavailable (api_key not set) last
|
|
337
|
+
sorted_providers = sorted(config.provider_list, key=lambda p: (p.is_api_key_missing(), p.provider_name))
|
|
338
|
+
|
|
339
|
+
# Filter out unavailable providers unless show_all is True
|
|
340
|
+
if not show_all:
|
|
341
|
+
sorted_providers = [p for p in sorted_providers if not p.is_api_key_missing()]
|
|
342
|
+
|
|
343
|
+
# Display each provider with its models table
|
|
344
|
+
for provider in sorted_providers:
|
|
345
|
+
provider_available = not provider.is_api_key_missing()
|
|
346
|
+
|
|
347
|
+
# Provider info panel
|
|
348
|
+
provider_panel = _build_provider_info_panel(provider, provider_available)
|
|
349
|
+
console.print(provider_panel)
|
|
350
|
+
console.print()
|
|
351
|
+
|
|
352
|
+
# Models table for this provider
|
|
353
|
+
models_table = _build_models_table(provider, config.main_model, config.sub_agent_models)
|
|
354
|
+
console.print(models_table)
|
|
355
|
+
console.print("\n")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Commit current git changes
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
### Step 1: Detect version control system
|
|
8
|
+
|
|
9
|
+
Check if `jj` is available in the current environment.
|
|
10
|
+
|
|
11
|
+
### Step 2A: If jj is available
|
|
12
|
+
|
|
13
|
+
1. Run `jj status` and `jj log -r 'ancestors(@, 10)'` to see working copy changes and the last 10 changes
|
|
14
|
+
2. For each change that has no description (shows as "(no description set)"):
|
|
15
|
+
- Run `jj diff -r <change_id> --git` to view the diff
|
|
16
|
+
- Read related files if needed to understand the context
|
|
17
|
+
- Use `jj describe -r <change_id>` to add a meaningful description
|
|
18
|
+
|
|
19
|
+
### Step 2B: If jj is not available (git)
|
|
20
|
+
|
|
21
|
+
1. Run `git status` to check working directory state
|
|
22
|
+
2. Run `git diff --cached` to check if there are staged changes
|
|
23
|
+
3. If staging area has content:
|
|
24
|
+
- Ask the user: "Staging area has changes. Commit only staged changes, or stage and commit all changes?"
|
|
25
|
+
- If user chooses staged only: proceed with staged changes
|
|
26
|
+
- If user chooses all: run `git add -A` first
|
|
27
|
+
4. If staging area is empty:
|
|
28
|
+
- Run `git add -A` to stage all changes
|
|
29
|
+
5. Review the changes with `git diff --cached`
|
|
30
|
+
6. Create the commit
|
|
31
|
+
|
|
32
|
+
## Commit Message Format
|
|
33
|
+
|
|
34
|
+
In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC:
|
|
35
|
+
|
|
36
|
+
For jj:
|
|
37
|
+
```bash
|
|
38
|
+
jj describe -m "$(cat <<'EOF'
|
|
39
|
+
Commit message here.
|
|
40
|
+
EOF
|
|
41
|
+
)"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For git:
|
|
45
|
+
```bash
|
|
46
|
+
git commit -m "$(cat <<'EOF'
|
|
47
|
+
Commit message here.
|
|
48
|
+
EOF
|
|
49
|
+
)"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Message Style
|
|
53
|
+
|
|
54
|
+
Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
<type>(<scope>): <description>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Types:
|
|
61
|
+
- `feat`: New feature
|
|
62
|
+
- `fix`: Bug fix
|
|
63
|
+
- `docs`: Documentation changes
|
|
64
|
+
- `style`: Code style changes (formatting, no logic change)
|
|
65
|
+
- `refactor`: Code refactoring (no feature or fix)
|
|
66
|
+
- `test`: Adding or updating tests
|
|
67
|
+
- `chore`: Build process, dependencies, or tooling changes
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
- `feat(cli): add --verbose flag for debug output`
|
|
71
|
+
- `fix(llm): handle API timeout errors gracefully`
|
|
72
|
+
- `docs(readme): update installation instructions`
|
|
73
|
+
- `refactor(core): simplify session state management`
|
|
@@ -259,7 +259,6 @@ provider_list:
|
|
|
259
259
|
base_url: https://api.deepseek.com/anthropic
|
|
260
260
|
model_list:
|
|
261
261
|
- model_name: deepseek
|
|
262
|
-
provider: deepseek
|
|
263
262
|
model_params:
|
|
264
263
|
model: deepseek-reasoner
|
|
265
264
|
context_limit: 128000
|
|
@@ -290,11 +289,45 @@ provider_list:
|
|
|
290
289
|
cache_read: 1.0
|
|
291
290
|
currency: CNY
|
|
292
291
|
|
|
292
|
+
- provider_name: claude-max
|
|
293
|
+
protocol: claude_oauth
|
|
294
|
+
model_list:
|
|
295
|
+
- model_name: sonnet@claude-max
|
|
296
|
+
model_params:
|
|
297
|
+
model: claude-sonnet-4-5-20250929
|
|
298
|
+
context_limit: 200000
|
|
299
|
+
cost:
|
|
300
|
+
input: 3.0
|
|
301
|
+
output: 15.0
|
|
302
|
+
cache_read: 0.3
|
|
303
|
+
cache_write: 3.75
|
|
304
|
+
- model_name: opus@claude-max
|
|
305
|
+
model_params:
|
|
306
|
+
model: claude-opus-4-5-20251101
|
|
307
|
+
context_limit: 200000
|
|
308
|
+
verbosity: high
|
|
309
|
+
thinking:
|
|
310
|
+
type: enabled
|
|
311
|
+
budget_tokens: 2048
|
|
312
|
+
cost:
|
|
313
|
+
input: 5.0
|
|
314
|
+
output: 25.0
|
|
315
|
+
cache_read: 0.5
|
|
316
|
+
cache_write: 6.25
|
|
317
|
+
- model_name: haiku@claude-max
|
|
318
|
+
model_params:
|
|
319
|
+
model: claude-haiku-4-5-20251001
|
|
320
|
+
context_limit: 200000
|
|
321
|
+
cost:
|
|
322
|
+
input: 1.0
|
|
323
|
+
output: 5.0
|
|
324
|
+
cache_read: 0.1
|
|
325
|
+
cache_write: 1.25
|
|
326
|
+
|
|
293
327
|
- provider_name: codex
|
|
294
|
-
protocol:
|
|
328
|
+
protocol: codex_oauth
|
|
295
329
|
model_list:
|
|
296
330
|
- model_name: gpt-5.2-codex
|
|
297
|
-
provider: codex
|
|
298
331
|
model_params:
|
|
299
332
|
model: gpt-5.2-codex
|
|
300
333
|
thinking:
|