From 2b69f4e0412e719e86c46c8565820f8ac7313779 Mon Sep 17 00:00:00 2001 From: Hunter B Date: Sun, 31 May 2026 19:07:23 -0700 Subject: [PATCH] chore: polish codewhale home defaults --- Dockerfile | 3 +- README.ja-JP.md | 7 +- README.vi.md | 8 +- README.zh-CN.md | 5 +- config.example.toml | 32 ++-- crates/tui/src/artifacts.rs | 8 +- crates/tui/src/audit.rs | 2 +- crates/tui/src/automation_manager.rs | 10 +- crates/tui/src/commands/config.rs | 111 +++++++++++++- crates/tui/src/commands/core.rs | 2 +- crates/tui/src/commands/hooks.rs | 4 +- crates/tui/src/commands/memory.rs | 2 +- crates/tui/src/commands/skills.rs | 12 +- crates/tui/src/commands/user_commands.rs | 41 +++-- crates/tui/src/composer_stash.rs | 11 +- crates/tui/src/config.rs | 144 +++++++++++++++++- crates/tui/src/core/capacity_memory.rs | 15 +- crates/tui/src/localization.rs | 4 +- crates/tui/src/main.rs | 18 ++- crates/tui/src/memory.rs | 2 +- crates/tui/src/network_policy.rs | 15 +- crates/tui/src/skills/install.rs | 2 +- crates/tui/src/tools/tool_result_retrieval.rs | 17 ++- crates/tui/src/tools/truncate.rs | 47 +++--- crates/tui/src/utils.rs | 6 +- docs/ARCHITECTURE.md | 30 ++-- docs/CONFIGURATION.md | 14 +- docs/DOCKER.md | 48 +++--- docs/KEYBINDINGS.md | 2 +- docs/MCP.md | 6 +- docs/MEMORY.md | 26 ++-- docs/OPERATIONS_RUNBOOK.md | 18 +-- docs/RUNTIME_API.md | 10 +- docs/SUBAGENTS.md | 8 +- docs/TOOL_SURFACE.md | 2 +- docs/capacity_controller.md | 4 +- docs/examples/compose.toolbox.yml | 2 +- npm/codewhale/README.md | 10 +- web/app/[locale]/layout.tsx | 2 +- web/app/layout.tsx | 2 +- web/components/footer.tsx | 2 +- web/lib/i18n/dictionaries/en.ts | 6 +- web/redirect/src/index.ts | 4 +- 43 files changed, 509 insertions(+), 215 deletions(-) diff --git a/Dockerfile b/Dockerfile index 73b24efa..2a3d7352 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.ja-JP.md b/README.ja-JP.md index a8bc867a..f3379501 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -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 のエンドポイント | diff --git a/README.vi.md b/README.vi.md index 4ca618d3..ab4d6b3c 100644 --- a/README.vi.md +++ b/README.vi.md @@ -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 | diff --git a/README.zh-CN.md b/README.zh-CN.md index 25cffab2..d1adce36 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -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 端点覆盖 | diff --git a/config.example.toml b/config.example.toml index e92996e4..ea08c775 100644 --- a/config.example.toml +++ b/config.example.toml @@ -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:` and `post-turn:` snapshot of # your workspace into a side-git repo at: # -# ~/.deepseek/snapshots///.git +# ~/.codewhale/snapshots///.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. # diff --git a/crates/tui/src/artifacts.rs b/crates/tui/src/artifacts.rs index adb2647b..3d021d28 100644 --- a/crates/tui/src/artifacts.rs +++ b/crates/tui/src/artifacts.rs @@ -79,7 +79,13 @@ fn artifact_sessions_root() -> Option { 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)] diff --git a/crates/tui/src/audit.rs b/crates/tui/src/audit.rs index 2638131d..9ba0c574 100644 --- a/crates/tui/src/audit.rs +++ b/crates/tui/src/audit.rs @@ -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. diff --git a/crates/tui/src/automation_manager.rs b/crates/tui/src/automation_manager.rs index 79bc8765..982a15e6 100644 --- a/crates/tui/src/automation_manager.rs +++ b/crates/tui/src/automation_manager.rs @@ -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")) } diff --git a/crates/tui/src/commands/config.rs b/crates/tui/src/commands/config.rs index 20a592b8..5f753edf 100644 --- a/crates/tui/src/commands/config.rs +++ b/crates/tui/src/commands/config.rs @@ -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 { 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, userprofile: Option, + codewhale_config_path: Option, deepseek_config_path: Option, _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() diff --git a/crates/tui/src/commands/core.rs b/crates/tui/src/commands/core.rs index 1154338d..aff43d65 100644 --- a/crates/tui/src/commands/core.rs +++ b/crates/tui/src/commands/core.rs @@ -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 \n\nSwitch to a named config profile. Profiles are defined in ~/.deepseek/config.toml under [profiles] sections.", + "Usage: /profile \n\nSwitch to a named config profile. Profiles are defined in ~/.codewhale/config.toml under [profiles] sections.", ); } }; diff --git a/crates/tui/src/commands/hooks.rs b/crates/tui/src/commands/hooks.rs index d9589c6c..52650029 100644 --- a/crates/tui/src/commands/hooks.rs +++ b/crates/tui/src/commands/hooks.rs @@ -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.", ); } diff --git a/crates/tui/src/commands/memory.rs b/crates/tui/src/commands/memory.rs index 78bd4480..0c9af71a 100644 --- a/crates/tui/src/commands/memory.rs +++ b/crates/tui/src/commands/memory.rs @@ -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.", ); } diff --git a/crates/tui/src/commands/skills.rs b/crates/tui/src/commands/skills.rs index a8a4997f..7e311906 100644 --- a/crates/tui/src/commands/skills.rs +++ b/crates/tui/src/commands/skills.rs @@ -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 = 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//`). +/// local cache (`~/.codewhale/cache/skills//`). /// /// 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( diff --git a/crates/tui/src/commands/user_commands.rs b/crates/tui/src/commands/user_commands.rs index 48260bfe..207fdc8f 100644 --- a/crates/tui/src/commands/user_commands.rs +++ b/crates/tui/src/commands/user_commands.rs @@ -1,5 +1,5 @@ -//! User-defined slash commands from `~/.deepseek/commands/.md` and -//! workspace-local `/.deepseek/commands/.md`. +//! User-defined slash commands from `~/.codewhale/commands/.md` and +//! workspace-local `/.codewhale/commands/.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. `/.deepseek/commands/` (project-local, highest) -//! 2. `/.claude/commands/` (Claude Code interop) -//! 3. `/.cursor/commands/` (Cursor interop) -//! 4. `~/.deepseek/commands/` (user-global, lowest) +//! 1. `/.codewhale/commands/` (project-local, highest) +//! 2. `/.deepseek/commands/` (legacy project-local) +//! 3. `/.claude/commands/` (Claude Code interop) +//! 4. `/.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 { 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; diff --git a/crates/tui/src/composer_stash.rs b/crates/tui/src/composer_stash.rs index 846c73ba..5f2face7 100644 --- a/crates/tui/src/composer_stash.rs +++ b/crates/tui/src/composer_stash.rs @@ -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 { - 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 diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index e7713206..505dbd4a 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -2591,7 +2591,11 @@ fn home_config_path() -> Option { 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 { + 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 { 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 { 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 { 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, userprofile: Option, codewhale_home: Option, + codewhale_config_path: Option, deepseek_config_path: Option, codewhale_secret_backend: Option, deepseek_secret_backend: Option, @@ -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(); diff --git a/crates/tui/src/core/capacity_memory.rs b/crates/tui/src/core/capacity_memory.rs index 0d22e4df..d559051a 100644 --- a/crates/tui/src/core/capacity_memory.rs +++ b/crates/tui/src/core/capacity_memory.rs @@ -56,20 +56,25 @@ fn capacity_memory_dirs() -> Vec { 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 diff --git a/crates/tui/src/localization.rs b/crates/tui/src/localization.rs index 338ab330..5e67b571 100644 --- a/crates/tui/src/localization.rs +++ b/crates/tui/src/localization.rs @@ -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)" diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 6dc33945..71bf8214 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -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 { diff --git a/crates/tui/src/memory.rs b/crates/tui/src/memory.rs index 92fa7396..65533bd9 100644 --- a/crates/tui/src/memory.rs +++ b/crates/tui/src/memory.rs @@ -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 `` block, and prepend it to the system //! prompt alongside the existing `` block. diff --git a/crates/tui/src/network_policy.rs b/crates/tui/src/network_policy.rs index a36ef2e9..b8c767b4 100644 --- a/crates/tui/src/network_policy.rs +++ b/crates/tui/src/network_policy.rs @@ -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 `` (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 { 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(); diff --git a/crates/tui/src/skills/install.rs b/crates/tui/src/skills/install.rs index 787b6c4a..8262aa37 100644 --- a/crates/tui/src/skills/install.rs +++ b/crates/tui/src/skills/install.rs @@ -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( diff --git a/crates/tui/src/tools/tool_result_retrieval.rs b/crates/tui/src/tools/tool_result_retrieval.rs index b697d752..507f906b 100644 --- a/crates/tui/src/tools/tool_result_retrieval.rs +++ b/crates/tui/src/tools/tool_result_retrieval.rs @@ -1,7 +1,7 @@ //! `retrieve_tool_result` - selective retrieval for spilled tool outputs. //! //! Large successful tool results are spilled to -//! `~/.deepseek/tool_outputs/.txt` by `tools::truncate`. This +//! `~/.codewhale/tool_outputs/.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 ``), 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 ``), 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_`), SHA ref (`sha:<64-hex>`), spillover filename, or absolute path under ~/.deepseek." + "description": "Tool call id, artifact id (`art_`), 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_.txt`. /// 4. `tool_result:` — `` is any of the above after the prefix. /// 5. `artifacts/.txt` or `.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 { - 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//

` — so passing the literal + // `~/.codewhale/sessions//

` — 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 (`` instead @@ -202,7 +203,7 @@ fn resolve_spillover_reference(reference: &str, session_id: &str) -> Result.txt`. The id is the tool +//! `~/.codewhale/tool_outputs/.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> = 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 { 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.txt` file is still written +/// The home-level `tool_outputs/.txt` file is still written /// so `retrieve_tool_result ref=` keeps working during the /// transition. The canonical artifact content is also written under -/// `~/.deepseek/sessions//artifacts/`, and the inline tool result +/// `~/.codewhale/sessions//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 { } /// 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 `/.deepseek/tool_outputs/`. + // Directory landed under `/.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]")); diff --git a/crates/tui/src/utils.rs b/crates/tui/src/utils.rs index e7ea299e..5f91ca25 100644 --- a/crates/tui/src/utils.rs +++ b/crates/tui/src/utils.rs @@ -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/-.log`. +/// 2. Writes a crash dump to `~/.codewhale/crashes/-.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(name: &'static str, f: F) -> tokio::task::JoinHandle<()> diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ac2e9f13..73c5f158 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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///.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///.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 diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 9344b830..7e90b046 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -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 `. +`"volcengine"`, `"openrouter"`, `"xiaomi-mimo"`, `"novita"`, `"fireworks"`, +`"siliconflow"`, `"moonshot"`, `"sglang"`, `"vllm"`, or `"ollama"` or pass +`codewhale --provider `. 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 diff --git a/docs/DOCKER.md b/docs/DOCKER.md index fa1c075f..2d8b6674 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -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. diff --git a/docs/KEYBINDINGS.md b/docs/KEYBINDINGS.md index e95bc38a..3e7892ed 100644 --- a/docs/KEYBINDINGS.md +++ b/docs/KEYBINDINGS.md @@ -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). diff --git a/docs/MCP.md b/docs/MCP.md index 00cca407..3d8b58a5 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -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. diff --git a/docs/MEMORY.md b/docs/MEMORY.md index e91402c2..f8dc22ef 100644 --- a/docs/MEMORY.md +++ b/docs/MEMORY.md @@ -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 - + - (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 diff --git a/docs/OPERATIONS_RUNBOOK.md b/docs/OPERATIONS_RUNBOOK.md index 04b21e75..d53a8a45 100644 --- a/docs/OPERATIONS_RUNBOOK.md +++ b/docs/OPERATIONS_RUNBOOK.md @@ -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 diff --git a/docs/RUNTIME_API.md b/docs/RUNTIME_API.md index fd144c70..9ebfada8 100644 --- a/docs/RUNTIME_API.md +++ b/docs/RUNTIME_API.md @@ -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"] diff --git a/docs/SUBAGENTS.md b/docs/SUBAGENTS.md index 60ecbddf..8ef1fd8a 100644 --- a/docs/SUBAGENTS.md +++ b/docs/SUBAGENTS.md @@ -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: `/.deepseek/state/subagents.v1.json`. Schema +- Persisted state: `/.codewhale/state/subagents.v1.json`. Schema version `1` (forward-compatible — new optional fields use `#[serde(default)]`). - `SubAgentRuntime::background_runtime()` starts from `child_runtime()` but diff --git a/docs/TOOL_SURFACE.md b/docs/TOOL_SURFACE.md index e8cf6f4d..ae53fe62 100644 --- a/docs/TOOL_SURFACE.md +++ b/docs/TOOL_SURFACE.md @@ -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 diff --git a/docs/capacity_controller.md b/docs/capacity_controller.md index 988150c8..803e31d1 100644 --- a/docs/capacity_controller.md +++ b/docs/capacity_controller.md @@ -112,8 +112,8 @@ the model input budget. Path: - `DEEPSEEK_CAPACITY_MEMORY_DIR` (if set) -- otherwise `~/.deepseek/memory/.jsonl` -- fallback: `/.deepseek/memory/.jsonl` when home path is unavailable/unwritable +- otherwise `~/.codewhale/memory/.jsonl` +- fallback: existing `~/.deepseek/memory/.jsonl` or workspace-local `.codewhale` / legacy `.deepseek` memory paths when needed Record fields: diff --git a/docs/examples/compose.toolbox.yml b/docs/examples/compose.toolbox.yml index 49b1451e..0cd7c9ec 100644 --- a/docs/examples/compose.toolbox.yml +++ b/docs/examples/compose.toolbox.yml @@ -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 diff --git a/npm/codewhale/README.md b/npm/codewhale/README.md index 0e57173e..d6a8c44b 100644 --- a/npm/codewhale/README.md +++ b/npm/codewhale/README.md @@ -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 diff --git a/web/app/[locale]/layout.tsx b/web/app/[locale]/layout.tsx index 8eb0b1e7..86c5f8c1 100644 --- a/web/app/[locale]/layout.tsx +++ b/web/app/[locale]/layout.tsx @@ -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.", diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 214aceef..2955c63f 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -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"), diff --git a/web/components/footer.tsx b/web/components/footer.tsx index 62a7b8c7..34184b21 100644 --- a/web/components/footer.tsx +++ b/web/components/footer.tsx @@ -136,7 +136,7 @@ export function Footer({ locale = "en" }: { locale?: Locale }) {

© {new Date().getFullYear()} · CodeWhale · Hmbown - {isZh ? "本网站由 DeepSeek V4-Flash 协助维护" : "本网站由 DeepSeek V4-Flash 协同维护"} + {isZh ? "本网站由 DeepSeek V4-Flash 协助维护" : "Maintained with DeepSeek V4-Flash"}
diff --git a/web/lib/i18n/dictionaries/en.ts b/web/lib/i18n/dictionaries/en.ts index 8675e619..c7331ec9 100644 --- a/web/lib/i18n/dictionaries/en.ts +++ b/web/lib/i18n/dictionaries/en.ts @@ -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: "中文" }, }; diff --git a/web/redirect/src/index.ts b/web/redirect/src/index.ts index 07f803ab..8b474639 100644 --- a/web/redirect/src/index.ts +++ b/web/redirect/src/index.ts @@ -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;