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