fix(kimi): support API-key setup for Kimi Code

This commit is contained in:
Hunter Bown
2026-05-25 23:39:34 -05:00
parent 2f3750b850
commit ab38635f78
11 changed files with 248 additions and 36 deletions
+9 -3
View File
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- **Kimi Code API-key setup.** `codewhale config set providers.moonshot.*`
now writes the Moonshot/Kimi provider table, and Kimi Code API-key
endpoints default to `kimi-for-coding` without using the Kimi CLI OAuth path.
## [0.8.45] - 2026-05-25
### Added
@@ -17,9 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Command palette voice input.** The command palette can launch a configured
speech-to-text helper and show footer status while transcription runs
(#2047).
- **Moonshot/Kimi OAuth provider.** Moonshot/Kimi is now a first-class
provider, including Kimi CLI OAuth reuse, secure refresh writes, model
completion, CLI auth, and secret-store integration.
- **Moonshot/Kimi provider.** Moonshot/Kimi is now a first-class provider,
including API-key auth, model completion, CLI auth, secret-store
integration, and optional Kimi CLI credential reuse.
- **Deterministic whale-species sub-agent names.** Sub-agents now get stable,
human-readable whale-species nicknames (e.g. "Beluga", "Orca") while
preserving the raw agent ID in the popup (#2035, #2016).
+17 -9
View File
@@ -314,15 +314,23 @@ codewhale --provider novita --model deepseek/deepseek-v4-pro
codewhale auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"
codewhale --provider fireworks --model deepseek-v4-pro
# Moonshot/Kimi
codewhale auth set --provider moonshot --api-key "YOUR_MOONSHOT_OR_KIMI_API_KEY"
codewhale --provider moonshot --model kimi-k2.6
# Kimi Code plan API key
codewhale auth set --provider moonshot --api-key "YOUR_KIMI_CODE_API_KEY"
codewhale config set providers.moonshot.auth_mode api_key
codewhale config set providers.moonshot.base_url https://api.kimi.com/coding/v1
codewhale config set providers.moonshot.model kimi-for-coding
codewhale --provider moonshot
# Moonshot/Kimi with Kimi CLI OAuth
kimi login
mkdir -p ~/.deepseek
printf 'provider = "moonshot"\n\n[providers.moonshot]\nauth_mode = "kimi_oauth"\n' >> ~/.deepseek/config.toml
codewhale --provider moonshot --model kimi-for-coding
# Kimi/Moonshot Platform API key
codewhale auth set --provider moonshot --api-key "YOUR_MOONSHOT_OR_KIMI_API_KEY"
codewhale config set providers.moonshot.auth_mode api_key
codewhale config set providers.moonshot.base_url https://api.moonshot.ai/v1
codewhale config set providers.moonshot.model kimi-k2.6
codewhale --provider moonshot
# Kimi through OpenRouter's catalog
codewhale auth set --provider openrouter --api-key "YOUR_OPENROUTER_API_KEY"
codewhale --provider openrouter --model moonshotai/kimi-k2.6
# Self-hosted SGLang
SGLANG_BASE_URL="http://localhost:30000/v1" codewhale --provider sglang --model deepseek-v4-flash
@@ -512,7 +520,7 @@ Key environment variables:
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | Generic OpenAI-compatible endpoint and model ID |
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud endpoint and model override |
| `WANJIE_ARK_BASE_URL` / `WANJIE_BASE_URL` / `WANJIE_MAAS_BASE_URL` / `WANJIE_ARK_MODEL` / `WANJIE_MODEL` / `WANJIE_MAAS_MODEL` | Wanjie Ark endpoint and model override |
| `MOONSHOT_BASE_URL` / `KIMI_BASE_URL` / `MOONSHOT_MODEL` / `KIMI_MODEL_NAME` / `KIMI_MODEL` | Moonshot/Kimi endpoint and model override |
| `MOONSHOT_BASE_URL` / `KIMI_BASE_URL` / `MOONSHOT_MODEL` / `KIMI_MODEL_NAME` / `KIMI_MODEL` | Moonshot/Kimi endpoint and model override. For a Kimi Code plan API key, use `KIMI_BASE_URL=https://api.kimi.com/coding/v1` and `KIMI_MODEL=kimi-for-coding`. |
| `OPENROUTER_BASE_URL` | OpenRouter endpoint override |
| `NOVITA_BASE_URL` | Novita endpoint override |
| `FIREWORKS_BASE_URL` | Fireworks endpoint override |
+121 -1
View File
@@ -462,6 +462,13 @@ impl ConfigToml {
"providers.fireworks.http_headers" => {
serialize_http_headers(&self.providers.fireworks.http_headers)
}
"providers.moonshot.api_key" => self.providers.moonshot.api_key.clone(),
"providers.moonshot.base_url" => self.providers.moonshot.base_url.clone(),
"providers.moonshot.model" => self.providers.moonshot.model.clone(),
"providers.moonshot.auth_mode" => self.providers.moonshot.auth_mode.clone(),
"providers.moonshot.http_headers" => {
serialize_http_headers(&self.providers.moonshot.http_headers)
}
"providers.sglang.api_key" => self.providers.sglang.api_key.clone(),
"providers.sglang.base_url" => self.providers.sglang.base_url.clone(),
"providers.sglang.model" => self.providers.sglang.model.clone(),
@@ -612,6 +619,21 @@ impl ConfigToml {
"providers.fireworks.http_headers" => {
self.providers.fireworks.http_headers = parse_http_headers(value)?;
}
"providers.moonshot.api_key" => {
self.providers.moonshot.api_key = Some(value.to_string());
}
"providers.moonshot.base_url" => {
self.providers.moonshot.base_url = Some(value.to_string());
}
"providers.moonshot.model" => {
self.providers.moonshot.model = Some(value.to_string());
}
"providers.moonshot.auth_mode" => {
self.providers.moonshot.auth_mode = Some(value.to_string());
}
"providers.moonshot.http_headers" => {
self.providers.moonshot.http_headers = parse_http_headers(value)?;
}
"providers.sglang.api_key" => {
self.providers.sglang.api_key = Some(value.to_string());
}
@@ -716,6 +738,11 @@ impl ConfigToml {
"providers.fireworks.base_url" => self.providers.fireworks.base_url = None,
"providers.fireworks.model" => self.providers.fireworks.model = None,
"providers.fireworks.http_headers" => self.providers.fireworks.http_headers.clear(),
"providers.moonshot.api_key" => self.providers.moonshot.api_key = None,
"providers.moonshot.base_url" => self.providers.moonshot.base_url = None,
"providers.moonshot.model" => self.providers.moonshot.model = None,
"providers.moonshot.auth_mode" => self.providers.moonshot.auth_mode = None,
"providers.moonshot.http_headers" => self.providers.moonshot.http_headers.clear(),
"providers.sglang.api_key" => self.providers.sglang.api_key = None,
"providers.sglang.base_url" => self.providers.sglang.base_url = None,
"providers.sglang.model" => self.providers.sglang.model = None,
@@ -869,6 +896,21 @@ impl ConfigToml {
if let Some(v) = serialize_http_headers(&self.providers.fireworks.http_headers) {
out.insert("providers.fireworks.http_headers".to_string(), v);
}
if let Some(v) = self.providers.moonshot.api_key.as_ref() {
out.insert("providers.moonshot.api_key".to_string(), redact_secret(v));
}
if let Some(v) = self.providers.moonshot.base_url.as_ref() {
out.insert("providers.moonshot.base_url".to_string(), v.clone());
}
if let Some(v) = self.providers.moonshot.model.as_ref() {
out.insert("providers.moonshot.model".to_string(), v.clone());
}
if let Some(v) = self.providers.moonshot.auth_mode.as_ref() {
out.insert("providers.moonshot.auth_mode".to_string(), v.clone());
}
if let Some(v) = serialize_http_headers(&self.providers.moonshot.http_headers) {
out.insert("providers.moonshot.http_headers".to_string(), v);
}
if let Some(v) = self.providers.sglang.api_key.as_ref() {
out.insert("providers.sglang.api_key".to_string(), redact_secret(v));
}
@@ -1028,7 +1070,8 @@ impl ConfigToml {
.or_else(|| self.model.clone())
.unwrap_or_else(|| {
if provider == ProviderKind::Moonshot
&& auth_mode.as_deref().is_some_and(auth_mode_uses_kimi_oauth)
&& (auth_mode.as_deref().is_some_and(auth_mode_uses_kimi_oauth)
|| moonshot_base_url_uses_kimi_code(&base_url))
{
DEFAULT_KIMI_CODE_MODEL.to_string()
} else {
@@ -1257,6 +1300,13 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str {
}
}
fn moonshot_base_url_uses_kimi_code(base_url: &str) -> bool {
let normalized = base_url.trim_end_matches('/').to_ascii_lowercase();
normalized == DEFAULT_KIMI_CODE_BASE_URL
|| normalized == "https://api.kimi.com/coding"
|| normalized.starts_with("https://api.kimi.com/coding/")
}
fn base_url_is_custom_for_provider(provider: ProviderKind, base_url: &str) -> bool {
let actual = base_url.trim_end_matches('/');
let default = default_base_url_for_provider(provider).trim_end_matches('/');
@@ -2358,6 +2408,52 @@ mod tests {
);
}
#[test]
fn moonshot_provider_config_values_round_trip() -> Result<()> {
let mut config = ConfigToml::default();
config.set_value("providers.moonshot.api_key", "moonshot-secret-value")?;
config.set_value("providers.moonshot.base_url", DEFAULT_KIMI_CODE_BASE_URL)?;
config.set_value("providers.moonshot.model", DEFAULT_KIMI_CODE_MODEL)?;
config.set_value("providers.moonshot.auth_mode", "api_key")?;
config.set_value("providers.moonshot.http_headers", "X-Test=ok")?;
assert_eq!(
config
.get_display_value("providers.moonshot.api_key")
.as_deref(),
Some("moon***alue")
);
assert_eq!(
config.get_value("providers.moonshot.base_url").as_deref(),
Some(DEFAULT_KIMI_CODE_BASE_URL)
);
assert_eq!(
config.get_value("providers.moonshot.model").as_deref(),
Some(DEFAULT_KIMI_CODE_MODEL)
);
assert_eq!(
config.get_value("providers.moonshot.auth_mode").as_deref(),
Some("api_key")
);
assert_eq!(
config
.list_values()
.get("providers.moonshot.api_key")
.map(String::as_str),
Some("moon***alue")
);
config.unset_value("providers.moonshot.auth_mode")?;
config.unset_value("providers.moonshot.base_url")?;
config.unset_value("providers.moonshot.model")?;
assert_eq!(config.get_value("providers.moonshot.auth_mode"), None);
assert_eq!(config.get_value("providers.moonshot.base_url"), None);
assert_eq!(config.get_value("providers.moonshot.model"), None);
Ok(())
}
#[test]
fn project_merge_denies_credentials_endpoints_and_provider_selection() {
let mut base = ConfigToml {
@@ -2637,6 +2733,30 @@ mod tests {
assert_eq!(resolved.api_key_source, None);
}
#[test]
fn moonshot_kimi_code_api_key_endpoint_defaults_to_kimi_for_coding() {
let _lock = env_lock();
let _env = EnvGuard::without_deepseek_runtime_overrides();
let mut config = ConfigToml {
provider: ProviderKind::Moonshot,
..ConfigToml::default()
};
config.providers.moonshot.api_key = Some("kimi-code-key".to_string());
config.providers.moonshot.base_url = Some(DEFAULT_KIMI_CODE_BASE_URL.to_string());
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
assert_eq!(resolved.provider, ProviderKind::Moonshot);
assert_eq!(resolved.auth_mode, None);
assert_eq!(resolved.base_url, DEFAULT_KIMI_CODE_BASE_URL);
assert_eq!(resolved.model, DEFAULT_KIMI_CODE_MODEL);
assert_eq!(resolved.api_key.as_deref(), Some("kimi-code-key"));
assert_eq!(
resolved.api_key_source,
Some(RuntimeApiKeySource::ConfigFile)
);
}
#[test]
fn wanjie_ark_provider_defaults_to_openai_compatible_endpoint_and_model() {
let _lock = env_lock();
+9 -3
View File
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- **Kimi Code API-key setup.** `codewhale config set providers.moonshot.*`
now writes the Moonshot/Kimi provider table, and Kimi Code API-key
endpoints default to `kimi-for-coding` without using the Kimi CLI OAuth path.
## [0.8.45] - 2026-05-25
### Added
@@ -17,9 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Command palette voice input.** The command palette can launch a configured
speech-to-text helper and show footer status while transcription runs
(#2047).
- **Moonshot/Kimi OAuth provider.** Moonshot/Kimi is now a first-class
provider, including Kimi CLI OAuth reuse, secure refresh writes, model
completion, CLI auth, and secret-store integration.
- **Moonshot/Kimi provider.** Moonshot/Kimi is now a first-class provider,
including API-key auth, model completion, CLI auth, secret-store
integration, and optional Kimi CLI credential reuse.
- **Deterministic whale-species sub-agent names.** Sub-agents now get stable,
human-readable whale-species nicknames (e.g. "Beluga", "Orca") while
preserving the raw agent ID in the popup (#2035, #2016).
+57 -7
View File
@@ -1570,11 +1570,17 @@ impl Config {
{
return model_for_provider(provider, normalized);
}
if provider == ApiProvider::Moonshot
&& self
.provider_config()
.is_some_and(provider_config_uses_kimi_oauth)
{
let moonshot_config = (provider == ApiProvider::Moonshot)
.then(|| self.provider_config())
.flatten();
let moonshot_uses_kimi_code = moonshot_config.is_some_and(|config| {
provider_config_uses_kimi_oauth(config)
|| config
.base_url
.as_deref()
.is_some_and(moonshot_base_url_uses_kimi_code)
});
if moonshot_uses_kimi_code {
return DEFAULT_KIMI_CODE_MODEL.to_string();
}
@@ -1771,8 +1777,9 @@ impl Config {
),
ApiProvider::Moonshot => anyhow::bail!(
"Moonshot/Kimi API key not found. Run 'codewhale auth set --provider moonshot', \
set MOONSHOT_API_KEY/KIMI_API_KEY, add [providers.moonshot] api_key, \
or run `kimi login` and set [providers.moonshot] auth_mode = \"kimi_oauth\"."
set MOONSHOT_API_KEY/KIMI_API_KEY, or add [providers.moonshot] api_key. \
For a Kimi Code plan key, set [providers.moonshot] base_url = \
\"https://api.kimi.com/coding/v1\" and model = \"kimi-for-coding\"."
),
// Self-hosted deployments commonly run without auth on localhost.
// Return an empty key and let the client omit the Authorization header.
@@ -2880,6 +2887,13 @@ fn provider_preserves_custom_base_url_model(provider: ApiProvider, base_url: &st
base_url_is_custom_for_provider(provider, base_url)
}
fn moonshot_base_url_uses_kimi_code(base_url: &str) -> bool {
let normalized = normalize_base_url(base_url).to_ascii_lowercase();
normalized == DEFAULT_KIMI_CODE_BASE_URL
|| normalized == "https://api.kimi.com/coding"
|| normalized.starts_with("https://api.kimi.com/coding/")
}
fn provider_config_uses_kimi_oauth(config: &ProviderConfig) -> bool {
config
.auth_mode
@@ -6434,6 +6448,42 @@ api_key = "stale-api-key"
Ok(())
}
#[test]
fn moonshot_kimi_code_api_key_uses_coding_model() -> Result<()> {
let _lock = lock_test_env();
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"codewhale-tui-kimi-code-key-{}-{}",
std::process::id(),
nanos
));
fs::create_dir_all(&temp_root)?;
let _guard = EnvGuard::new(&temp_root);
let config_path = temp_root.join(".deepseek").join("config.toml");
ensure_parent_dir(&config_path)?;
fs::write(
&config_path,
r#"provider = "moonshot"
[providers.moonshot]
api_key = "kimi-code-key"
base_url = "https://api.kimi.com/coding/v1"
"#,
)?;
let config = Config::load(None, None)?;
assert_eq!(config.api_provider(), ApiProvider::Moonshot);
assert_eq!(config.deepseek_base_url(), DEFAULT_KIMI_CODE_BASE_URL);
assert_eq!(config.default_model(), DEFAULT_KIMI_CODE_MODEL);
assert_eq!(config.deepseek_api_key()?, "kimi-code-key");
assert!(has_api_key_for(&config, ApiProvider::Moonshot));
Ok(())
}
#[test]
fn has_api_key_for_detects_env_and_config_per_provider() -> Result<()> {
let _lock = lock_test_env();
+10
View File
@@ -267,6 +267,11 @@ command = "~/.deepseek/hooks/pre.sh" # / message_submit / mode_change /
<p className="mt-5 text-ink-soft leading-[1.9] tracking-wide">
CodeWhale DeepSeek Moonshot/KimiOpenRouterNVIDIA NIM
AtlasCloudWanjie ArkNovitaFireworks SGLang/vLLM/Ollama
Kimi Code API Key 使 <code className="inline">providers.moonshot.base_url</code>
<code className="inline">https://api.kimi.com/coding/v1</code>,模型为
<code className="inline">kimi-for-coding</code>Kimi/Moonshot API Key 使
<code className="inline">https://api.moonshot.ai/v1</code> 和
<code className="inline">kimi-k2.6</code>
</p>
</section>
@@ -515,6 +520,11 @@ command = "~/.deepseek/hooks/pre.sh" # / message_submit / mode_change /
Open-model platform direction: CodeWhale stays DeepSeek-first while shipping Moonshot/Kimi,
OpenRouter, NVIDIA NIM, AtlasCloud, Wanjie Ark, Novita, Fireworks, and self-hosted
SGLang/vLLM/Ollama paths.
Kimi Code membership API keys use <code className="inline">providers.moonshot.base_url</code>
set to <code className="inline">https://api.kimi.com/coding/v1</code> with
<code className="inline">kimi-for-coding</code>; Kimi/Moonshot Platform API keys use
<code className="inline">https://api.moonshot.ai/v1</code> with
<code className="inline">kimi-k2.6</code>.
</p>
</section>
+2 -2
View File
@@ -112,7 +112,7 @@ codewhale doctor # full connectivity check`}
<p className="mb-2">CodeWhale ships with these built-in providers:</p>
<ul className="list-disc pl-5 space-y-1 text-sm text-ink-soft mb-3">
<li><strong>DeepSeek</strong> first-class, native API. Reasoning streaming, cache metrics, thinking effort control.</li>
<li><strong>Moonshot/Kimi</strong> Kimi API key mode or local Kimi CLI OAuth reuse.</li>
<li><strong>Moonshot/Kimi</strong> Kimi Code and Kimi/Moonshot Platform API-key modes.</li>
<li><strong>OpenRouter</strong> unified API for DeepSeek models and more.</li>
<li><strong>OpenAI-compatible</strong>, <strong>NVIDIA NIM</strong>, <strong>AtlasCloud</strong>, <strong>Wanjie Ark</strong>, <strong>Novita</strong>, <strong>Fireworks</strong>, <strong>SGLang</strong>, <strong>vLLM</strong>, <strong>Ollama</strong></li>
</ul>
@@ -425,7 +425,7 @@ codewhale doctor # 完整连接检查`}
<p className="mb-2">CodeWhale </p>
<ul className="list-disc pl-5 space-y-1 text-sm text-ink-soft mb-3">
<li><strong>DeepSeek</strong> API</li>
<li><strong>Moonshot/Kimi</strong> Kimi API key Kimi CLI OAuth</li>
<li><strong>Moonshot/Kimi</strong> Kimi Code Kimi/Moonshot API key </li>
<li><strong>OpenRouter</strong> API访 DeepSeek </li>
<li><strong>OpenAI </strong><strong>NVIDIA NIM</strong><strong>AtlasCloud</strong><strong>Wanjie Ark</strong><strong>Novita</strong><strong>Fireworks</strong><strong>SGLang</strong><strong>vLLM</strong><strong>Ollama</strong></li>
</ul>
+2 -2
View File
@@ -31,7 +31,7 @@ const tracksEn = [
{ title: "Bidirectional MCP", note: "Consume tools from external servers; expose as server via `codewhale mcp`; ~/.deepseek/mcp.json" },
{ title: "Skills + unified slash palette", note: "~/.deepseek/skills/ auto-loading; /help, /mode, /status, /config, /trust, /feedback" },
{ title: "v0.8.45 provider surface", note: "DeepSeek, NVIDIA NIM, OpenAI-compatible, AtlasCloud, Wanjie Ark, OpenRouter, Novita, Fireworks, Moonshot/Kimi, SGLang, vLLM, and Ollama" },
{ title: "Moonshot/Kimi OAuth", note: "Kimi CLI OAuth reuse plus API-key mode for Moonshot/Kimi sessions" },
{ title: "Moonshot/Kimi API-key setup", note: "Kimi Code plan and Kimi/Moonshot Platform API-key paths for Moonshot/Kimi sessions" },
],
},
{
@@ -97,7 +97,7 @@ const tracksZh = [
{ title: "双向 MCP 协议", note: "消费外部服务器工具;通过 `codewhale mcp` 暴露为服务器;~/.deepseek/mcp.json" },
{ title: "技能 + 统一命令面板", note: "~/.deepseek/skills/ 自动加载;/help、/mode、/status、/config、/trust、/feedback" },
{ title: "v0.8.45 提供商表面", note: "DeepSeek、NVIDIA NIM、OpenAI 兼容、AtlasCloud、Wanjie Ark、OpenRouter、Novita、Fireworks、Moonshot/Kimi、SGLang、vLLM、Ollama" },
{ title: "Moonshot/Kimi OAuth", note: "复用 Kimi CLI OAuth,也支持 Moonshot/Kimi API key 模式" },
{ title: "Moonshot/Kimi API-key 设置", note: "Kimi Code 会员与 Kimi/Moonshot 平台 API key 路径" },
],
},
{
+4
View File
@@ -11,6 +11,10 @@ interface KVNamespace {
}
async function getKv(): Promise<KVNamespace | undefined> {
if (process.env.NEXT_PHASE === "phase-production-build") {
return undefined;
}
try {
const mod = await import("@opennextjs/cloudflare");
const ctx = await mod.getCloudflareContext({ async: true });
+16 -8
View File
@@ -23,20 +23,28 @@ interface CloudflareEnv {
GITHUB_REPO?: string;
}
function envFromProcess(): CloudflareEnv {
return {
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL: process.env.DEEPSEEK_MODEL,
GITHUB_TOKEN: process.env.GITHUB_TOKEN,
CRON_SECRET: process.env.CRON_SECRET,
GITHUB_REPO: process.env.GITHUB_REPO,
};
}
export async function getEnv(): Promise<CloudflareEnv> {
if (process.env.NEXT_PHASE === "phase-production-build") {
return envFromProcess();
}
try {
const mod = await import("@opennextjs/cloudflare");
const ctx = await mod.getCloudflareContext({ async: true });
return ctx.env as CloudflareEnv;
} catch {
return {
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL: process.env.DEEPSEEK_MODEL,
GITHUB_TOKEN: process.env.GITHUB_TOKEN,
CRON_SECRET: process.env.CRON_SECRET,
GITHUB_REPO: process.env.GITHUB_REPO,
};
return envFromProcess();
}
}
+1 -1
View File
@@ -58,7 +58,7 @@ interface GhIssue { number: number; title: string; html_url: string; body: strin
const FALLBACK_SHIPPED: RoadmapItem[] = [
{
title: "v0.8.45",
note: "Moonshot/Kimi OAuth, provider-surface sync, and current Windows install/runtime guidance",
note: "Moonshot/Kimi provider support, API-key setup guidance, provider-surface sync, and current Windows install/runtime guidance",
href: "https://github.com/Hmbown/CodeWhale/releases/tag/v0.8.45",
},
];