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
|
@@ -88,7 +88,7 @@ class EditTool(ToolABC):
|
|
|
88
88
|
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
89
89
|
try:
|
|
90
90
|
args = EditTool.EditArguments.model_validate_json(arguments)
|
|
91
|
-
except
|
|
91
|
+
except ValueError as e: # pragma: no cover - defensive
|
|
92
92
|
return model.ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
93
93
|
|
|
94
94
|
file_path = os.path.abspath(args.file_path)
|
|
@@ -150,7 +150,7 @@ class EditTool(ToolABC):
|
|
|
150
150
|
# Backward-compat: old sessions only stored mtime.
|
|
151
151
|
try:
|
|
152
152
|
current_mtime = Path(file_path).stat().st_mtime
|
|
153
|
-
except
|
|
153
|
+
except OSError:
|
|
154
154
|
current_mtime = tracked_status.mtime
|
|
155
155
|
if current_mtime != tracked_status.mtime:
|
|
156
156
|
return model.ToolResultItem(
|
|
@@ -188,7 +188,7 @@ class EditTool(ToolABC):
|
|
|
188
188
|
# Write back
|
|
189
189
|
try:
|
|
190
190
|
await asyncio.to_thread(write_text, file_path, after)
|
|
191
|
-
except
|
|
191
|
+
except (OSError, UnicodeError) as e: # pragma: no cover
|
|
192
192
|
return model.ToolResultItem(status="error", output=f"<tool_use_error>{e}</tool_use_error>")
|
|
193
193
|
|
|
194
194
|
# Prepare UI extra: unified diff with 3 context lines
|
|
@@ -233,7 +233,7 @@ class EditTool(ToolABC):
|
|
|
233
233
|
plus_range = plus.split(" ")[0]
|
|
234
234
|
start = int(plus_range.split(",")[0]) if "," in plus_range else int(plus_range)
|
|
235
235
|
after_line_no = start - 1
|
|
236
|
-
except
|
|
236
|
+
except (ValueError, IndexError):
|
|
237
237
|
after_line_no = 0
|
|
238
238
|
continue
|
|
239
239
|
if line.startswith(" ") or (line.startswith("+") and not line.startswith("+++ ")):
|
|
@@ -49,7 +49,7 @@ class WriteTool(ToolABC):
|
|
|
49
49
|
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
50
50
|
try:
|
|
51
51
|
args = WriteArguments.model_validate_json(arguments)
|
|
52
|
-
except
|
|
52
|
+
except ValueError as e: # pragma: no cover - defensive
|
|
53
53
|
return model.ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
54
54
|
|
|
55
55
|
file_path = os.path.abspath(args.file_path)
|
|
@@ -79,7 +79,7 @@ class WriteTool(ToolABC):
|
|
|
79
79
|
try:
|
|
80
80
|
before = await asyncio.to_thread(read_text, file_path)
|
|
81
81
|
before_read_ok = True
|
|
82
|
-
except
|
|
82
|
+
except OSError:
|
|
83
83
|
before = ""
|
|
84
84
|
before_read_ok = False
|
|
85
85
|
|
|
@@ -98,7 +98,7 @@ class WriteTool(ToolABC):
|
|
|
98
98
|
# Backward-compat: old sessions only stored mtime, or we couldn't hash.
|
|
99
99
|
try:
|
|
100
100
|
current_mtime = Path(file_path).stat().st_mtime
|
|
101
|
-
except
|
|
101
|
+
except OSError:
|
|
102
102
|
current_mtime = tracked_status.mtime
|
|
103
103
|
if current_mtime != tracked_status.mtime:
|
|
104
104
|
return model.ToolResultItem(
|
|
@@ -111,7 +111,7 @@ class WriteTool(ToolABC):
|
|
|
111
111
|
|
|
112
112
|
try:
|
|
113
113
|
await asyncio.to_thread(write_text, file_path, args.content)
|
|
114
|
-
except
|
|
114
|
+
except (OSError, UnicodeError) as e: # pragma: no cover
|
|
115
115
|
return model.ToolResultItem(status="error", output=f"<tool_use_error>{e}</tool_use_error>")
|
|
116
116
|
|
|
117
117
|
if file_tracker is not None:
|
|
@@ -274,7 +274,7 @@ class BashTool(ToolABC):
|
|
|
274
274
|
proc.terminate()
|
|
275
275
|
except ProcessLookupError:
|
|
276
276
|
return
|
|
277
|
-
except
|
|
277
|
+
except OSError:
|
|
278
278
|
# Fall back to kill below.
|
|
279
279
|
pass
|
|
280
280
|
|
|
@@ -356,7 +356,7 @@ class BashTool(ToolABC):
|
|
|
356
356
|
except asyncio.CancelledError:
|
|
357
357
|
# Propagate cooperative cancellation so outer layers can handle interrupts correctly.
|
|
358
358
|
raise
|
|
359
|
-
except
|
|
359
|
+
except OSError as e: # safeguard: catch remaining OS-level errors (permissions, resources, etc.)
|
|
360
360
|
return model.ToolResultItem(
|
|
361
361
|
status="error",
|
|
362
362
|
output=f"Execution error: {e}",
|
|
@@ -20,6 +20,7 @@ from typing import Any, Literal, cast
|
|
|
20
20
|
import httpx
|
|
21
21
|
import openai
|
|
22
22
|
import openai.types
|
|
23
|
+
import pydantic
|
|
23
24
|
from openai import AsyncStream
|
|
24
25
|
from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
|
|
25
26
|
|
|
@@ -204,7 +205,7 @@ async def parse_chat_completions_stream(
|
|
|
204
205
|
try:
|
|
205
206
|
usage = openai.types.CompletionUsage.model_validate(choice_usage)
|
|
206
207
|
metadata_tracker.set_usage(convert_usage(usage, param.context_limit, param.max_tokens))
|
|
207
|
-
except
|
|
208
|
+
except pydantic.ValidationError:
|
|
208
209
|
pass
|
|
209
210
|
|
|
210
211
|
delta = cast(Any, getattr(choice0, "delta", None))
|
klaude_code/session/export.py
CHANGED
klaude_code/session/selector.py
CHANGED
|
@@ -23,7 +23,7 @@ def resume_select_session() -> str | None:
|
|
|
23
23
|
def _fmt(ts: float) -> str:
|
|
24
24
|
try:
|
|
25
25
|
return time.strftime("%m-%d %H:%M:%S", time.localtime(ts))
|
|
26
|
-
except
|
|
26
|
+
except (ValueError, OSError):
|
|
27
27
|
return str(ts)
|
|
28
28
|
|
|
29
29
|
try:
|
|
@@ -76,6 +76,6 @@ def resume_select_session() -> str | None:
|
|
|
76
76
|
idx = int(raw)
|
|
77
77
|
if 1 <= idx <= len(sessions):
|
|
78
78
|
return str(sessions[idx - 1].id)
|
|
79
|
-
except
|
|
79
|
+
except (ValueError, EOFError):
|
|
80
80
|
return None
|
|
81
81
|
return None
|
klaude_code/session/session.py
CHANGED
|
@@ -7,7 +7,7 @@ from collections.abc import Iterable, Sequence
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, cast
|
|
9
9
|
|
|
10
|
-
from pydantic import BaseModel, Field, PrivateAttr
|
|
10
|
+
from pydantic import BaseModel, Field, PrivateAttr, ValidationError
|
|
11
11
|
|
|
12
12
|
from klaude_code.protocol import events, llm_param, model, tools
|
|
13
13
|
from klaude_code.session.store import JsonlSessionStore, ProjectPaths, build_meta_snapshot
|
|
@@ -124,7 +124,7 @@ class Session(BaseModel):
|
|
|
124
124
|
if isinstance(k, str) and isinstance(v, dict):
|
|
125
125
|
try:
|
|
126
126
|
file_tracker[k] = model.FileStatus.model_validate(v)
|
|
127
|
-
except
|
|
127
|
+
except ValidationError:
|
|
128
128
|
continue
|
|
129
129
|
|
|
130
130
|
todos_raw = raw.get("todos")
|
|
@@ -135,7 +135,7 @@ class Session(BaseModel):
|
|
|
135
135
|
continue
|
|
136
136
|
try:
|
|
137
137
|
todos.append(model.TodoItem.model_validate(todo_raw))
|
|
138
|
-
except
|
|
138
|
+
except ValidationError:
|
|
139
139
|
continue
|
|
140
140
|
|
|
141
141
|
created_at = float(raw.get("created_at", time.time()))
|
|
@@ -306,7 +306,7 @@ class Session(BaseModel):
|
|
|
306
306
|
seen_sub_agent_sessions.add(session_id)
|
|
307
307
|
try:
|
|
308
308
|
sub_session = Session.load(session_id)
|
|
309
|
-
except
|
|
309
|
+
except (OSError, json.JSONDecodeError, ValueError):
|
|
310
310
|
return
|
|
311
311
|
yield from sub_session.get_history_item()
|
|
312
312
|
|
|
@@ -204,7 +204,7 @@ class _SkillCompleter(Completer):
|
|
|
204
204
|
from klaude_code.skill import get_available_skills
|
|
205
205
|
|
|
206
206
|
return get_available_skills()
|
|
207
|
-
except
|
|
207
|
+
except (ImportError, RuntimeError):
|
|
208
208
|
return []
|
|
209
209
|
|
|
210
210
|
def is_skill_context(self, document: Document) -> bool:
|
|
@@ -497,7 +497,7 @@ class _AtFilesCompleter(Completer):
|
|
|
497
497
|
try:
|
|
498
498
|
if (cwd / s).is_dir():
|
|
499
499
|
uniq[idx] = f"{s}/"
|
|
500
|
-
except
|
|
500
|
+
except OSError:
|
|
501
501
|
continue
|
|
502
502
|
return uniq
|
|
503
503
|
|
|
@@ -530,7 +530,7 @@ class _AtFilesCompleter(Completer):
|
|
|
530
530
|
if tag != "search":
|
|
531
531
|
return root, None
|
|
532
532
|
return root, kw
|
|
533
|
-
except
|
|
533
|
+
except ValueError:
|
|
534
534
|
return None, None
|
|
535
535
|
|
|
536
536
|
# ---- Utilities ----
|
|
@@ -680,7 +680,7 @@ class _AtFilesCompleter(Completer):
|
|
|
680
680
|
if p.is_dir() and not rel.endswith("/"):
|
|
681
681
|
rel += "/"
|
|
682
682
|
items.append(rel)
|
|
683
|
-
except
|
|
683
|
+
except OSError:
|
|
684
684
|
return []
|
|
685
685
|
return items[: min(self._max_results, 100)]
|
|
686
686
|
|
|
@@ -246,7 +246,7 @@ class SpinnerStatusState:
|
|
|
246
246
|
"""Get current spinner status as rich Text (without context)."""
|
|
247
247
|
activity_text = self._activity.get_activity_text()
|
|
248
248
|
|
|
249
|
-
base_status = self.
|
|
249
|
+
base_status = self._reasoning_status or self._todo_status
|
|
250
250
|
|
|
251
251
|
if base_status:
|
|
252
252
|
result = Text(base_status, style=ThemeKey.STATUS_TEXT_BOLD)
|
|
@@ -118,7 +118,7 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
118
118
|
try:
|
|
119
119
|
status = self._status_provider()
|
|
120
120
|
update_message = status.update_message
|
|
121
|
-
except
|
|
121
|
+
except (AttributeError, RuntimeError):
|
|
122
122
|
pass
|
|
123
123
|
|
|
124
124
|
# If update available, show only the update message
|
|
@@ -127,7 +127,7 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
127
127
|
try:
|
|
128
128
|
terminal_width = shutil.get_terminal_size().columns
|
|
129
129
|
padding = " " * max(0, terminal_width - len(left_text))
|
|
130
|
-
except
|
|
130
|
+
except (OSError, ValueError):
|
|
131
131
|
padding = ""
|
|
132
132
|
toolbar_text = left_text + padding
|
|
133
133
|
return FormattedText([("#ansiyellow", toolbar_text)])
|
|
@@ -151,7 +151,7 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
151
151
|
# Add context if available
|
|
152
152
|
if status.context_usage_percent is not None:
|
|
153
153
|
right_parts.append(f"context {status.context_usage_percent:.1f}%")
|
|
154
|
-
except
|
|
154
|
+
except (AttributeError, RuntimeError):
|
|
155
155
|
pass
|
|
156
156
|
|
|
157
157
|
# Build left and right text with borders
|
|
@@ -163,7 +163,7 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
163
163
|
terminal_width = shutil.get_terminal_size().columns
|
|
164
164
|
used_width = len(left_text) + len(right_text)
|
|
165
165
|
padding = " " * max(0, terminal_width - used_width)
|
|
166
|
-
except
|
|
166
|
+
except (OSError, ValueError):
|
|
167
167
|
padding = ""
|
|
168
168
|
|
|
169
169
|
# Build result with style
|
|
@@ -52,7 +52,7 @@ def create_key_bindings(
|
|
|
52
52
|
buf.delete_before_cursor() # remove the sentinel backslash # type: ignore[reportUnknownMemberType]
|
|
53
53
|
buf.insert_text("\n") # type: ignore[reportUnknownMemberType]
|
|
54
54
|
return
|
|
55
|
-
except
|
|
55
|
+
except (AttributeError, TypeError):
|
|
56
56
|
# Fall through to default behavior if anything goes wrong
|
|
57
57
|
pass
|
|
58
58
|
|
|
@@ -111,7 +111,7 @@ def create_key_bindings(
|
|
|
111
111
|
|
|
112
112
|
if should_refresh:
|
|
113
113
|
buf.start_completion(select_first=False) # type: ignore[reportUnknownMemberType]
|
|
114
|
-
except
|
|
114
|
+
except (AttributeError, TypeError):
|
|
115
115
|
pass
|
|
116
116
|
|
|
117
117
|
@kb.add("left")
|
|
@@ -136,7 +136,7 @@ def create_key_bindings(
|
|
|
136
136
|
# Default behavior: move one character left when possible.
|
|
137
137
|
if doc.cursor_position > 0: # type: ignore[reportUnknownMemberType]
|
|
138
138
|
buf.cursor_left() # type: ignore[reportUnknownMemberType]
|
|
139
|
-
except
|
|
139
|
+
except (AttributeError, IndexError, TypeError):
|
|
140
140
|
pass
|
|
141
141
|
|
|
142
142
|
@kb.add("right")
|
|
@@ -163,7 +163,7 @@ def create_key_bindings(
|
|
|
163
163
|
# Default behavior: move one character right when possible.
|
|
164
164
|
if doc.cursor_position < len(doc.text): # type: ignore[reportUnknownMemberType]
|
|
165
165
|
buf.cursor_right() # type: ignore[reportUnknownMemberType]
|
|
166
|
-
except
|
|
166
|
+
except (AttributeError, IndexError, TypeError):
|
|
167
167
|
pass
|
|
168
168
|
|
|
169
169
|
return kb
|
|
@@ -148,7 +148,7 @@ def render_diff(diff_text: str, show_file_name: bool = False) -> RenderableType:
|
|
|
148
148
|
plus = parts[2] # like '+12,4'
|
|
149
149
|
new_start = int(plus[1:].split(",")[0])
|
|
150
150
|
new_ln = new_start
|
|
151
|
-
except
|
|
151
|
+
except (IndexError, ValueError):
|
|
152
152
|
new_ln = None
|
|
153
153
|
if has_rendered_diff_content:
|
|
154
154
|
grid.add_row(Text(f"{'⋮':>{const.DIFF_PREFIX_WIDTH}}", style=ThemeKey.TOOL_RESULT), "")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from importlib.metadata import version
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
2
|
|
|
3
3
|
from rich import box
|
|
4
4
|
from rich.console import Group, RenderableType
|
|
@@ -17,7 +17,7 @@ def _get_version() -> str:
|
|
|
17
17
|
"""Get the current version of klaude-code."""
|
|
18
18
|
try:
|
|
19
19
|
return version("klaude-code")
|
|
20
|
-
except
|
|
20
|
+
except PackageNotFoundError:
|
|
21
21
|
return "unknown"
|
|
22
22
|
|
|
23
23
|
|
klaude_code/ui/rich/markdown.py
CHANGED
|
@@ -192,7 +192,7 @@ class MarkdownStream:
|
|
|
192
192
|
|
|
193
193
|
try:
|
|
194
194
|
tokens = self._parser.parse(text)
|
|
195
|
-
except Exception:
|
|
195
|
+
except Exception: # markdown-it-py may raise various internal errors during parsing
|
|
196
196
|
return 0
|
|
197
197
|
|
|
198
198
|
top_level: list[Token] = [token for token in tokens if token.level == 0 and token.map is not None]
|
klaude_code/ui/rich/theme.py
CHANGED
|
@@ -276,7 +276,7 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
276
276
|
ThemeKey.RESUME_FLAG.value: "bold reverse " + palette.green,
|
|
277
277
|
ThemeKey.RESUME_INFO.value: palette.green,
|
|
278
278
|
# CONFIGURATION DISPLAY
|
|
279
|
-
ThemeKey.CONFIG_TABLE_HEADER.value: palette.
|
|
279
|
+
ThemeKey.CONFIG_TABLE_HEADER.value: palette.grey1,
|
|
280
280
|
ThemeKey.CONFIG_STATUS_OK.value: palette.green,
|
|
281
281
|
ThemeKey.CONFIG_STATUS_PRIMARY.value: palette.yellow,
|
|
282
282
|
ThemeKey.CONFIG_STATUS_ERROR.value: palette.red,
|
klaude_code/ui/terminal/color.py
CHANGED
|
@@ -182,7 +182,7 @@ def _parse_osc_color_response(data: bytes) -> tuple[int, int, int] | None:
|
|
|
182
182
|
|
|
183
183
|
try:
|
|
184
184
|
text = data.decode("ascii", errors="ignore")
|
|
185
|
-
except
|
|
185
|
+
except LookupError: # encoding lookup failure (should not happen with "ascii")
|
|
186
186
|
return None
|
|
187
187
|
|
|
188
188
|
match = _OSC_BG_REGEX.search(text)
|
|
@@ -48,7 +48,7 @@ def start_esc_interrupt_monitor(
|
|
|
48
48
|
try:
|
|
49
49
|
fd = sys.stdin.fileno()
|
|
50
50
|
old = termios.tcgetattr(fd)
|
|
51
|
-
except
|
|
51
|
+
except OSError as exc: # pragma: no cover - environment dependent
|
|
52
52
|
log((f"esc monitor init error: {exc}", "r red"))
|
|
53
53
|
return
|
|
54
54
|
|
|
@@ -60,7 +60,7 @@ def start_esc_interrupt_monitor(
|
|
|
60
60
|
continue
|
|
61
61
|
try:
|
|
62
62
|
ch = os.read(fd, 1).decode(errors="ignore")
|
|
63
|
-
except
|
|
63
|
+
except OSError:
|
|
64
64
|
continue
|
|
65
65
|
if ch != "\x1b":
|
|
66
66
|
continue
|
|
@@ -71,7 +71,7 @@ def start_esc_interrupt_monitor(
|
|
|
71
71
|
while r2:
|
|
72
72
|
try:
|
|
73
73
|
seq += os.read(fd, 1).decode(errors="ignore")
|
|
74
|
-
except
|
|
74
|
+
except OSError:
|
|
75
75
|
break
|
|
76
76
|
r2, _, _ = select.select([sys.stdin], [], [], 0.0)
|
|
77
77
|
|
|
@@ -127,7 +127,7 @@ def install_sigint_double_press_exit(
|
|
|
127
127
|
|
|
128
128
|
try:
|
|
129
129
|
signal.signal(signal.SIGINT, _handler)
|
|
130
|
-
except
|
|
130
|
+
except (OSError, ValueError): # pragma: no cover - platform dependent
|
|
131
131
|
# If installing the handler fails, restore() will be a no-op.
|
|
132
132
|
return lambda: None
|
|
133
133
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 1.2.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.2.27
|
|
4
|
+
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
7
7
|
Requires-Dist: ddgs>=9.9.3
|
|
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
|
|
22
22
|
# Klaude Code
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Minimal code agent CLI.
|
|
25
25
|
|
|
26
26
|
## Features
|
|
27
27
|
- **Multi-provider**: Anthropic, OpenAI Responses API, OpenRouter
|
|
@@ -92,147 +92,141 @@ klaude [--model <name>] [--select-model]
|
|
|
92
92
|
|
|
93
93
|
### Configuration
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
#### Quick Start (Zero Config)
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
Klaude comes with built-in provider configurations. Just set an API key environment variable and start using it:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Pick one (or more) of these:
|
|
101
|
+
export ANTHROPIC_API_KEY=sk-ant-xxx # Claude models
|
|
102
|
+
export OPENAI_API_KEY=sk-xxx # GPT models
|
|
103
|
+
export OPENROUTER_API_KEY=sk-or-xxx # OpenRouter (multi-provider)
|
|
104
|
+
export DEEPSEEK_API_KEY=sk-xxx # DeepSeek models
|
|
105
|
+
export MOONSHOT_API_KEY=sk-xxx # Moonshot/Kimi models
|
|
106
|
+
|
|
107
|
+
# Then just run:
|
|
108
|
+
klaude
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
On first run, you'll be prompted to select a model. Your choice is saved as `main_model`.
|
|
112
|
+
|
|
113
|
+
#### Built-in Providers
|
|
114
|
+
|
|
115
|
+
| Provider | Env Variable | Models |
|
|
116
|
+
|-------------|-----------------------|-------------------------------------------------------------------------------|
|
|
117
|
+
| anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
|
|
118
|
+
| openai | `OPENAI_API_KEY` | gpt-5.2 |
|
|
119
|
+
| openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
|
|
120
|
+
| deepseek | `DEEPSEEK_API_KEY` | deepseek |
|
|
121
|
+
| moonshot | `MOONSHOT_API_KEY` | kimi@moonshot |
|
|
122
|
+
| codex | N/A (OAuth) | gpt-5.2-codex |
|
|
123
|
+
|
|
124
|
+
List all configured providers and models:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
klaude list
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Models from providers without a valid API key are shown as dimmed/unavailable.
|
|
131
|
+
|
|
132
|
+
#### Custom Configuration
|
|
133
|
+
|
|
134
|
+
User config file: `~/.klaude/klaude-config.yaml`
|
|
135
|
+
|
|
136
|
+
Open in editor:
|
|
98
137
|
|
|
99
138
|
```bash
|
|
100
139
|
klaude config
|
|
101
140
|
```
|
|
102
141
|
|
|
103
|
-
|
|
142
|
+
##### Adding Models to Built-in Providers
|
|
143
|
+
|
|
144
|
+
You can add custom models to existing providers without redefining the entire provider:
|
|
104
145
|
|
|
105
146
|
```yaml
|
|
147
|
+
# Just specify provider_name and your new models - no need for protocol/api_key
|
|
106
148
|
provider_list:
|
|
107
|
-
- provider_name: openrouter
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
-
|
|
116
|
-
protocol: anthropic
|
|
117
|
-
api_key: <your-anthropic-api-key>
|
|
118
|
-
|
|
119
|
-
- provider_name: moonshot
|
|
120
|
-
protocol: anthropic
|
|
121
|
-
base_url: https://api.moonshot.cn/anthropic
|
|
122
|
-
api_key: <your-api-key>
|
|
123
|
-
|
|
124
|
-
- provider_name: deepseek
|
|
125
|
-
protocol: anthropic
|
|
126
|
-
base_url: https://api.deepseek.com/anthropic
|
|
127
|
-
api_key: <your-api-key>
|
|
128
|
-
|
|
129
|
-
model_list:
|
|
130
|
-
|
|
131
|
-
- model_name: deepseek
|
|
132
|
-
provider: deepseek
|
|
133
|
-
model_params:
|
|
134
|
-
model: deepseek-reasoner
|
|
135
|
-
context_limit: 128000
|
|
136
|
-
thinking:
|
|
137
|
-
type: enabled
|
|
138
|
-
budget_tokens: 8192
|
|
139
|
-
cost:
|
|
140
|
-
currency: CNY
|
|
141
|
-
input: 2
|
|
142
|
-
output: 3
|
|
143
|
-
cache_read: 0.2
|
|
144
|
-
|
|
145
|
-
- model_name: codex-max
|
|
146
|
-
provider: openai-responses
|
|
147
|
-
model_params:
|
|
148
|
-
model: gpt-5.1-codex-max
|
|
149
|
-
thinking:
|
|
150
|
-
reasoning_effort: medium
|
|
151
|
-
context_limit: 400000
|
|
152
|
-
max_tokens: 128000
|
|
153
|
-
cost:
|
|
154
|
-
input: 1.25
|
|
155
|
-
output: 10
|
|
156
|
-
cache_read: 0.13
|
|
157
|
-
|
|
158
|
-
- model_name: gpt-5.1
|
|
159
|
-
provider: openrouter
|
|
160
|
-
model_params:
|
|
161
|
-
model: openai/gpt-5.1
|
|
162
|
-
context_limit: 400000
|
|
163
|
-
max_tokens: 128000
|
|
164
|
-
verbosity: high
|
|
165
|
-
thinking:
|
|
166
|
-
reasoning_effort: high
|
|
167
|
-
cost:
|
|
168
|
-
input: 1.25
|
|
169
|
-
output: 10
|
|
170
|
-
cache_read: 0.13
|
|
171
|
-
|
|
172
|
-
- model_name: kimi@moonshot
|
|
173
|
-
provider: moonshot
|
|
174
|
-
model_params:
|
|
175
|
-
model: kimi-k2-thinking
|
|
176
|
-
context_limit: 262144
|
|
177
|
-
thinking:
|
|
178
|
-
type: enabled
|
|
179
|
-
budget_tokens: 8192
|
|
180
|
-
cost:
|
|
181
|
-
currency: CNY
|
|
182
|
-
input: 4
|
|
183
|
-
output: 16
|
|
184
|
-
cache_read: 1
|
|
185
|
-
|
|
186
|
-
- model_name: opus
|
|
187
|
-
provider: openrouter
|
|
188
|
-
model_params:
|
|
189
|
-
model: anthropic/claude-4.5-opus
|
|
190
|
-
context_limit: 200000
|
|
191
|
-
provider_routing:
|
|
192
|
-
only: [ google-vertex ]
|
|
193
|
-
verbosity: high
|
|
194
|
-
thinking:
|
|
195
|
-
type: enabled
|
|
196
|
-
budget_tokens: 31999
|
|
197
|
-
cost:
|
|
198
|
-
input: 5
|
|
199
|
-
output: 25
|
|
200
|
-
cache_read: 0.5
|
|
201
|
-
cache_write: 6.25
|
|
202
|
-
|
|
203
|
-
- model_name: gemini
|
|
204
|
-
provider: openrouter
|
|
205
|
-
model_params:
|
|
206
|
-
model: google/gemini-3-pro-preview
|
|
207
|
-
context_limit: 1048576
|
|
208
|
-
thinking:
|
|
209
|
-
reasoning_effort: medium
|
|
210
|
-
cost:
|
|
211
|
-
input: 2
|
|
212
|
-
output: 12
|
|
213
|
-
cache_read: 0.2
|
|
214
|
-
|
|
215
|
-
- model_name: haiku
|
|
216
|
-
provider: anthropic
|
|
217
|
-
model_params:
|
|
218
|
-
model: claude-haiku-4-5-20251001
|
|
219
|
-
context_limit: 200000
|
|
220
|
-
cost:
|
|
221
|
-
input: 1
|
|
222
|
-
output: 5
|
|
223
|
-
cache_read: 0.1
|
|
224
|
-
cache_write: 1.25
|
|
149
|
+
- provider_name: openrouter
|
|
150
|
+
model_list:
|
|
151
|
+
- model_name: my-custom-model
|
|
152
|
+
model_params:
|
|
153
|
+
model: some-provider/some-model-id
|
|
154
|
+
context_limit: 200000
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Your models are merged with built-in models. To override a built-in model, use the same `model_name`.
|
|
225
158
|
|
|
159
|
+
##### Overriding Provider Settings
|
|
160
|
+
|
|
161
|
+
Override provider-level settings (like api_key) while keeping built-in models:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
provider_list:
|
|
165
|
+
- provider_name: anthropic
|
|
166
|
+
api_key: sk-my-custom-key # Override the default ${ANTHROPIC_API_KEY}
|
|
167
|
+
# Built-in models (sonnet, opus) are still available
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
##### Adding New Providers
|
|
171
|
+
|
|
172
|
+
For providers not in the built-in list, you must specify `protocol`:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
provider_list:
|
|
176
|
+
- provider_name: my-azure-openai
|
|
177
|
+
protocol: openai
|
|
178
|
+
api_key: ${AZURE_OPENAI_KEY}
|
|
179
|
+
base_url: https://my-instance.openai.azure.com/
|
|
180
|
+
is_azure: true
|
|
181
|
+
azure_api_version: "2024-02-15-preview"
|
|
182
|
+
model_list:
|
|
183
|
+
- model_name: gpt-4-azure
|
|
184
|
+
model_params:
|
|
185
|
+
model: gpt-4
|
|
186
|
+
context_limit: 128000
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
##### Full Example
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
# User configuration - merged with built-in config
|
|
226
193
|
main_model: opus
|
|
227
194
|
|
|
228
195
|
sub_agent_models:
|
|
229
|
-
oracle: gpt-
|
|
230
|
-
explore:
|
|
196
|
+
oracle: gpt-4.1
|
|
197
|
+
explore: sonnet
|
|
231
198
|
task: opus
|
|
232
|
-
webagent:
|
|
199
|
+
webagent: sonnet
|
|
233
200
|
|
|
201
|
+
provider_list:
|
|
202
|
+
# Add models to built-in openrouter
|
|
203
|
+
- provider_name: openrouter
|
|
204
|
+
model_list:
|
|
205
|
+
- model_name: qwen-coder
|
|
206
|
+
model_params:
|
|
207
|
+
model: qwen/qwen-2.5-coder-32b-instruct
|
|
208
|
+
context_limit: 131072
|
|
209
|
+
|
|
210
|
+
# Add a completely new provider
|
|
211
|
+
- provider_name: local-ollama
|
|
212
|
+
protocol: openai
|
|
213
|
+
base_url: http://localhost:11434/v1
|
|
214
|
+
api_key: ollama
|
|
215
|
+
model_list:
|
|
216
|
+
- model_name: local-llama
|
|
217
|
+
model_params:
|
|
218
|
+
model: llama3.2
|
|
219
|
+
context_limit: 8192
|
|
234
220
|
```
|
|
235
221
|
|
|
222
|
+
##### Supported Protocols
|
|
223
|
+
|
|
224
|
+
- `anthropic` - Anthropic Claude API
|
|
225
|
+
- `openai` - OpenAI-compatible API
|
|
226
|
+
- `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
|
|
227
|
+
- `openrouter` - OpenRouter API
|
|
228
|
+
- `codex` - OpenAI Codex CLI (OAuth-based)
|
|
229
|
+
|
|
236
230
|
List configured providers and models:
|
|
237
231
|
|
|
238
232
|
```bash
|