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.
Files changed (33) hide show
  1. klaude_code/auth/base.py +101 -0
  2. klaude_code/auth/claude/__init__.py +6 -0
  3. klaude_code/auth/claude/exceptions.py +9 -0
  4. klaude_code/auth/claude/oauth.py +172 -0
  5. klaude_code/auth/claude/token_manager.py +26 -0
  6. klaude_code/auth/codex/token_manager.py +10 -50
  7. klaude_code/cli/auth_cmd.py +127 -46
  8. klaude_code/cli/config_cmd.py +4 -2
  9. klaude_code/cli/cost_cmd.py +14 -9
  10. klaude_code/cli/list_model.py +248 -200
  11. klaude_code/command/prompt-commit.md +73 -0
  12. klaude_code/config/assets/builtin_config.yaml +36 -3
  13. klaude_code/config/config.py +24 -5
  14. klaude_code/config/thinking.py +4 -4
  15. klaude_code/core/prompt.py +1 -1
  16. klaude_code/llm/anthropic/client.py +28 -3
  17. klaude_code/llm/claude/__init__.py +3 -0
  18. klaude_code/llm/claude/client.py +95 -0
  19. klaude_code/llm/codex/client.py +1 -1
  20. klaude_code/llm/registry.py +3 -1
  21. klaude_code/protocol/llm_param.py +2 -1
  22. klaude_code/protocol/sub_agent/__init__.py +1 -2
  23. klaude_code/session/session.py +4 -4
  24. klaude_code/ui/renderers/metadata.py +6 -26
  25. klaude_code/ui/rich/theme.py +6 -5
  26. klaude_code/ui/utils/common.py +46 -0
  27. {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/METADATA +25 -5
  28. {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/RECORD +30 -25
  29. klaude_code/command/prompt-jj-describe.md +0 -32
  30. klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
  31. klaude_code/protocol/sub_agent/oracle.py +0 -91
  32. {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/WHEEL +0 -0
  33. {klaude_code-1.8.0.dist-info → klaude_code-1.9.0.dist-info}/entry_points.txt +0 -0
@@ -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 _get_codex_status_elements() -> list[Text]:
16
- """Get Codex OAuth login status as Text elements for panel display."""
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
- elements: list[Text] = []
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
- elements.append(
25
- Text.assemble(
26
- ("Status: ", "bold"),
27
- ("Not logged in", ThemeKey.CONFIG_STATUS_ERROR),
28
- (" (run 'klaude login codex' to authenticate)", "dim"),
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
- elements.append(
33
- Text.assemble(
34
- ("Status: ", "bold"),
35
- ("Token expired", ThemeKey.CONFIG_STATUS_ERROR),
36
- (" (run 'klaude login codex' to re-authenticate)", "dim"),
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
- elements.append(
42
- Text.assemble(
43
- ("Status: ", "bold"),
44
- ("Logged in", ThemeKey.CONFIG_STATUS_OK),
45
- (f" (account: {state.account_id[:8]}..., expires: {expires_dt.strftime('%Y-%m-%d %H:%M UTC')})", "dim"),
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
- elements.append(
50
- Text.assemble(
51
- ("Visit ", "dim"),
52
- (
60
+ rows.append(
61
+ (
62
+ Text("Usage", style="dim"),
63
+ Text(
53
64
  "https://chatgpt.com/codex/settings/usage",
54
- "blue underline link https://chatgpt.com/codex/settings/usage",
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 elements
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 or api_key == "N/A":
65
- return "N/A"
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]} {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("N/A")
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("N/A")
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
- params: list[Text] = []
123
- if model.model_params.thinking:
124
- if model.model_params.thinking.reasoning_effort is not None:
125
- params.append(
126
- Text.assemble(
127
- ("reason-effort", ThemeKey.CONFIG_PARAM_LABEL),
128
- ": ",
129
- model.model_params.thinking.reasoning_effort,
130
- )
131
- )
132
- if model.model_params.thinking.reasoning_summary is not None:
133
- params.append(
134
- Text.assemble(
135
- ("reason-summary", ThemeKey.CONFIG_PARAM_LABEL),
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
- # Display each provider as a separate panel
167
- for provider in config.provider_list:
168
- # Provider info section
169
- provider_info = Table.grid(padding=(0, 1))
170
- provider_info.add_column(width=12)
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
- provider_info.add_row(
174
- Text("Protocol:", style=ThemeKey.CONFIG_PARAM_LABEL),
175
- Text(provider.protocol.value),
176
- )
177
- if provider.base_url:
178
- provider_info.add_row(
179
- Text("Base URL:", style=ThemeKey.CONFIG_PARAM_LABEL),
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
- # AWS Bedrock parameters
189
- if provider.protocol == LLMClientProtocol.BEDROCK:
190
- if provider.aws_access_key:
191
- provider_info.add_row(
192
- Text("AWS Key:", style=ThemeKey.CONFIG_PARAM_LABEL),
193
- format_env_var_display(provider.aws_access_key),
194
- )
195
- if provider.aws_secret_key:
196
- provider_info.add_row(
197
- Text("AWS Secret:", style=ThemeKey.CONFIG_PARAM_LABEL),
198
- format_env_var_display(provider.aws_secret_key),
199
- )
200
- if provider.aws_region:
201
- provider_info.add_row(
202
- Text("AWS Region:", style=ThemeKey.CONFIG_PARAM_LABEL),
203
- format_env_var_display(provider.aws_region),
204
- )
205
- if provider.aws_session_token:
206
- provider_info.add_row(
207
- Text("AWS Token:", style=ThemeKey.CONFIG_PARAM_LABEL),
208
- format_env_var_display(provider.aws_session_token),
209
- )
210
- if provider.aws_profile:
211
- provider_info.add_row(
212
- Text("AWS Profile:", style=ThemeKey.CONFIG_PARAM_LABEL),
213
- format_env_var_display(provider.aws_profile),
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
- # Check if provider has valid API key
217
- provider_available = not provider.is_api_key_missing()
294
+ models_table.add_row(name, model_id, params)
218
295
 
219
- # Models table for this provider
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
- console.print(panel)
279
- console.print()
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
- # Display main model info
282
- console.print()
312
+ # Default model
283
313
  if config.main_model:
284
- console.print(
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
- console.print(
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 not sub_model_name:
301
- continue
302
- console.print(
303
- Text.assemble(
304
- (f"{profile.name} Model: ", "bold"),
305
- (sub_model_name, ThemeKey.CONFIG_STATUS_PRIMARY),
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: codex
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: