chore: polish codewhale home defaults

This commit is contained in:
Hunter B
2026-05-31 19:07:23 -07:00
parent 394f35b7bb
commit 2b69f4e041
43 changed files with 509 additions and 215 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
# CodeWhale multi-arch Docker image (#501)
#
# Build: docker buildx build --platform linux/amd64,linux/arm64 -t codewhale:latest .
# Run: docker run --rm -it -e DEEPSEEK_API_KEY -v codewhale-home:/home/codewhale/.deepseek codewhale
# Run: docker run --rm -it -e DEEPSEEK_API_KEY -v codewhale-home:/home/codewhale/.codewhale codewhale
#
# The image ships the canonical binaries (`codewhale`, `codewhale-tui`) plus
# the legacy `deepseek` / `deepseek-tui` shims in a minimal runtime layer.
@@ -77,6 +77,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# Non-root user with explicit UID/GID for filesystem ownership clarity.
RUN groupadd --gid 1000 codewhale \
&& useradd --create-home --shell /bin/bash --uid 1000 --gid 1000 codewhale \
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.codewhale \
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.deepseek
USER codewhale
WORKDIR /home/codewhale
+5 -2
View File
@@ -330,18 +330,21 @@ codewhale update # バイナリ更新の確認
| `DEEPSEEK_HTTP_HEADERS` | 任意のモデルリクエストヘッダー |
| `DEEPSEEK_MODEL` | デフォルトモデル |
| `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` | ストリームのアイドルタイムアウト秒数 |
| `DEEPSEEK_PROVIDER` | `codewhale`(デフォルト)、`nvidia-nim``openai``atlascloud``wanjie-ark``openrouter``xiaomi-mimo``novita``fireworks``sglang``vllm``ollama` |
| `CODEWHALE_PROVIDER` / `DEEPSEEK_PROVIDER` | `deepseek`(デフォルト)、`nvidia-nim``openai``atlascloud``wanjie-ark``volcengine``openrouter``xiaomi-mimo``novita``fireworks``siliconflow``moonshot``sglang``vllm``ollama` |
| `DEEPSEEK_PROFILE` | 設定プロファイル名 |
| `DEEPSEEK_MEMORY` | `on` に設定するとユーザーメモリを有効化 |
| `DEEPSEEK_ALLOW_INSECURE_HTTP=1` | 信頼できるネットワークで非ローカル `http://` API ベース URL を許可 |
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | プロバイダー認証 |
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `VOLCENGINE_API_KEY` / `ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | プロバイダー認証 |
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | 汎用 OpenAI 互換エンドポイントとモデル ID |
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud エンドポイントとモデル上書き |
| `WANJIE_ARK_BASE_URL` / `WANJIE_ARK_MODEL` | Wanjie Ark エンドポイントとモデル上書き |
| `VOLCENGINE_BASE_URL` / `ARK_BASE_URL` / `VOLCENGINE_MODEL` / `ARK_MODEL` | Volcengine Ark エンドポイントとモデル上書き |
| `OPENROUTER_BASE_URL` | OpenRouter エンドポイント上書き |
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Xiaomi MiMo エンドポイントとモデル上書き |
| `NOVITA_BASE_URL` | Novita エンドポイント上書き |
| `FIREWORKS_BASE_URL` | Fireworks エンドポイント上書き |
| `SILICONFLOW_BASE_URL` / `SILICONFLOW_MODEL` | SiliconFlow エンドポイントとモデル上書き |
| `MOONSHOT_BASE_URL` / `MOONSHOT_MODEL` / `KIMI_BASE_URL` / `KIMI_MODEL` | Moonshot/Kimi エンドポイントとモデル上書き |
| `SGLANG_BASE_URL` | セルフホスト SGLang のエンドポイント |
| `SGLANG_MODEL` | セルフホスト SGLang のモデル ID |
| `VLLM_BASE_URL` | セルフホスト vLLM のエンドポイント |
+6 -2
View File
@@ -407,17 +407,21 @@ Các biến môi trường chính:
| `DEEPSEEK_HTTP_HEADERS` | Các header tùy chỉnh gửi kèm yêu cầu API, ví dụ `X-Model-Provider-Id=your-model-provider` |
| `DEEPSEEK_MODEL` | Mô hình mặc định |
| `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` | Thời gian chờ tối đa khi stream bị rảnh (giây), mặc định là `300`, giới hạn trong khoảng `1..=3600` |
| `CODEWHALE_PROVIDER` / `DEEPSEEK_PROVIDER` | Các nhà cung cấp: `deepseek` (mặc định), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `novita`, `fireworks`, `moonshot`, `sglang`, `vllm`, `ollama` |
| `CODEWHALE_PROVIDER` / `DEEPSEEK_PROVIDER` | Các nhà cung cấp: `deepseek` (mặc định), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `volcengine`, `openrouter`, `xiaomi-mimo`, `novita`, `fireworks`, `siliconflow`, `moonshot`, `sglang`, `vllm`, `ollama` |
| `DEEPSEEK_PROFILE` | Tên cấu hình profile sử dụng |
| `DEEPSEEK_MEMORY` | Thiết lập là `on` để kích hoạt tính năng tự ghi nhớ thông tin người dùng |
| `DEEPSEEK_ALLOW_INSECURE_HTTP=1` | Cho phép sử dụng các đường dẫn API dạng `http://` không mã hóa trong các mạng LAN tin cậy |
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `OPENROUTER_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | Thông tin đăng nhập theo từng nhà cung cấp tương ứng |
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `VOLCENGINE_API_KEY` / `ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | Thông tin đăng nhập theo từng nhà cung cấp tương ứng |
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | Điểm cuối (endpoint) và mã mô hình cho nhà cung cấp tương thích định dạng OpenAI chung |
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | Endpoint và mô hình ghi đè cho AtlasCloud |
| `WANJIE_ARK_BASE_URL` / `WANJIE_ARK_MODEL` | Endpoint và mô hình ghi đè cho Wanjie Ark |
| `VOLCENGINE_BASE_URL` / `ARK_BASE_URL` / `VOLCENGINE_MODEL` / `ARK_MODEL` | Endpoint và mô hình ghi đè cho Volcengine Ark |
| `OPENROUTER_BASE_URL` | Endpoint ghi đè cho OpenRouter |
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Endpoint và mô hình ghi đè cho Xiaomi MiMo |
| `NOVITA_BASE_URL` | Endpoint ghi đè cho Novita |
| `FIREWORKS_BASE_URL` | Endpoint ghi đè cho Fireworks |
| `SILICONFLOW_BASE_URL` / `SILICONFLOW_MODEL` | Endpoint và mô hình ghi đè cho SiliconFlow |
| `MOONSHOT_BASE_URL` / `MOONSHOT_MODEL` / `KIMI_BASE_URL` / `KIMI_MODEL` | Endpoint và mô hình ghi đè cho Moonshot/Kimi |
| `SGLANG_BASE_URL` | Endpoint cho máy chủ SGLang tự host |
| `SGLANG_MODEL` | Mã mô hình cho máy chủ SGLang tự host |
| `VLLM_BASE_URL` | Endpoint cho máy chủ vLLM tự host |
+3 -2
View File
@@ -415,14 +415,15 @@ DeepSeek 可作为自定义 Agent Client Protocol 服务器运行,供 Zed 等
| `DEEPSEEK_HTTP_HEADERS` | 可选模型请求头,例如 `X-Model-Provider-Id=your-model-provider` |
| `DEEPSEEK_MODEL` | 默认模型 |
| `DEEPSEEK_STREAM_IDLE_TIMEOUT_SECS` | 流式响应空闲超时秒数,默认 `300`,限制在 `1..=3600` |
| `CODEWHALE_PROVIDER` / `DEEPSEEK_PROVIDER` | `deepseek`(默认)、`nvidia-nim``openai``atlascloud``wanjie-ark``openrouter``xiaomi-mimo``novita``fireworks``siliconflow``moonshot``sglang``vllm``ollama` |
| `CODEWHALE_PROVIDER` / `DEEPSEEK_PROVIDER` | `deepseek`(默认)、`nvidia-nim``openai``atlascloud``wanjie-ark``volcengine``openrouter``xiaomi-mimo``novita``fireworks``siliconflow``moonshot``sglang``vllm``ollama` |
| `DEEPSEEK_PROFILE` | 配置 profile 名称 |
| `DEEPSEEK_MEMORY` | 设为 `on` 启用用户记忆 |
| `DEEPSEEK_ALLOW_INSECURE_HTTP=1` | 在可信网络上允许非本机 `http://` API base URL |
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | 提供商认证 |
| `NVIDIA_API_KEY` / `OPENAI_API_KEY` / `ATLASCLOUD_API_KEY` / `WANJIE_ARK_API_KEY` / `VOLCENGINE_API_KEY` / `ARK_API_KEY` / `OPENROUTER_API_KEY` / `XIAOMI_MIMO_API_KEY` / `MIMO_API_KEY` / `NOVITA_API_KEY` / `FIREWORKS_API_KEY` / `SILICONFLOW_API_KEY` / `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `SGLANG_API_KEY` / `VLLM_API_KEY` / `OLLAMA_API_KEY` | 提供商认证 |
| `OPENAI_BASE_URL` / `OPENAI_MODEL` | 通用 OpenAI 兼容端点和模型 ID |
| `ATLASCLOUD_BASE_URL` / `ATLASCLOUD_MODEL` | AtlasCloud 端点和模型覆盖 |
| `WANJIE_ARK_BASE_URL` / `WANJIE_ARK_MODEL` | Wanjie Ark 端点和模型覆盖 |
| `VOLCENGINE_BASE_URL` / `ARK_BASE_URL` / `VOLCENGINE_MODEL` / `ARK_MODEL` | Volcengine Ark 端点和模型覆盖 |
| `OPENROUTER_BASE_URL` | OpenRouter 端点覆盖 |
| `XIAOMI_MIMO_BASE_URL` / `MIMO_BASE_URL` / `XIAOMI_MIMO_MODEL` / `MIMO_MODEL` | Xiaomi MiMo 端点和模型覆盖 |
| `NOVITA_BASE_URL` | Novita 端点覆盖 |
+19 -13
View File
@@ -14,12 +14,12 @@
# this file — keeping both stored at once means `/provider deepseek` and
# `/provider nvidia-nim` (or `--provider openai`, `--provider wanjie-ark`,
# `--provider volcengine`, `--provider xiaomi-mimo`, `--provider fireworks`,
# `--provider siliconflow`, `/provider sglang`, `/provider vllm`,
# `/provider ollama`) toggle without having to re-enter keys. Top-level
# `--provider siliconflow`, `/provider moonshot`, `/provider sglang`,
# `/provider vllm`, `/provider ollama`) toggle without having to re-enter keys. Top-level
# `api_key` / `base_url` are
# still read as DeepSeek defaults when `[providers.deepseek]` is absent
# (backward compatibility).
provider = "deepseek" # deepseek | deepseek-cn | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | xiaomi-mimo | novita | fireworks | siliconflow | sglang | vllm | ollama
provider = "deepseek" # deepseek | deepseek-cn | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | xiaomi-mimo | novita | fireworks | siliconflow | moonshot | sglang | vllm | ollama
api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty
base_url = "https://api.deepseek.com/beta"
# provider = "deepseek-cn" # legacy alias (official host is still https://api.deepseek.com)
@@ -84,13 +84,16 @@ check_for_updates = true
# ─────────────────────────────────────────────────────────────────────────────────
# Paths
# ─────────────────────────────────────────────────────────────────────────────────
skills_dir = "~/.deepseek/skills"
mcp_config_path = "~/.deepseek/mcp.json"
notes_path = "~/.deepseek/notes.txt"
# New installs write product state under ~/.codewhale/. Existing ~/.deepseek/
# files are still read as compatibility fallbacks when the .codewhale file is
# absent.
skills_dir = "~/.codewhale/skills"
mcp_config_path = "~/.codewhale/mcp.json"
notes_path = "~/.codewhale/notes.txt"
memory_path = "~/.deepseek/memory.md"
memory_path = "~/.codewhale/memory.md"
# instructions = ["./AGENTS.md", "~/.deepseek/global.md"]
# instructions = ["./AGENTS.md", "~/.codewhale/global.md"]
#
# Optional list of additional instruction files concatenated into the
# system prompt in declared order (#454). Useful for layering
@@ -98,7 +101,7 @@ memory_path = "~/.deepseek/memory.md"
# is expanded so `~` and env vars work; missing files are skipped with
# a tracing warning. Files are capped at 100 KiB per entry.
#
# Project-level config (.deepseek/config.toml in the workspace) replaces
# Project-level config (.codewhale/config.toml in the workspace) replaces
# the user-level array wholesale rather than merging — list `~/global.md`
# inside the project array if you want both. An explicit empty array
# (`instructions = []`) clears the user list for the current repo.
@@ -198,18 +201,21 @@ max_subagents = 10 # optional (1-20)
# ─────────────────────────────────────────────────────────────────────────────────
# Providers can be stored at once; `provider = "..."` (top of file) or
# `/provider deepseek` / `/provider nvidia-nim` / `--provider openai` /
# `--provider wanjie-ark` / `/provider fireworks` / `--provider siliconflow`
# `--provider wanjie-ark` / `/provider volcengine` / `/provider fireworks` /
# `--provider siliconflow` / `/provider moonshot`
# switches between them without having to re-enter keys. Env vars override anything set here:
# DeepSeek: DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL
# NIM: NVIDIA_API_KEY (or NVIDIA_NIM_API_KEY), NIM_BASE_URL
# (or NVIDIA_NIM_BASE_URL / NVIDIA_BASE_URL), NVIDIA_NIM_MODEL
# OpenAI-compatible: OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
# Wanjie Ark: WANJIE_ARK_API_KEY (or WANJIE_API_KEY), WANJIE_ARK_BASE_URL, WANJIE_ARK_MODEL
# Volcengine Ark: VOLCENGINE_API_KEY (or VOLCENGINE_ARK_API_KEY / ARK_API_KEY), VOLCENGINE_BASE_URL, VOLCENGINE_MODEL
# OpenRouter: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL
# Xiaomi MiMo: XIAOMI_MIMO_API_KEY (or XIAOMI_API_KEY / MIMO_API_KEY), XIAOMI_MIMO_BASE_URL, XIAOMI_MIMO_MODEL
# Novita: NOVITA_API_KEY, NOVITA_BASE_URL, NOVITA_MODEL
# Fireworks: FIREWORKS_API_KEY, FIREWORKS_BASE_URL
# SiliconFlow: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL
# Moonshot/Kimi: MOONSHOT_API_KEY (or KIMI_API_KEY), MOONSHOT_BASE_URL, MOONSHOT_MODEL
# SGLang: SGLANG_BASE_URL, SGLANG_MODEL, optional SGLANG_API_KEY
# vLLM: VLLM_BASE_URL, VLLM_MODEL, optional VLLM_API_KEY
# Ollama: OLLAMA_BASE_URL, OLLAMA_MODEL, optional OLLAMA_API_KEY
@@ -369,7 +375,7 @@ max_subagents = 10 # optional (1-20)
# default = "prompt" # allow | deny | prompt
# allow = ["api.deepseek.com", "github.com", ".githubusercontent.com"]
# deny = []
# audit = true # one line per call to ~/.deepseek/audit.log
# audit = true # one line per call to ~/.codewhale/audit.log
# ─────────────────────────────────────────────────────────────────────────────────
# Skills (#140)
@@ -557,7 +563,7 @@ default_text_model = "deepseek-ai/deepseek-v4-pro"
# Each turn the TUI takes a `pre-turn:<seq>` and `post-turn:<seq>` snapshot of
# your workspace into a side-git repo at:
#
# ~/.deepseek/snapshots/<project_hash>/<worktree_hash>/.git
# ~/.codewhale/snapshots/<project_hash>/<worktree_hash>/.git
#
# Your own `.git` is never touched — `--git-dir` and `--work-tree` are always
# set together when shelling out to git. Use `/restore N` (slash command) or
@@ -631,7 +637,7 @@ default_text_model = "deepseek-ai/deepseek-v4-pro"
# Those vars are merged into the spawned process environment (later hooks
# override earlier ones). Use this for ephemeral credentials, per-skill
# PATH adjustments, or short-lived tokens. The resolved KEY names (NEVER
# values) are written to `~/.deepseek/audit.log` so each session can be
# values) are written to `~/.codewhale/audit.log` so each session can be
# reconciled later. Hook failure / timeout simply contributes no vars —
# it does not abort the shell call.
#
+7 -1
View File
@@ -79,7 +79,13 @@ fn artifact_sessions_root() -> Option<PathBuf> {
return Some(root);
}
Some(dirs::home_dir()?.join(".deepseek").join("sessions"))
let home = dirs::home_dir()?;
let primary = home.join(".codewhale").join("sessions");
let legacy = home.join(".deepseek").join("sessions");
if primary.exists() || !legacy.exists() {
return Some(primary);
}
Some(legacy)
}
#[cfg(test)]
+1 -1
View File
@@ -8,7 +8,7 @@ use serde_json::{Value, json};
use crate::utils::{flush_and_sync, open_append};
/// Append an audit event to `~/.deepseek/audit.log`.
/// Append an audit event to `~/.codewhale/audit.log`.
///
/// This helper is best-effort by design: callers should not fail critical flows
/// if audit persistence fails.
+5 -5
View File
@@ -2,7 +2,7 @@
//!
//! Automations are local-first recurring jobs that enqueue standard background
//! tasks. This module stores automation definitions and run history under
//! `~/.deepseek/automations` (or `DEEPSEEK_AUTOMATIONS_DIR` override).
//! `~/.codewhale/automations` (or `DEEPSEEK_AUTOMATIONS_DIR` override).
use std::collections::BTreeMap;
use std::fs;
@@ -797,11 +797,11 @@ pub fn default_automations_dir() -> PathBuf {
dirs::home_dir()
.map(|home| {
let primary = home.join(".codewhale").join("automations");
if primary.exists() {
primary
} else {
home.join(".deepseek").join("automations")
let legacy = home.join(".deepseek").join("automations");
if primary.exists() || !legacy.exists() {
return primary;
}
legacy
})
.unwrap_or_else(|| PathBuf::from(".codewhale").join("automations"))
}
+105 -6
View File
@@ -6,7 +6,8 @@ use std::time::Duration;
use super::CommandResult;
use crate::client::DeepSeekClient;
use crate::config::{
COMMON_DEEPSEEK_MODELS, Config, clear_api_key, expand_path, normalize_model_name_for_provider,
COMMON_DEEPSEEK_MODELS, Config, clear_api_key, effective_home_dir, expand_path,
normalize_model_name_for_provider,
};
use crate::config_ui::{ConfigUiMode, parse_mode};
use crate::llm_client::LlmClient;
@@ -287,7 +288,7 @@ pub fn verbose(app: &mut App, arg: Option<&str>) -> CommandResult {
})
}
/// Persist `tui.status_items` to `~/.deepseek/config.toml` without disturbing
/// Persist `tui.status_items` to `~/.codewhale/config.toml` without disturbing
/// the rest of the file. We round-trip through `toml::Value` so any keys we
/// don't know about (provider blocks, MCP, etc.) survive the write
/// untouched.
@@ -365,26 +366,37 @@ pub fn persist_root_string_key(
Ok(path)
}
/// Resolve the path to `~/.deepseek/config.toml` (or
/// `$DEEPSEEK_CONFIG_PATH`). Mirrors what `Config::load` accepts so we
/// Resolve the path to `~/.codewhale/config.toml` (or
/// `$CODEWHALE_CONFIG_PATH` / `$DEEPSEEK_CONFIG_PATH`). Mirrors what `Config::load` accepts so we
/// never write to a different file than the one we read.
pub(super) fn config_toml_path(config_path: Option<&Path>) -> anyhow::Result<PathBuf> {
use anyhow::Context;
if let Some(path) = config_path {
return Ok(expand_path(path.to_string_lossy().as_ref()));
}
if let Ok(env) = std::env::var("CODEWHALE_CONFIG_PATH") {
let trimmed = env.trim();
if !trimmed.is_empty() {
return Ok(PathBuf::from(trimmed));
}
}
if let Ok(env) = std::env::var("DEEPSEEK_CONFIG_PATH") {
let trimmed = env.trim();
if !trimmed.is_empty() {
return Ok(PathBuf::from(trimmed));
}
}
let home = dirs::home_dir().context("failed to resolve home directory for config.toml path")?;
let home =
effective_home_dir().context("failed to resolve home directory for config.toml path")?;
let primary = home.join(".codewhale").join("config.toml");
if primary.exists() {
return Ok(primary);
}
Ok(home.join(".deepseek").join("config.toml"))
let legacy = home.join(".deepseek").join("config.toml");
if legacy.exists() {
return Ok(legacy);
}
Ok(primary)
}
/// Modify a setting at runtime
@@ -1360,6 +1372,7 @@ mod tests {
struct EnvGuard {
home: Option<OsString>,
userprofile: Option<OsString>,
codewhale_config_path: Option<OsString>,
deepseek_config_path: Option<OsString>,
_lock: std::sync::MutexGuard<'static, ()>,
}
@@ -1372,18 +1385,21 @@ mod tests {
let config_str = OsString::from(config_path.as_os_str());
let home_prev = env::var_os("HOME");
let userprofile_prev = env::var_os("USERPROFILE");
let codewhale_config_prev = env::var_os("CODEWHALE_CONFIG_PATH");
let deepseek_config_prev = env::var_os("DEEPSEEK_CONFIG_PATH");
// Safety: test-only environment mutation guarded by process-wide mutex.
unsafe {
env::set_var("HOME", &home_str);
env::set_var("USERPROFILE", &home_str);
env::remove_var("CODEWHALE_CONFIG_PATH");
env::set_var("DEEPSEEK_CONFIG_PATH", &config_str);
}
Self {
home: home_prev,
userprofile: userprofile_prev,
codewhale_config_path: codewhale_config_prev,
deepseek_config_path: deepseek_config_prev,
_lock: lock,
}
@@ -1416,6 +1432,18 @@ mod tests {
}
}
if let Some(value) = self.codewhale_config_path.take() {
// Safety: test-only environment mutation guarded by a global mutex.
unsafe {
env::set_var("CODEWHALE_CONFIG_PATH", value);
}
} else {
// Safety: test-only environment mutation guarded by a global mutex.
unsafe {
env::remove_var("CODEWHALE_CONFIG_PATH");
}
}
if let Some(value) = self.deepseek_config_path.take() {
// Safety: test-only environment mutation guarded by a global mutex.
unsafe {
@@ -2181,6 +2209,77 @@ mod tests {
assert!(body.contains("\"cost\""), "expected cost key in {body}");
}
#[test]
fn config_toml_path_uses_codewhale_home_for_fresh_installs() {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"codewhale-config-path-fresh-{}-{}",
std::process::id(),
nanos
));
fs::create_dir_all(&temp_root).unwrap();
let _guard = EnvGuard::new(&temp_root);
unsafe {
env::remove_var("DEEPSEEK_CONFIG_PATH");
}
assert_eq!(
config_toml_path(None).unwrap(),
temp_root.join(".codewhale").join("config.toml")
);
}
#[test]
fn config_toml_path_preserves_legacy_config_when_it_exists() {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"codewhale-config-path-legacy-{}-{}",
std::process::id(),
nanos
));
let legacy_config = temp_root.join(".deepseek").join("config.toml");
fs::create_dir_all(legacy_config.parent().unwrap()).unwrap();
fs::write(&legacy_config, "").unwrap();
let _guard = EnvGuard::new(&temp_root);
unsafe {
env::remove_var("DEEPSEEK_CONFIG_PATH");
}
assert_eq!(config_toml_path(None).unwrap(), legacy_config);
}
#[test]
fn config_toml_path_prefers_codewhale_env_over_legacy_env() {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"codewhale-config-path-env-{}-{}",
std::process::id(),
nanos
));
fs::create_dir_all(&temp_root).unwrap();
let _guard = EnvGuard::new(&temp_root);
let preferred = temp_root.join("preferred.toml");
let legacy = temp_root.join("legacy.toml");
unsafe {
env::set_var("CODEWHALE_CONFIG_PATH", &preferred);
env::set_var("DEEPSEEK_CONFIG_PATH", &legacy);
}
assert_eq!(config_toml_path(None).unwrap(), preferred);
}
#[test]
fn persist_status_items_preserves_existing_unrelated_keys() {
let nanos = SystemTime::now()
+1 -1
View File
@@ -200,7 +200,7 @@ pub fn profile_switch(_app: &mut App, arg: Option<&str>) -> CommandResult {
Some(name) if !name.trim().is_empty() => name.trim().to_string(),
_ => {
return CommandResult::error(
"Usage: /profile <name>\n\nSwitch to a named config profile. Profiles are defined in ~/.deepseek/config.toml under [profiles] sections.",
"Usage: /profile <name>\n\nSwitch to a named config profile. Profiles are defined in ~/.codewhale/config.toml under [profiles] sections.",
);
}
};
+2 -2
View File
@@ -3,7 +3,7 @@
//!
//! The full picker / persisted enable-disable surface in #460 is
//! still M-sized. This MVP gives the user a no-typing view of what's
//! actually configured in `~/.deepseek/config.toml`'s `[hooks]`
//! actually configured in `~/.codewhale/config.toml`'s `[hooks]`
//! table — the most-asked question once hooks start firing.
use crate::hooks::HookEvent;
@@ -74,7 +74,7 @@ fn list(app: &App) -> CommandResult {
let config = app.hooks.config();
if config.hooks.is_empty() {
return CommandResult::message(
"No hooks configured. Add a `[[hooks.hooks]]` entry to `~/.deepseek/config.toml` to define one.",
"No hooks configured. Add a `[[hooks.hooks]]` entry to `~/.codewhale/config.toml` to define one.",
);
}
+1 -1
View File
@@ -46,7 +46,7 @@ fn memory_help(path: &Path) -> String {
pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
if !app.use_memory {
return CommandResult::error(
"user memory is disabled. Enable with `[memory] enabled = true` in `~/.deepseek/config.toml` or `DEEPSEEK_MEMORY=on` in your environment, then restart the TUI.",
"user memory is disabled. Enable with `[memory] enabled = true` in `~/.codewhale/config.toml` or `DEEPSEEK_MEMORY=on` in your environment, then restart the TUI.",
);
}
+6 -6
View File
@@ -33,7 +33,7 @@ fn render_skill_warnings(registry: &SkillRegistry) -> String {
/// List all available skills. Pass `--remote` (or `remote`) to fetch the
/// curated registry instead of scanning the local skills directory.
/// Pass `sync` to pull the registry index and download all skills to the
/// local cache (`~/.deepseek/cache/skills/`).
/// local cache (`~/.codewhale/cache/skills/`).
pub fn list_skills(app: &mut App, arg: Option<&str>) -> CommandResult {
let mut prefix: Option<String> = None;
if let Some(arg) = arg {
@@ -401,7 +401,7 @@ pub fn list_remote_skills(app: &mut App) -> CommandResult {
// ─── /skills sync ──────────────────────────────────────────────────────────
/// Fetch the remote registry index and download every listed skill into the
/// local cache (`~/.deepseek/cache/skills/<name>/`).
/// local cache (`~/.codewhale/cache/skills/<name>/`).
///
/// For each skill the sync checks the cached ETag / SHA-256 before
/// downloading so unchanged skills are skipped in O(1) network round-trips.
@@ -523,14 +523,14 @@ fn path_or_default(path: &std::path::Path) -> String {
fn needs_approval_message(host: &str) -> String {
format!(
"Network policy requires approval for {host}.\n\
Add it to your allow list with `/network allow {host}` (or set [network].default = \"allow\" in ~/.deepseek/config.toml), then retry."
Add it to your allow list with `/network allow {host}` (or set [network].default = \"allow\" in ~/.codewhale/config.toml), then retry."
)
}
fn network_denied_message(host: &str) -> String {
format!(
"Network policy denied access to {host}.\n\
Remove the deny entry from ~/.deepseek/config.toml under [network] or contact your administrator."
Remove the deny entry from ~/.codewhale/config.toml under [network] or contact your administrator."
)
}
@@ -547,7 +547,7 @@ fn registry_fetch_error_hint(err: &anyhow::Error) -> Option<&'static str> {
|| msg.contains("nodename nor servname")
{
Some(
"Hint: DNS lookup failed. Check internet/DNS connectivity, or override the registry URL in [skills] of ~/.deepseek/config.toml.",
"Hint: DNS lookup failed. Check internet/DNS connectivity, or override the registry URL in [skills] of ~/.codewhale/config.toml.",
)
} else if msg.contains("connection refused")
|| msg.contains("connection reset")
@@ -566,7 +566,7 @@ fn registry_fetch_error_hint(err: &anyhow::Error) -> Option<&'static str> {
)
} else if msg.contains(" 404") || msg.contains("not found") {
Some(
"Hint: registry URL returned 404. Verify the registry URL in [skills] of ~/.deepseek/config.toml.",
"Hint: registry URL returned 404. Verify the registry URL in [skills] of ~/.codewhale/config.toml.",
)
} else if msg.contains(" 401") || msg.contains(" 403") || msg.contains("forbidden") {
Some(
+25 -16
View File
@@ -1,5 +1,5 @@
//! User-defined slash commands from `~/.deepseek/commands/<name>.md` and
//! workspace-local `<workspace>/.deepseek/commands/<name>.md`.
//! User-defined slash commands from `~/.codewhale/commands/<name>.md` and
//! workspace-local `<workspace>/.codewhale/commands/<name>.md`.
//!
//! Users drop `.md` files into a commands directory and the filename
//! (without `.md` extension) becomes a slash command. When invoked via
@@ -13,10 +13,12 @@
//!
//! Workspace-local directories shadow user-global by name:
//!
//! 1. `<workspace>/.deepseek/commands/` (project-local, highest)
//! 2. `<workspace>/.claude/commands/` (Claude Code interop)
//! 3. `<workspace>/.cursor/commands/` (Cursor interop)
//! 4. `~/.deepseek/commands/` (user-global, lowest)
//! 1. `<workspace>/.codewhale/commands/` (project-local, highest)
//! 2. `<workspace>/.deepseek/commands/` (legacy project-local)
//! 3. `<workspace>/.claude/commands/` (Claude Code interop)
//! 4. `<workspace>/.cursor/commands/` (Cursor interop)
//! 5. `~/.codewhale/commands/` (user-global)
//! 6. `~/.deepseek/commands/` (legacy user-global)
use std::collections::HashSet;
use std::path::{Path, PathBuf};
@@ -25,8 +27,13 @@ use crate::tui::app::{App, AppAction, HuntVerdict};
use super::CommandResult;
/// Path to the global user commands directory: `~/.deepseek/commands/`.
/// Path to the global user commands directory: `~/.codewhale/commands/`.
fn global_commands_dir() -> PathBuf {
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("~"));
home.join(".codewhale").join("commands")
}
fn legacy_global_commands_dir() -> PathBuf {
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("~"));
home.join(".deepseek").join("commands")
}
@@ -35,11 +42,13 @@ fn global_commands_dir() -> PathBuf {
fn commands_dirs(workspace: Option<&Path>) -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(ws) = workspace {
dirs.push(ws.join(".codewhale").join("commands"));
dirs.push(ws.join(".deepseek").join("commands"));
dirs.push(ws.join(".claude").join("commands"));
dirs.push(ws.join(".cursor").join("commands"));
}
dirs.push(global_commands_dir());
dirs.push(legacy_global_commands_dir());
dirs
}
@@ -239,7 +248,7 @@ mod tests {
use tempfile::TempDir;
#[test]
fn test_global_commands_dir_contains_deepseek_commands() {
fn test_global_commands_dir_contains_codewhale_commands() {
let dir = global_commands_dir();
let parts: Vec<_> = dir
.components()
@@ -248,8 +257,8 @@ mod tests {
assert!(
parts
.windows(2)
.any(|pair| pair == [".deepseek", "commands"]),
"expected .deepseek/commands components in path, got: {}",
.any(|pair| pair == [".codewhale", "commands"]),
"expected .codewhale/commands components in path, got: {}",
dir.display()
);
}
@@ -333,7 +342,7 @@ mod tests {
fn load_user_commands_scans_workspace_local_dir() {
let tmp = TempDir::new().unwrap();
let ws = tmp.path();
let cmds_dir = ws.join(".deepseek").join("commands");
let cmds_dir = ws.join(".codewhale").join("commands");
write_command(&cmds_dir, "hello", "echo hi");
let cmds = load_user_commands(Some(ws));
@@ -378,7 +387,7 @@ mod tests {
// Workspace-local version
write_command(
&ws.join(".deepseek").join("commands"),
&ws.join(".codewhale").join("commands"),
"shared",
"workspace version",
);
@@ -399,15 +408,15 @@ mod tests {
.expect("shared present");
assert_eq!(
shared.1, "workspace version",
"workspace-local (.deepseek) must shadow later dirs"
"workspace-local (.codewhale) must shadow later dirs"
);
}
#[test]
fn load_user_commands_without_workspace_falls_back_to_global_only() {
// When no workspace is passed, only the global ~/.deepseek/commands/
// is scanned. On test machines this dir often doesn't exist, so we
// just verify we don't panic.
// When no workspace is passed, only global command directories are
// scanned. On test machines these often don't exist, so we just
// verify we don't panic.
let cmds = load_user_commands(None);
// This should not panic; can be empty or have user's real commands.
let _ = cmds;
+9 -2
View File
@@ -7,7 +7,7 @@
//!
//! ## On-disk format
//!
//! `~/.deepseek/composer_stash.jsonl` — one JSON object per line:
//! `~/.codewhale/composer_stash.jsonl` — one JSON object per line:
//!
//! ```jsonl
//! {"ts":"2026-05-04T01:23:45Z","text":"draft here"}
@@ -52,7 +52,14 @@ pub struct StashedDraft {
}
fn default_stash_path() -> Option<PathBuf> {
dirs::home_dir().map(|home| home.join(".deepseek").join(STASH_FILE_NAME))
dirs::home_dir().map(|home| {
let primary = home.join(".codewhale").join(STASH_FILE_NAME);
let legacy = home.join(".deepseek").join(STASH_FILE_NAME);
if primary.exists() || !legacy.exists() {
return primary;
}
legacy
})
}
/// Load every stashed draft from disk in the order they were
+140 -4
View File
@@ -2591,7 +2591,11 @@ fn home_config_path() -> Option<PathBuf> {
if primary.exists() {
return primary;
}
home.join(".deepseek").join("config.toml")
let legacy = home.join(".deepseek").join("config.toml");
if legacy.exists() {
return legacy;
}
primary
})
}
@@ -2673,6 +2677,12 @@ fn canonicalize_or_keep(path: &Path) -> PathBuf {
}
fn env_config_path() -> Option<PathBuf> {
if let Ok(path) = std::env::var("CODEWHALE_CONFIG_PATH") {
let trimmed = path.trim();
if !trimmed.is_empty() {
return Some(expand_path(trimmed));
}
}
if let Ok(path) = std::env::var("DEEPSEEK_CONFIG_PATH") {
let trimmed = path.trim();
if !trimmed.is_empty() {
@@ -2813,7 +2823,11 @@ fn default_mcp_config_path() -> Option<PathBuf> {
if primary.exists() {
return primary;
}
home.join(".deepseek").join("mcp.json")
let legacy = home.join(".deepseek").join("mcp.json");
if legacy.exists() {
return legacy;
}
primary
})
}
@@ -2823,7 +2837,11 @@ fn default_notes_path() -> Option<PathBuf> {
if primary.exists() {
return primary;
}
home.join(".deepseek").join("notes.txt")
let legacy = home.join(".deepseek").join("notes.txt");
if legacy.exists() {
return legacy;
}
primary
})
}
@@ -2833,7 +2851,11 @@ fn default_memory_path() -> Option<PathBuf> {
if primary.exists() {
return primary;
}
home.join(".deepseek").join("memory.md")
let legacy = home.join(".deepseek").join("memory.md");
if legacy.exists() {
return legacy;
}
primary
})
}
@@ -5013,6 +5035,7 @@ mod tests {
home: Option<OsString>,
userprofile: Option<OsString>,
codewhale_home: Option<OsString>,
codewhale_config_path: Option<OsString>,
deepseek_config_path: Option<OsString>,
codewhale_secret_backend: Option<OsString>,
deepseek_secret_backend: Option<OsString>,
@@ -5091,6 +5114,7 @@ mod tests {
let home_prev = env::var_os("HOME");
let userprofile_prev = env::var_os("USERPROFILE");
let codewhale_home_prev = env::var_os("CODEWHALE_HOME");
let codewhale_config_prev = env::var_os("CODEWHALE_CONFIG_PATH");
let deepseek_config_prev = env::var_os("DEEPSEEK_CONFIG_PATH");
let codewhale_secret_backend_prev = env::var_os("CODEWHALE_SECRET_BACKEND");
let deepseek_secret_backend_prev = env::var_os("DEEPSEEK_SECRET_BACKEND");
@@ -5164,6 +5188,7 @@ mod tests {
env::set_var("HOME", &home_str);
env::set_var("USERPROFILE", &home_str);
env::remove_var("CODEWHALE_HOME");
env::remove_var("CODEWHALE_CONFIG_PATH");
env::set_var("DEEPSEEK_CONFIG_PATH", &config_str);
env::remove_var("CODEWHALE_SECRET_BACKEND");
env::remove_var("DEEPSEEK_SECRET_BACKEND");
@@ -5237,6 +5262,7 @@ mod tests {
home: home_prev,
userprofile: userprofile_prev,
codewhale_home: codewhale_home_prev,
codewhale_config_path: codewhale_config_prev,
deepseek_config_path: deepseek_config_prev,
codewhale_secret_backend: codewhale_secret_backend_prev,
deepseek_secret_backend: deepseek_secret_backend_prev,
@@ -5316,6 +5342,7 @@ mod tests {
Self::restore_var("HOME", self.home.take());
Self::restore_var("USERPROFILE", self.userprofile.take());
Self::restore_var("CODEWHALE_HOME", self.codewhale_home.take());
Self::restore_var("CODEWHALE_CONFIG_PATH", self.codewhale_config_path.take());
Self::restore_var("DEEPSEEK_CONFIG_PATH", self.deepseek_config_path.take());
Self::restore_var(
"CODEWHALE_SECRET_BACKEND",
@@ -5973,6 +6000,115 @@ api_key = "old-openrouter-key"
Ok(())
}
#[test]
fn default_user_paths_use_codewhale_home_for_fresh_installs() -> 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-fresh-home-test-{}-{}",
std::process::id(),
nanos
));
fs::create_dir_all(&temp_root)?;
let _guard = EnvGuard::new(&temp_root);
// EnvGuard pins DEEPSEEK_CONFIG_PATH for older tests; this test wants
// the no-explicit-path startup behavior.
unsafe {
env::remove_var("DEEPSEEK_CONFIG_PATH");
}
let config = Config::default();
assert_eq!(
default_config_path().unwrap(),
temp_root.join(".codewhale").join("config.toml")
);
assert_eq!(
config.mcp_config_path(),
temp_root.join(".codewhale").join("mcp.json")
);
assert_eq!(
config.notes_path(),
temp_root.join(".codewhale").join("notes.txt")
);
assert_eq!(
config.memory_path(),
temp_root.join(".codewhale").join("memory.md")
);
Ok(())
}
#[test]
fn default_user_paths_preserve_existing_legacy_files() -> 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-legacy-home-test-{}-{}",
std::process::id(),
nanos
));
let legacy_home = temp_root.join(".deepseek");
fs::create_dir_all(&legacy_home)?;
for name in ["config.toml", "mcp.json", "notes.txt", "memory.md"] {
fs::write(legacy_home.join(name), "")?;
}
let _guard = EnvGuard::new(&temp_root);
unsafe {
env::remove_var("DEEPSEEK_CONFIG_PATH");
}
let config = Config::default();
assert_eq!(
default_config_path().unwrap(),
legacy_home.join("config.toml")
);
assert_eq!(config.mcp_config_path(), legacy_home.join("mcp.json"));
assert_eq!(config.notes_path(), legacy_home.join("notes.txt"));
assert_eq!(config.memory_path(), legacy_home.join("memory.md"));
Ok(())
}
#[test]
fn codewhale_config_path_env_wins_over_legacy_env() -> Result<()> {
let _lock = lock_test_env();
let prev_codewhale = env::var_os("CODEWHALE_CONFIG_PATH");
let prev_deepseek = env::var_os("DEEPSEEK_CONFIG_PATH");
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"codewhale-tui-config-env-test-{}-{}",
std::process::id(),
nanos
));
let preferred = temp_root.join("preferred.toml");
let legacy = temp_root.join("legacy.toml");
unsafe {
env::set_var("CODEWHALE_CONFIG_PATH", &preferred);
env::set_var("DEEPSEEK_CONFIG_PATH", &legacy);
}
assert_eq!(env_config_path().unwrap(), preferred);
unsafe {
EnvGuard::restore_var("CODEWHALE_CONFIG_PATH", prev_codewhale);
EnvGuard::restore_var("DEEPSEEK_CONFIG_PATH", prev_deepseek);
}
Ok(())
}
#[test]
fn test_tilde_expansion_in_paths() -> Result<()> {
let _lock = lock_test_env();
+10 -5
View File
@@ -56,20 +56,25 @@ fn capacity_memory_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(home) = dirs::home_dir() {
// Prefer .codewhale, fall back to .deepseek
let primary = home.join(".codewhale").join("memory");
if primary.exists() {
let legacy = home.join(".deepseek").join("memory");
if primary.exists() || !legacy.exists() {
dirs.push(primary);
}
dirs.push(home.join(".deepseek").join("memory"));
if legacy.exists() {
dirs.push(legacy);
}
}
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let primary_cwd = cwd.join(".codewhale").join("memory");
if primary_cwd.exists() {
let legacy_cwd = cwd.join(".deepseek").join("memory");
if primary_cwd.exists() || !legacy_cwd.exists() {
dirs.push(primary_cwd);
}
dirs.push(cwd.join(".deepseek").join("memory"));
if legacy_cwd.exists() {
dirs.push(legacy_cwd);
}
dirs.dedup();
dirs
+3 -1
View File
@@ -3031,7 +3031,9 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> {
MessageId::CmdModelDescription => "Cambiar o mostrar el modelo actual",
MessageId::CmdModelsDescription => "Listar los modelos disponibles por la API",
MessageId::CmdNetworkDescription => "Gestionar reglas de red permitidas y bloqueadas",
MessageId::CmdNoteDescription => "Agregar nota al archivo persistente (.deepseek/notes.md)",
MessageId::CmdNoteDescription => {
"Agregar nota al archivo persistente (.codewhale/notes.md)"
}
MessageId::CmdThemeDescription => "Alternar entre tema claro y oscuro",
MessageId::CmdProviderDescription => {
"Cambiar o mostrar el backend LLM activo (deepseek | nvidia-nim | ollama)"
+10 -8
View File
@@ -186,7 +186,7 @@ struct Cli {
#[arg(long = "fresh")]
fresh: bool,
/// Skip loading project-level config from $WORKSPACE/.deepseek/config.toml
/// Skip loading project-level config from $WORKSPACE/.codewhale/config.toml
#[arg(long = "no-project-config")]
no_project_config: bool,
}
@@ -710,13 +710,13 @@ enum McpCommand {
},
/// Validate MCP config and required servers
Validate,
/// Register this DeepSeek binary as a local MCP stdio server.
/// Register this CodeWhale binary as a local MCP stdio server.
///
/// This adds a config entry that runs `codewhale serve --mcp` (stdio protocol).
/// For the HTTP/SSE runtime API, use `codewhale serve --http` directly instead.
#[command(
name = "add-self",
long_about = "Register this DeepSeek binary as a local MCP stdio server.\n\nAdds a config entry to ~/.deepseek/mcp.json that launches `codewhale serve --mcp`\nvia the stdio transport. Other DeepSeek sessions (or any MCP client) can then\ndiscover and call tools exposed by this server.\n\nUse `codewhale serve --http` instead if you need the HTTP/SSE runtime API."
long_about = "Register this CodeWhale binary as a local MCP stdio server.\n\nAdds a config entry to ~/.codewhale/mcp.json that launches `codewhale serve --mcp`\nvia the stdio transport. Other CodeWhale sessions (or any MCP client) can then\ndiscover and call tools exposed by this server.\n\nUse `codewhale serve --http` instead if you need the HTTP/SSE runtime API."
)]
AddSelf {
/// Server name in mcp.json (default: "codewhale")
@@ -1573,7 +1573,7 @@ fn plugins_readme_template() -> &'static str {
Plugins are richer than tools: each one lives in its own subdirectory\n\
with a `PLUGIN.md` describing what it does and how to enable it. The\n\
directory is created so users have a documented place to drop\n\
experiments without touching `~/.deepseek/skills/`.\n\n\
experiments without touching `~/.codewhale/skills/`.\n\n\
A plugin layout looks like:\n\n\
```\n\
plugins/\n\
@@ -4676,7 +4676,7 @@ fn should_use_mouse_capture_with(
/// Off elsewhere only for JetBrains' JediTerm, which advertises mouse
/// support but forwards the same SGR escape sequences as raw input. The
/// user can still opt back in with `[tui] mouse_capture = true` in
/// `~/.deepseek/config.toml` or `--mouse-capture`.
/// `~/.codewhale/config.toml` or `--mouse-capture`.
fn default_mouse_capture_enabled(
terminal_emulator: Option<&str>,
wt_session: Option<&str>,
@@ -4809,8 +4809,9 @@ fn preserve_interrupted_checkpoint_for_explicit_resume(launch_workspace: &Path)
}
}
/// Load project-level config from `$WORKSPACE/.deepseek/config.toml` and
/// apply its fields as overrides on top of the global config (#485).
/// Load project-level config from `$WORKSPACE/.codewhale/config.toml`, with
/// legacy `$WORKSPACE/.deepseek/config.toml` fallback, then apply its fields as
/// overrides on top of the global config (#485).
/// Only explicitly set fields in the project file are applied; everything
/// else falls back to the global value.
fn merge_project_config(config: &mut Config, workspace: &Path) {
@@ -4958,7 +4959,8 @@ async fn run_interactive(
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
// Merge project-level config from $WORKSPACE/.deepseek/config.toml
// Merge project-level config from $WORKSPACE/.codewhale/config.toml
// or legacy $WORKSPACE/.deepseek/config.toml
// unless --no-project-config was passed (#485).
let mut merged_config = config.clone();
if !cli.no_project_config {
+1 -1
View File
@@ -3,7 +3,7 @@
//! v0.8.8 ships an MVP that lets the user keep a persistent personal
//! note file the model sees on every turn:
//!
//! - **Load** `~/.deepseek/memory.md` (path is configurable via
//! - **Load** `~/.codewhale/memory.md` (path is configurable via
//! `memory_path` in `config.toml` and `DEEPSEEK_MEMORY_PATH` env),
//! wrap it in a `<user_memory>` block, and prepend it to the system
//! prompt alongside the existing `<project_instructions>` block.
+9 -6
View File
@@ -17,7 +17,7 @@
//! with **deny-wins precedence**: a host that matches an entry in `deny`
//! is denied even if it also matches `allow`.
//! 3. [`NetworkAuditor`] — appends one plaintext line per outbound call to
//! `~/.deepseek/audit.log` in the format described below.
//! `~/.codewhale/audit.log` in the format described below.
//!
//! In addition, [`NetworkSessionCache`] holds in-process "approve once for
//! this session" state for the `Prompt` flow, and [`NetworkDenied`] is the
@@ -41,7 +41,7 @@
//! ```
//!
//! Plaintext, one line per call, appended to `<audit_path>` (defaults to
//! `~/.deepseek/audit.log`). Best-effort: write failures are logged but do
//! `~/.codewhale/audit.log`). Best-effort: write failures are logged but do
//! not block the call.
use std::fs::{self, OpenOptions};
@@ -301,12 +301,15 @@ impl NetworkAuditor {
Self { path, enabled }
}
/// Auditor pointing at `~/.deepseek/audit.log`. Returns `None` if the
/// Auditor pointing at `~/.codewhale/audit.log`. Returns `None` if the
/// home directory can't be resolved.
#[must_use]
pub fn default_path(enabled: bool) -> Option<Self> {
let home = dirs::home_dir()?;
Some(Self::new(home.join(".deepseek").join("audit.log"), enabled))
Some(Self::new(
home.join(".codewhale").join("audit.log"),
enabled,
))
}
/// Append one line. Best-effort: errors are logged via `eprintln!` but
@@ -317,7 +320,7 @@ impl NetworkAuditor {
}
if let Err(err) = self.try_record(host, tool, decision_label) {
// Routed through tracing so it lands in
// `~/.deepseek/logs/tui-YYYY-MM-DD.log` rather than the
// `~/.codewhale/logs/tui-YYYY-MM-DD.log` rather than the
// alt-screen — see `runtime_log` for the scroll-demon
// rationale.
tracing::warn!(target: "network_policy", ?err, host, tool, "network audit write failed");
@@ -489,7 +492,7 @@ impl NetworkPolicyDecider {
}
/// Convenience: build a decider with default audit logging at
/// `~/.deepseek/audit.log`, if `policy.audit` is true.
/// `~/.codewhale/audit.log`, if `policy.audit` is true.
#[must_use]
pub fn with_default_audit(policy: NetworkPolicy) -> Self {
let audit_enabled = policy.audit_enabled();
+1 -1
View File
@@ -47,7 +47,7 @@ use crate::network_policy::{Decision, NetworkPolicy, host_from_url};
/// Cache directory for registry-synced skills.
///
/// Lives at `~/.deepseek/cache/skills/` so it's separate from user-installed
/// Lives at `~/.codewhale/cache/skills/` so it's separate from user-installed
/// skills and can be blown away without losing anything irreplaceable.
pub fn default_cache_skills_dir() -> PathBuf {
dirs::home_dir().map_or_else(
@@ -1,7 +1,7 @@
//! `retrieve_tool_result` - selective retrieval for spilled tool outputs.
//!
//! Large successful tool results are spilled to
//! `~/.deepseek/tool_outputs/<tool-call-id>.txt` by `tools::truncate`. This
//! `~/.codewhale/tool_outputs/<tool-call-id>.txt` by `tools::truncate`. This
//! tool gives the model a read-only, directory-scoped way to fetch summaries or
//! slices of those historical outputs without replaying the entire file into
//! every subsequent request.
@@ -36,7 +36,7 @@ impl ToolSpec for RetrieveToolResultTool {
}
fn description(&self) -> &'static str {
"Retrieve a previously spilled large tool result. Accepts a tool_call_id (`call_abc123`), artifact id (`art_call_abc123`), SHA reference (`sha:<64-hex>` or bare 64-hex from `<TOOL_RESULT_REF>`), relative filename (`call_abc123.txt`, `artifacts/art_call_abc123.txt`), or absolute path under ~/.deepseek. Modes: summary, head, tail, lines, query."
"Retrieve a previously spilled large tool result. Accepts a tool_call_id (`call_abc123`), artifact id (`art_call_abc123`), SHA reference (`sha:<64-hex>` or bare 64-hex from `<TOOL_RESULT_REF>`), relative filename (`call_abc123.txt`, `artifacts/art_call_abc123.txt`), or absolute path under ~/.codewhale. Modes: summary, head, tail, lines, query."
}
fn input_schema(&self) -> Value {
@@ -45,7 +45,7 @@ impl ToolSpec for RetrieveToolResultTool {
"properties": {
"ref": {
"type": "string",
"description": "Tool call id, artifact id (`art_<id>`), SHA ref (`sha:<64-hex>`), spillover filename, or absolute path under ~/.deepseek."
"description": "Tool call id, artifact id (`art_<id>`), SHA ref (`sha:<64-hex>`), spillover filename, or absolute path under ~/.codewhale."
},
"mode": {
"type": "string",
@@ -149,18 +149,19 @@ impl ToolSpec for RetrieveToolResultTool {
/// 3. `sha:<64-hex>` or bare 64-hex — content-addressed wire dedup, `sha_<hex>.txt`.
/// 4. `tool_result:<x>` — `<x>` is any of the above after the prefix.
/// 5. `artifacts/<file>.txt` or `<file>.txt` — relative paths.
/// 6. Absolute paths under `~/.deepseek/`.
/// 6. Absolute paths under the CodeWhale home.
///
/// The error message on a miss enumerates which forms were tried so the
/// model can correct course without a second blind guess.
fn resolve_spillover_reference(reference: &str, session_id: &str) -> Result<PathBuf, ToolError> {
let root = crate::tools::truncate::spillover_root()
.ok_or_else(|| ToolError::execution_failed("could not resolve ~/.deepseek/tool_outputs"))?;
let root = crate::tools::truncate::spillover_root().ok_or_else(|| {
ToolError::execution_failed("could not resolve ~/.codewhale/tool_outputs")
})?;
let root_canonical = root.canonicalize().ok();
// Resolve the session's `artifacts/` directory.
// `session_artifact_absolute_path(sid, p)` returns
// `~/.deepseek/sessions/<sid>/<p>` — so passing the literal
// `~/.codewhale/sessions/<sid>/<p>` — so passing the literal
// `ARTIFACTS_DIR_NAME` ("artifacts") gets us the real artifacts
// root. An earlier draft passed `Path::new(".")` and took
// `.parent()`, which landed one directory too high (`<sid>` instead
@@ -202,7 +203,7 @@ fn resolve_spillover_reference(reference: &str, session_id: &str) -> Result<Path
// `retrieve_tool_result`. canonicalize() would happily
// follow such a link and then pass the `starts_with(root)`
// check because of the resolved-then-compare order. The
// legacy `~/.deepseek/tool_outputs/` dir is engine-only and
// home-level `~/.codewhale/tool_outputs/` dir is engine-only and
// never carried this concern; session artifact dirs hold
// arbitrary tool output and need the guard.
if let Ok(meta) = std::fs::symlink_metadata(&candidate)
+23 -24
View File
@@ -10,7 +10,7 @@
//! the user can open it in `$EDITOR`.
//!
//! This module owns the disk side. Files land in
//! `~/.deepseek/tool_outputs/<sanitised-id>.txt`. The id is the tool
//! `~/.codewhale/tool_outputs/<sanitised-id>.txt`. The id is the tool
//! call id the engine assigns; we sanitise it conservatively (ASCII
//! alphanumeric + `-`/`_`) so a hostile id can't escape the directory
//! via `..` or absolute-path tricks.
@@ -45,7 +45,7 @@ use crate::tools::spec::ToolResult;
#[cfg(test)]
use std::path::Path;
/// Name of the spillover directory under `~/.deepseek/`.
/// Name of the spillover directory under the CodeWhale home.
pub const SPILLOVER_DIR_NAME: &str = "tool_outputs";
/// Default threshold above which a tool result is a candidate for
@@ -56,7 +56,7 @@ pub const SPILLOVER_DIR_NAME: &str = "tool_outputs";
pub const SPILLOVER_THRESHOLD_BYTES: usize = 100 * 1024; // 100 KiB
/// Default boot-prune age. Older spillover files are deleted on
/// startup to keep `~/.deepseek/tool_outputs/` from growing without
/// startup to keep `~/.codewhale/tool_outputs/` from growing without
/// bound. Mirrors the workspace-snapshot 7-day default.
pub const SPILLOVER_MAX_AGE: Duration = Duration::from_secs(7 * 24 * 60 * 60);
@@ -66,7 +66,7 @@ static TEST_SPILLOVER_ROOT: std::sync::Mutex<Option<PathBuf>> = std::sync::Mutex
#[cfg(test)]
pub(crate) static TEST_SPILLOVER_GUARD: std::sync::Mutex<()> = std::sync::Mutex::new(());
/// Resolve `~/.deepseek/tool_outputs/`. Returns `None` if the home
/// Resolve `~/.codewhale/tool_outputs/`. Returns `None` if the home
/// directory can't be determined (CI containers occasionally hit
/// this). Callers should treat `None` as "spillover unavailable" and
/// degrade gracefully rather than fail the tool call.
@@ -81,14 +81,13 @@ pub fn spillover_root() -> Option<PathBuf> {
return Some(root);
}
// Prefer .codewhale, fall back to .deepseek
let primary = dirs::home_dir()?
.join(".codewhale")
.join(SPILLOVER_DIR_NAME);
if primary.exists() {
let home = dirs::home_dir()?;
let primary = home.join(".codewhale").join(SPILLOVER_DIR_NAME);
let legacy = home.join(".deepseek").join(SPILLOVER_DIR_NAME);
if primary.exists() || !legacy.exists() {
return Some(primary);
}
Some(dirs::home_dir()?.join(".deepseek").join(SPILLOVER_DIR_NAME))
Some(legacy)
}
/// Override the spillover root for tests without mutating `$HOME`.
@@ -263,7 +262,7 @@ pub const SPILLOVER_HEAD_BYTES: usize = 32 * 1024;
/// Apply spillover to a tool result in place. If the result's
/// content exceeds [`SPILLOVER_THRESHOLD_BYTES`], writes the full
/// content to a sibling file under `~/.deepseek/tool_outputs/`,
/// content to a sibling file under `~/.codewhale/tool_outputs/`,
/// replaces `result.content` with a [`SPILLOVER_HEAD_BYTES`] head
/// plus a footer pointing the model at the spillover file, and
/// stamps `metadata.spillover_path` so the UI can render its
@@ -286,10 +285,10 @@ pub fn apply_spillover(result: &mut ToolResult, tool_id: &str) -> Option<PathBuf
/// Apply spillover and emit a session-scoped artifact reference.
///
/// The legacy `~/.deepseek/tool_outputs/<tool-id>.txt` file is still written
/// The home-level `tool_outputs/<tool-id>.txt` file is still written
/// so `retrieve_tool_result ref=<tool-id>` keeps working during the
/// transition. The canonical artifact content is also written under
/// `~/.deepseek/sessions/<session-id>/artifacts/`, and the inline tool result
/// `~/.codewhale/sessions/<session-id>/artifacts/`, and the inline tool result
/// becomes a fixed-format artifact reference block.
pub fn apply_spillover_with_artifact(
result: &mut ToolResult,
@@ -503,7 +502,7 @@ fn sanitise_id(id: &str) -> Option<String> {
}
/// Override the storage roots for tests so they don't pollute the
/// user's real `~/.deepseek/` directory. This uses explicit test hooks instead
/// user's real `~/.codewhale/` directory. This uses explicit test hooks instead
/// of `$HOME` because Windows home-dir resolution can ignore environment
/// overrides and return the runner profile directory.
#[cfg(test)]
@@ -531,9 +530,9 @@ where
// artifact guard above protects the session-artifact root shared with
// artifacts.rs tests.
let prior_spillover =
set_test_spillover_root(Some(home.join(".deepseek").join(SPILLOVER_DIR_NAME)));
set_test_spillover_root(Some(home.join(".codewhale").join(SPILLOVER_DIR_NAME)));
let prior_artifacts = crate::artifacts::set_test_artifact_sessions_root(Some(
home.join(".deepseek").join("sessions"),
home.join(".codewhale").join("sessions"),
));
let _restore = StorageRootOverride {
prior_spillover,
@@ -564,7 +563,7 @@ mod tests {
with_test_home(tmp.path(), || {
assert_eq!(
spillover_root().as_deref(),
Some(tmp.path().join(".deepseek").join("tool_outputs").as_path())
Some(tmp.path().join(".codewhale").join("tool_outputs").as_path())
);
assert_eq!(
crate::artifacts::session_artifact_absolute_path(
@@ -574,7 +573,7 @@ mod tests {
.as_deref(),
Some(
tmp.path()
.join(".deepseek")
.join(".codewhale")
.join("sessions")
.join("session-123")
.join("artifacts")
@@ -605,7 +604,7 @@ mod tests {
assert!(path.exists(), "{path:?} missing");
let body = fs::read_to_string(&path).unwrap();
assert_eq!(body, "hello world");
// Directory landed under `<HOME>/.deepseek/tool_outputs/`.
// Directory landed under `<HOME>/.codewhale/tool_outputs/`.
// Compare components instead of a substring on `to_string_lossy`
// — Windows uses `\` as the separator so a `/` substring match
// would falsely fail there.
@@ -614,8 +613,8 @@ mod tests {
.filter_map(|c| c.as_os_str().to_str())
.collect();
assert!(
components.contains(&".deepseek") && components.contains(&"tool_outputs"),
"spillover path missing expected `.deepseek/tool_outputs/...` segments: {path:?}"
components.contains(&".codewhale") && components.contains(&"tool_outputs"),
"spillover path missing expected `.codewhale/tool_outputs/...` segments: {path:?}"
);
});
}
@@ -829,7 +828,7 @@ mod tests {
let session_artifact = tmp
.path()
.join(".deepseek")
.join(".codewhale")
.join("sessions")
.join("session-123")
.join("artifacts")
@@ -838,9 +837,9 @@ mod tests {
assert_eq!(fs::read_to_string(&session_artifact).unwrap(), big);
assert!(
tmp.path()
.join(".deepseek/tool_outputs/call-big.txt")
.join(".codewhale/tool_outputs/call-big.txt")
.exists(),
"legacy spillover file should remain during transition"
"home-level spillover file should remain during transition"
);
assert!(result.content.starts_with("[artifact: exec_shell]"));
+3 -3
View File
@@ -261,7 +261,7 @@ pub fn open_url(url: &str) -> Result<()> {
///
/// Wraps the future in `AssertUnwindSafe` + `catch_unwind`. On panic:
/// 1. Logs the panic with the task name and caller location via `tracing::error!`.
/// 2. Writes a crash dump to `~/.deepseek/crashes/<timestamp>-<name>.log`.
/// 2. Writes a crash dump to `~/.codewhale/crashes/<timestamp>-<name>.log`.
///
/// The returned `JoinHandle` resolves to `()` — the panic is caught and
/// handled internally so the parent process stays alive.
@@ -295,7 +295,7 @@ where
})
}
/// Write a panic dump file to `~/.deepseek/crashes/`.
/// Write a panic dump file to `~/.codewhale/crashes/`.
///
/// Creates the directory if needed and writes a timestamped log
/// with the task name, caller location, and panic message.
@@ -346,7 +346,7 @@ fn write_panic_dump_to(
/// CPU-bound or blocking-I/O task must run off the async runtime and its
/// completion is *not* awaited — for example a post-turn disk snapshot or a
/// file-tree build polled later via a shared data structure. If the closure
/// panics, a crash dump is written to `~/.deepseek/crashes/` and the panic
/// panics, a crash dump is written to `~/.codewhale/crashes/` and the panic
/// is logged at ERROR level rather than being silently swallowed.
#[track_caller]
pub fn spawn_blocking_supervised<F>(name: &'static str, f: F) -> tokio::task::JoinHandle<()>
+15 -15
View File
@@ -198,12 +198,12 @@ drives turns through Chat Completions.
### Crash Recovery + Offline Queue
1. Before sending user input, the TUI writes a checkpoint snapshot to `~/.deepseek/sessions/checkpoints/latest.json`
1. Before sending user input, the TUI writes a checkpoint snapshot to `~/.codewhale/sessions/checkpoints/latest.json`
2. Startup remains fresh by default; prior sessions are resumed explicitly via `--resume`/`--continue` (or `Ctrl+R` in TUI)
3. While degraded/offline, new prompts are queued in-memory and mirrored to `~/.deepseek/sessions/checkpoints/offline_queue.json`
3. While degraded/offline, new prompts are queued in-memory and mirrored to `~/.codewhale/sessions/checkpoints/offline_queue.json`
4. Queue edits (`/queue ...`) are persisted continuously so drafts and queued prompts survive restarts
5. Successful turn completion clears the active checkpoint and writes a durable session snapshot
6. Agent/Yolo turns also take pre/post-turn side-git workspace snapshots under `~/.deepseek/snapshots/<project_hash>/<worktree_hash>/.git`; `/restore N` and `revert_turn` restore file state without changing conversation history or the user's `.git`
6. Agent/Yolo turns also take pre/post-turn side-git workspace snapshots under `~/.codewhale/snapshots/<project_hash>/<worktree_hash>/.git`; `/restore N` and `revert_turn` restore file state without changing conversation history or the user's `.git`
### Tool Execution
@@ -221,7 +221,7 @@ drives turns through Chat Completions.
### Background Tasks
1. Client enqueues task (`/task add ...` or `POST /v1/tasks`)
2. `task_manager.rs` persists task + queue entry under `~/.deepseek/tasks`
2. `task_manager.rs` persists task + queue entry under `~/.codewhale/tasks`
3. Worker picks queued task (bounded pool), transitions to `running`
4. Task creates/uses a runtime thread and starts a runtime turn
5. `runtime_threads.rs` persists thread/turn/item records + monotonic event sequence
@@ -261,7 +261,7 @@ ordinary durable tasks.
### Adding an MCP Server
1. Configure in `~/.deepseek/mcp.json`
1. Configure in `~/.codewhale/mcp.json`
2. Server auto-discovered at startup
3. Tools exposed to LLM automatically
@@ -269,11 +269,11 @@ ordinary durable tasks.
1. Create skill directory with `SKILL.md`
2. Define skill prompt and optional scripts
3. Place in `~/.deepseek/skills/`
3. Place in `~/.codewhale/skills/`
### Adding Hooks
Configure in `~/.deepseek/config.toml`:
Configure in `~/.codewhale/config.toml`:
```toml
[[hooks]]
@@ -295,13 +295,13 @@ command = "echo 'Running tool: $TOOL_NAME'"
## Configuration Files
- `~/.deepseek/config.toml` - Main configuration
- `~/.codewhale/config.toml` - Main configuration (`~/.deepseek/config.toml` is still read as a legacy fallback)
- `/etc/deepseek/managed_config.toml` - Optional managed defaults layer (Unix)
- `/etc/deepseek/requirements.toml` - Optional allowed-policy constraints (Unix)
- `~/.deepseek/mcp.json` - MCP server configuration
- `~/.deepseek/skills/` - User skills directory
- `~/.deepseek/sessions/` - Session history
- `~/.deepseek/sessions/checkpoints/` - Crash checkpoint + offline queue persistence
- `~/.deepseek/snapshots/` - Side-git pre/post-turn workspace snapshots for `/restore` and `revert_turn`
- `~/.deepseek/tasks/` - Background task records, queue, timelines, artifacts
- `~/.deepseek/audit.log` - Append-only audit events for credential + approval/elevation actions
- `~/.codewhale/mcp.json` - MCP server configuration
- `~/.codewhale/skills/` - User skills directory
- `~/.codewhale/sessions/` - Session history
- `~/.codewhale/sessions/checkpoints/` - Crash checkpoint + offline queue persistence
- `~/.codewhale/snapshots/` - Side-git pre/post-turn workspace snapshots for `/restore` and `revert_turn`
- `~/.codewhale/tasks/` - Background task records, queue, timelines, artifacts
- `~/.codewhale/audit.log` - Append-only audit events for credential + approval/elevation actions
+9 -5
View File
@@ -68,8 +68,9 @@ provider's keyring entry.
For hosted, generic OpenAI-compatible, or self-hosted providers, set
`provider = "nvidia-nim"`, `"openai"`, `"atlascloud"`, `"wanjie-ark"`,
`"openrouter"`, `"xiaomi-mimo"`, `"novita"`, `"fireworks"`, `"siliconflow"`, `"moonshot"`,
`"sglang"`, `"vllm"`, or `"ollama"` or pass `codewhale --provider <name>`.
`"volcengine"`, `"openrouter"`, `"xiaomi-mimo"`, `"novita"`, `"fireworks"`,
`"siliconflow"`, `"moonshot"`, `"sglang"`, `"vllm"`, or `"ollama"` or pass
`codewhale --provider <name>`.
For the provider-by-provider registry, including auth variables, default base
URLs, model IDs, and capability metadata, see [PROVIDERS.md](PROVIDERS.md).
The facade saves provider credentials to the shared user config and forwards
@@ -298,6 +299,9 @@ Remaining variables:
- `WANJIE_ARK_API_KEY`, `WANJIE_API_KEY`, or `WANJIE_MAAS_API_KEY`
- `WANJIE_ARK_BASE_URL`, `WANJIE_BASE_URL`, or `WANJIE_MAAS_BASE_URL`
- `WANJIE_ARK_MODEL`, `WANJIE_MODEL`, or `WANJIE_MAAS_MODEL`
- `VOLCENGINE_API_KEY`, `VOLCENGINE_ARK_API_KEY`, or `ARK_API_KEY`
- `VOLCENGINE_BASE_URL`, `VOLCENGINE_ARK_BASE_URL`, or `ARK_BASE_URL`
- `VOLCENGINE_MODEL` or `VOLCENGINE_ARK_MODEL`
- `OPENROUTER_API_KEY`
- `OPENROUTER_BASE_URL`
- `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, or `MIMO_API_KEY`
@@ -346,8 +350,8 @@ Remaining variables:
and by TUI startup update checks when `[update].update_uri` is not set, or as
a fallback when that configured URI cannot be fetched)
- `DEEPSEEK_AUTOMATIONS_DIR` (override the automations storage directory; uses
`~/.codewhale/automations` when that directory exists, otherwise the legacy
`~/.deepseek/automations` path)
`~/.codewhale/automations` by default, with legacy `~/.deepseek/automations`
fallback when only the legacy directory exists)
- `DEEPSEEK_CAPACITY_ENABLED`
- `DEEPSEEK_CAPACITY_LOW_RISK_MAX`
- `DEEPSEEK_CAPACITY_MEDIUM_RISK_MAX`
@@ -465,7 +469,7 @@ the JSON stdout contract applies only to `message_submit`.
### Composer stash (`/stash`, Ctrl+S)
Press **Ctrl+S** in the composer to park the current draft to
`~/.deepseek/composer_stash.jsonl`. `/stash list` shows parked
`~/.codewhale/composer_stash.jsonl`. `/stash list` shows parked
drafts with one-line previews and timestamps; `/stash pop`
restores the most recently parked draft (LIFO); `/stash clear`
wipes the file. Capped at 200 entries; multiline drafts
+24 -24
View File
@@ -16,7 +16,7 @@ docker volume create codewhale-home
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v codewhale-home:/home/codewhale/.deepseek \
-v codewhale-home:/home/codewhale/.codewhale \
-v "$PWD:/workspace" \
-w /workspace \
ghcr.io/hmbown/codewhale:latest
@@ -27,7 +27,7 @@ Use a pinned release tag for reproducible installs:
```bash
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v codewhale-home:/home/codewhale/.deepseek \
-v codewhale-home:/home/codewhale/.codewhale \
-v "$PWD:/workspace" \
-w /workspace \
ghcr.io/hmbown/codewhale:vX.Y.Z
@@ -45,7 +45,7 @@ images:
- the image does not grant passwordless `sudo`
- the image is meant to run CodeWhale against mounted workspaces, not to mutate
the base operating system at runtime
- user state belongs in a volume mounted at `/home/codewhale/.deepseek`
- user state belongs in a volume mounted at `/home/codewhale/.codewhale`
That default is intentional. Keep using it for the smallest trust boundary. If a
project needs `apt-get`, compiler toolchains, Node/Python package managers,
@@ -78,7 +78,7 @@ docker volume create codewhale-my-project-home
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v codewhale-my-project-home:/home/codewhale/.deepseek \
-v codewhale-my-project-home:/home/codewhale/.codewhale \
-v "$PWD:/workspace" \
-w /workspace \
codewhale-toolbox:my-project
@@ -109,7 +109,7 @@ docker compose -f docs/examples/compose.toolbox.yml run --rm codewhale
```
Use a different `CODEWHALE_TOOLBOX_IMAGE` and `CODEWHALE_HOME_VOLUME` for each
project that needs an independent toolchain or independent `.deepseek` state.
project that needs an independent toolchain or independent `.codewhale` state.
The Compose file also shows opt-in, read-only mounts for SSH material and local
CA certificates; keep those commented out unless the project needs them.
@@ -126,7 +126,7 @@ docker volume create "codewhale-${project}-home"
docker run --rm -it \
--name "codewhale-${project}" \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v "codewhale-${project}-home:/home/codewhale/.deepseek" \
-v "codewhale-${project}-home:/home/codewhale/.codewhale" \
-v "$PWD:/workspace" \
-w /workspace \
"$image"
@@ -139,18 +139,17 @@ it is intentionally outside the core Docker image.
## Project bootstrap scripts
CodeWhale does not automatically execute `.deepseek/setup.sh` or
`.codewhale/setup.sh`. If you keep one of those files as a local project
recipe, run it explicitly. For shared team setup, prefer a committed project
script or the toolbox Dockerfile so the environment can be reviewed and
rebuilt.
CodeWhale does not automatically execute `.codewhale/setup.sh` or legacy
`.deepseek/setup.sh`. If you keep one of those files as a local project recipe,
run it explicitly. For shared team setup, prefer a committed project script or
the toolbox Dockerfile so the environment can be reviewed and rebuilt.
For example, to run a committed bootstrap script before starting CodeWhale:
```bash
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v codewhale-my-project-home:/home/codewhale/.deepseek \
-v codewhale-my-project-home:/home/codewhale/.codewhale \
-v "$PWD:/workspace" \
-w /workspace \
--entrypoint bash \
@@ -182,7 +181,7 @@ container start:
```bash
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v codewhale-my-project-home:/home/codewhale/.deepseek \
-v codewhale-my-project-home:/home/codewhale/.codewhale \
-v "$PWD:/workspace" \
-v "$PWD/docker/certs:/usr/local/share/ca-certificates/local:ro" \
-w /workspace \
@@ -207,7 +206,7 @@ Then run it with the same Docker-managed data volume:
```bash
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v codewhale-home:/home/codewhale/.deepseek \
-v codewhale-home:/home/codewhale/.codewhale \
-v "$PWD:/workspace" \
-w /workspace \
codewhale
@@ -226,13 +225,14 @@ registry.
## Volumes
Mount `/home/codewhale/.deepseek` to persist sessions, config, skills, memory,
and the offline queue across container restarts. A Docker-managed named volume
is the safest default because Docker creates it with ownership the container can
write:
Mount `/home/codewhale/.codewhale` to persist sessions, config, skills, memory,
and the offline queue across container restarts. The image also keeps
`/home/codewhale/.deepseek` available for legacy compatibility. A
Docker-managed named volume is the safest default because Docker creates it with
ownership the container can write:
```bash
-v codewhale-home:/home/codewhale/.deepseek
-v codewhale-home:/home/codewhale/.codewhale
```
Without this mount the container starts fresh each time.
@@ -240,20 +240,20 @@ Without this mount the container starts fresh each time.
If you bind-mount an existing host directory instead, the image runs as the
non-root `codewhale` user with UID/GID `1000:1000`. The mounted directory must be
writable by that user, or startup can fail while creating runtime directories
under `.deepseek/tasks`. On Linux hosts, either use the named volume above or
under `.codewhale/tasks`. On Linux hosts, either use the named volume above or
prepare the bind mount explicitly:
```bash
mkdir -p ~/.deepseek
sudo chown -R 1000:1000 ~/.deepseek
mkdir -p ~/.codewhale
sudo chown -R 1000:1000 ~/.codewhale
docker run --rm -it \
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
-v ~/.deepseek:/home/codewhale/.deepseek \
-v ~/.codewhale:/home/codewhale/.codewhale \
ghcr.io/hmbown/codewhale:latest
```
That `chown` changes ownership of the host `~/.deepseek` directory. Skip it if
That `chown` changes ownership of the host `~/.codewhale` directory. Skip it if
you do not want the container UID to own your local config, and use a named
volume instead.
+1 -1
View File
@@ -126,5 +126,5 @@ When `[memory] enabled = true`, typing `# foo` and pressing `Enter` appends `foo
- **Ctrl-S is stash, not history search.** Fixed in this revision — `Alt-R` is history search.
- **Phantom `Alt+Up` removed.** The "Edit last queued message" binding was listed in README but never existed in the key dispatch code.
- **Bare Up/Down arrows scroll transcript when composer empty (v0.8.13).** Previously the `should_scroll_with_arrows` gate was hardcoded to false, meaning bare arrows always navigated composer history even when the composer was empty. Users in virtual terminals (Ghostty, Codex, Kitty-protocol) were especially affected because they couldn't use Cmd+Up / Alt+Up shortcuts.
- **Configurable keymap (#436) and `tui.toml` (#437) remain deferred.** The `TuiPrefs` struct and loader exist in `settings.rs` but are not wired at startup. The named-binding registry that would let `~/.deepseek/tui.toml` override individual entries is still pending.
- **Configurable keymap (#436) and `tui.toml` (#437) remain deferred.** The `TuiPrefs` struct and loader exist in `settings.rs` but are not wired at startup. The named-binding registry that would let `~/.codewhale/tui.toml` override individual entries is still pending.
- **No other broken bindings found.** Every other chord listed above resolves to a live handler in `crates/tui/src/tui/ui.rs` (key-event dispatch) or `crates/tui/src/tui/app.rs` (mode + state transitions).
+3 -3
View File
@@ -65,7 +65,7 @@ restart-required until the TUI is restarted.
Default path:
- `~/.deepseek/mcp.json`
- `~/.codewhale/mcp.json` (`~/.deepseek/mcp.json` is still read when the CodeWhale file is absent)
Overrides:
@@ -142,7 +142,7 @@ Options:
### Manual Config
Equivalent manual entry in `~/.deepseek/mcp.json`:
Equivalent manual entry in `~/.codewhale/mcp.json`:
```json
{
@@ -182,7 +182,7 @@ For example, the `shell` tool becomes `mcp_deepseek_shell`.
|---|---|---|---|
| **Protocol** | MCP stdio | HTTP/SSE JSON-RPC | ACP stdio |
| **Use case** | Tool server for MCP clients | Runtime API for apps | Editor agent for Zed/custom ACP clients |
| **Config** | `~/.deepseek/mcp.json` entry | Direct URL connection | Editor `agent_servers` custom command |
| **Config** | `~/.codewhale/mcp.json` entry | Direct URL connection | Editor `agent_servers` custom command |
| **Lifecycle** | Spawned per client session | Long-running daemon | Spawned per editor agent session |
Use `mcp add-self` when you want DeepSeek tools available to other MCP clients.
+14 -12
View File
@@ -23,7 +23,7 @@ export DEEPSEEK_MEMORY=on
Accepted truthy values are `1`, `on`, `true`, `yes`, `y`, and
`enabled`.
…or add to `~/.deepseek/config.toml`:
…or add to `~/.codewhale/config.toml`:
```toml
[memory]
@@ -32,10 +32,11 @@ enabled = true
Restart the TUI after toggling. Disabling is the same in reverse.
The memory file lives at `~/.deepseek/memory.md` by default; override
The memory file lives at `~/.codewhale/memory.md` by default; override
with `memory_path` in `config.toml` or `DEEPSEEK_MEMORY_PATH` in
the environment. `DEEPSEEK_MEMORY_PATH` wins over the config file when
both are set.
both are set. Existing `~/.deepseek/memory.md` files remain supported as a
legacy fallback when no `.codewhale` memory file exists.
## Quick examples
@@ -60,7 +61,7 @@ When memory is enabled and the file exists, every turn's system
prompt carries an extra block:
```xml
<user_memory source="/Users/you/.deepseek/memory.md">
<user_memory source="/Users/you/.codewhale/memory.md">
- (2026-05-03 22:14 UTC) prefer pytest over unittest
- (2026-05-03 22:31 UTC) this codebase uses 4-space indentation
@@ -166,7 +167,8 @@ note was added when grooming the file.
Memory is intentionally **user-scoped** rather than repo-scoped. It
sits alongside — not inside — project instruction sources such as
`AGENTS.md`, `.deepseek/instructions.md`, and `instructions = [...]`.
`AGENTS.md`, `.codewhale/instructions.md`, legacy `.deepseek/instructions.md`,
and `instructions = [...]`.
- Use **memory** for durable personal preferences that should follow
you across repos and sessions.
@@ -194,7 +196,7 @@ Memory is for **durable** signal. Things that should NOT live there:
## Privacy and scope
The memory file lives entirely on your machine in `~/.deepseek/`.
The memory file lives entirely on your machine in `~/.codewhale/`.
It's never uploaded to any cloud service — the TUI only ever
includes it inline in the system prompt that the LLM provider
receives, and only when memory is enabled. If you switch providers
@@ -203,24 +205,24 @@ used; the file is provider-agnostic.
The file is per-user, not per-project. If you want project-specific
memory, use the project-level `AGENTS.md` or
`.deepseek/instructions.md` files instead — those are loaded by
`project_context` and live in the repo (or wherever you commit
them).
`.codewhale/instructions.md` files instead. Legacy
`.deepseek/instructions.md` files are still loaded for compatibility. These are
loaded by `project_context` and live in the repo (or wherever you commit them).
## Configuration reference
```toml
# ~/.deepseek/config.toml
# ~/.codewhale/config.toml
[memory]
enabled = true # default false; or set DEEPSEEK_MEMORY=on
# Path is configured at the top-level (next to skills_dir, notes_path):
memory_path = "~/.deepseek/memory.md"
memory_path = "~/.codewhale/memory.md"
```
| Setting | Default | Override |
|-----------------------|-------------------------------|---------------------------------------|
| Memory enabled | `false` | `[memory] enabled = true` or `DEEPSEEK_MEMORY=on` |
| Memory file path | `~/.deepseek/memory.md` | `memory_path = "..."` or `DEEPSEEK_MEMORY_PATH=` |
| Memory file path | `~/.codewhale/memory.md` | `memory_path = "..."` or `DEEPSEEK_MEMORY_PATH=` |
| Max file size | 100 KiB | (none today; truncation marker shows the cut) |
## Related
+9 -9
View File
@@ -6,14 +6,14 @@ This runbook covers practical debugging and incident response for the local CLI/
1. Confirm binary + config:
- `cargo run -- --version`
- `cat ~/.deepseek/config.toml` (or inspect configured profile)
- `cat ~/.codewhale/config.toml` (or inspect configured profile)
2. Enable verbose logs:
- `RUST_LOG=deepseek_cli=debug cargo run`
- For HTTP retries/reconnects: `RUST_LOG=deepseek_cli::client=debug cargo run`
3. Capture current state:
- `ls ~/.deepseek/sessions`
- `ls ~/.deepseek/sessions/checkpoints`
- `ls ~/.deepseek/tasks`
- `ls ~/.codewhale/sessions`
- `ls ~/.codewhale/sessions/checkpoints`
- `ls ~/.codewhale/tasks`
## Incident: Turn Hangs or Stream Stops
@@ -38,7 +38,7 @@ Actions:
Expected behavior:
- New prompts are queued while offline mode is active
- Queue state persists to `~/.deepseek/sessions/checkpoints/offline_queue.json`
- Queue state persists to `~/.codewhale/sessions/checkpoints/offline_queue.json`
Checks:
1. Open queue in TUI: `/queue list`
@@ -52,7 +52,7 @@ Actions:
## Incident: Crash Recovery Needed
Expected behavior:
- Checkpoint stored at `~/.deepseek/sessions/checkpoints/latest.json`
- Checkpoint stored at `~/.codewhale/sessions/checkpoints/latest.json`
- Startup begins a fresh session unless `--resume`/`--continue` is supplied
Actions:
@@ -66,9 +66,9 @@ Symptoms:
- Errors like `schema vX is newer than supported vY`
Affected stores:
- sessions (`~/.deepseek/sessions/*.json`)
- sessions (`~/.codewhale/sessions/*.json`)
- runtime thread/turn/item records
- tasks (`~/.deepseek/tasks/tasks/*.json`)
- tasks (`~/.codewhale/tasks/tasks/*.json`)
Actions:
1. Confirm binary version and migration expectations
@@ -80,7 +80,7 @@ Actions:
## Incident: MCP/Tool Execution Failures
Checks:
1. Validate `~/.deepseek/mcp.json` schema and server command paths
1. Validate `~/.codewhale/mcp.json` schema and server command paths
2. Confirm server process can start manually
3. Check sandbox denials in TUI history / logs
+5 -5
View File
@@ -68,7 +68,7 @@ codewhale doctor --json
| `mcp.present` | bool | Whether MCP config exists |
| `mcp.servers` | array | Per-server health: `{name, enabled, status, detail}` |
| `skills.selected` | string | Resolved skills directory |
| `skills.global.path` / `.present` / `.count` | — | DeepSeek global skills dir (`~/.deepseek/skills`) |
| `skills.global.path` / `.present` / `.count` | — | CodeWhale global skills dir (`~/.codewhale/skills`, with legacy `~/.deepseek/skills` support) |
| `skills.agents.path` / `.present` / `.count` | — | Workspace `.agents/skills/` dir |
| `skills.agents_global.path` / `.present` / `.count` | — | agentskills.io global skills dir (`~/.agents/skills`) |
| `skills.local.path` / `.present` / `.count` | — | `skills/` dir |
@@ -86,7 +86,7 @@ codewhale doctor --json
```json
{
"version": "0.8.9",
"config_path": "/Users/you/.deepseek/config.toml",
"config_path": "/Users/you/.codewhale/config.toml",
"config_present": true,
"workspace": "/Users/you/projects/codewhale-tui",
"api_key": {
@@ -96,11 +96,11 @@ codewhale doctor --json
"default_text_model": "deepseek-v4-pro",
"memory": {
"enabled": false,
"path": "/Users/you/.deepseek/memory.md",
"path": "/Users/you/.codewhale/memory.md",
"file_present": true
},
"mcp": {
"config_path": "/Users/you/.deepseek/mcp.json",
"config_path": "/Users/you/.codewhale/mcp.json",
"present": true,
"servers": [
{"name": "filesystem", "enabled": true, "status": "ok", "detail": "ready"}
@@ -377,7 +377,7 @@ when developing a UI on Vite's default `:5173`), use any of:
- CLI flag (repeatable): `codewhale serve --http --cors-origin http://localhost:5173`
- Env var (comma-separated): `DEEPSEEK_CORS_ORIGINS="http://localhost:5173,http://localhost:8080"`
- Config (`~/.deepseek/config.toml`):
- Config (`~/.codewhale/config.toml`):
```toml
[runtime_api]
cors_origins = ["http://localhost:5173"]
+4 -4
View File
@@ -99,7 +99,7 @@ the next turn.
## Concurrency cap
The dispatcher caps concurrent sub-agents at 10 by default
(configurable via `[subagents].max_concurrent` in `~/.deepseek/config.toml`,
(configurable via `[subagents].max_concurrent` in `~/.codewhale/config.toml`,
hard ceiling 20). When the parent hits the cap, `agent_open` returns
an error with the cap value; the parent should use `agent_eval` to wait for a
running agent to complete, or `agent_close` to cancel a running agent, before
@@ -117,7 +117,7 @@ per-step timeout so a single stuck request can't pin the parent's
completion wakeup channel indefinitely. The default is `120` seconds,
which matches the legacy hardcoded value. Long-thinking children that
legitimately exceed that, for example heavy plan or review work behind
`agent_open`, can extend the timeout in `~/.deepseek/config.toml`:
`agent_open`, can extend the timeout in `~/.codewhale/config.toml`:
```toml
[subagents]
@@ -137,7 +137,7 @@ Pending → Running → (Completed | Failed(reason) | Cancelled | Interrupted(re
`Interrupted` fires when the manager detects a `Running` agent whose task
handle is gone — typically after a process restart that loaded the workspace's
persisted state from `.deepseek/state/subagents.v1.json`. The parent can open a
persisted state from `.codewhale/state/subagents.v1.json`. The parent can open a
replacement session with the same assignment or treat it as a terminal state.
### Session boundaries (#405)
@@ -185,7 +185,7 @@ don't go through the standard write-approval flow.
## Implementation notes
- Source: `crates/tui/src/tools/subagent/mod.rs`.
- Persisted state: `<workspace>/.deepseek/state/subagents.v1.json`. Schema
- Persisted state: `<workspace>/.codewhale/state/subagents.v1.json`. Schema
version `1` (forward-compatible — new optional fields use
`#[serde(default)]`).
- `SubAgentRuntime::background_runtime()` starts from `child_runtime()` but
+1 -1
View File
@@ -26,7 +26,7 @@ chosen over the available shell equivalent. Companion to `crates/tui/src/prompts
| `write_file` | Create or overwrite a file. |
| `edit_file` | Search-and-replace inside a single file. Cheaper than a full rewrite. |
| `apply_patch` | Apply a unified diff. The right tool for multi-hunk edits. |
| `retrieve_tool_result` | Read summaries or slices of prior large tool outputs spilled to `~/.deepseek/tool_outputs/`; use `summary`, `head`, `tail`, `lines`, or `query` instead of replaying the whole result. |
| `retrieve_tool_result` | Read summaries or slices of prior large tool outputs spilled to `~/.codewhale/tool_outputs/`; use `summary`, `head`, `tail`, `lines`, or `query` instead of replaying the whole result. |
| `handle_read` | Read bounded projections from `var_handle` payloads held by live tool environments. This is the foundation for RLM sessions, sub-agent transcripts, and other large symbolic payloads. |
### Search
+2 -2
View File
@@ -112,8 +112,8 @@ the model input budget.
Path:
- `DEEPSEEK_CAPACITY_MEMORY_DIR` (if set)
- otherwise `~/.deepseek/memory/<session_id>.jsonl`
- fallback: `<workspace>/.deepseek/memory/<session_id>.jsonl` when home path is unavailable/unwritable
- otherwise `~/.codewhale/memory/<session_id>.jsonl`
- fallback: existing `~/.deepseek/memory/<session_id>.jsonl` or workspace-local `.codewhale` / legacy `.deepseek` memory paths when needed
Record fields:
+1 -1
View File
@@ -24,7 +24,7 @@ services:
- DEEPSEEK_BASE_URL
- DEEPSEEK_NO_COLOR
volumes:
- codewhale-home:/home/codewhale/.deepseek
- codewhale-home:/home/codewhale/.codewhale
- ${CODEWHALE_WORKSPACE:?set CODEWHALE_WORKSPACE to the project directory}:/workspace
# Mount SSH material only for projects that need it, and prefer read-only.
# - ${HOME}/.ssh:/home/codewhale/.ssh:ro
+6 -4
View File
@@ -35,10 +35,12 @@ codewhale doctor
codewhale
```
The `codewhale` facade and `codewhale-tui` binary share `~/.deepseek/config.toml`
for DeepSeek auth and default model settings. Common TUI commands are available
directly through the facade, including `codewhale doctor`, `codewhale models`,
`codewhale sessions`, and `codewhale resume --last`.
The `codewhale` facade and `codewhale-tui` binary share
`~/.codewhale/config.toml` for DeepSeek auth and default model settings. Legacy
`~/.deepseek/config.toml` installs are still read as a compatibility fallback.
Common TUI commands are available directly through the facade, including
`codewhale doctor`, `codewhale models`, `codewhale sessions`, and
`codewhale resume --last`.
The app talks to DeepSeek's documented OpenAI-compatible Chat Completions API.
Set `DEEPSEEK_BASE_URL` only if you need the China endpoint or DeepSeek beta
+1 -1
View File
@@ -11,7 +11,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
const { locale } = await params;
const isZh = locale === "zh";
return {
title: isZh ? "CodeWhale · DeepSeek V4 智能体运行框架" : "CodeWhale · DeepSeek V4 智能体运行框架",
title: isZh ? "CodeWhale · DeepSeek V4 智能体运行框架" : "CodeWhale · DeepSeek V4 Agent Harness",
description: isZh
? "DeepSeek V4 的最强智能体运行框架。宪政层级、结构化信任、验证与恢复——让模型持续工作并不断进步的规则、工具和反馈循环。国际开源社区,递归自改进。"
: "The most agentic harness for DeepSeek V4. Constitutional hierarchy, structured trust, verification, and recovery — rules, tools, and feedback loops that help the model keep working. An international open source community building a recursive, self-improving harness.",
+1 -1
View File
@@ -33,7 +33,7 @@ const cjk = Noto_Serif_SC({
});
export const metadata: Metadata = {
title: "CodeWhale · DeepSeek V4 智能体运行框架",
title: "CodeWhale · DeepSeek V4 Agent Harness",
description:
"The most agentic harness for DeepSeek V4. Constitutional hierarchy, structured trust, verification, and recovery — rules, tools, and feedback loops that help the model keep working.",
metadataBase: new URL("https://codewhale.net"),
+1 -1
View File
@@ -136,7 +136,7 @@ export function Footer({ locale = "en" }: { locale?: Locale }) {
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 font-mono text-[0.7rem] text-ink-mute uppercase tracking-widest">
<span>© {new Date().getFullYear()} · CodeWhale · Hmbown</span>
<span className="font-cjk normal-case tracking-normal">
{isZh ? "本网站由 DeepSeek V4-Flash 协助维护" : "本网站由 DeepSeek V4-Flash 协同维护"}
{isZh ? "本网站由 DeepSeek V4-Flash 协助维护" : "Maintained with DeepSeek V4-Flash"}
</span>
</div>
</div>
+3 -3
View File
@@ -48,9 +48,9 @@ const en = {
],
tagline:
"Open-model terminal-native coding agent. DeepSeek V4 is first-class. MIT licensed. Maintained from a small workshop in Texas. Pull requests welcome.",
crafted: "Made with care · 用心制作",
poweredBy: "本网站由 DeepSeek V4-Flash 协同维护",
mirrors: "镜像源 / Mirror",
crafted: "Made with care",
poweredBy: "Maintained with DeepSeek V4-Flash",
mirrors: "Mirrors",
},
localeSwitch: { en: "EN", zh: "中文" },
};
+3 -1
View File
@@ -1,7 +1,9 @@
export default {
const redirectWorker = {
fetch(request: Request): Response {
const url = new URL(request.url);
url.host = "codewhale.net";
return Response.redirect(url.toString(), 301);
},
};
export default redirectWorker;