Merge remote-tracking branch 'origin/rebrand/r5-prompts-strings' into work/v0.8.41-codewhale-ready

This commit is contained in:
Hunter Bown
2026-05-23 13:18:55 -05:00
32 changed files with 308 additions and 308 deletions
+4 -4
View File
@@ -143,7 +143,7 @@ impl AcpServer {
.and_then(Value::as_str)
.map(PathBuf::from)
.unwrap_or_else(|| self.default_cwd.clone());
let session_id = format!("deepseek-{}", uuid::Uuid::new_v4());
let session_id = format!("codewhale-{}", uuid::Uuid::new_v4());
self.sessions.insert(session_id.clone(), AcpSession { cwd });
Ok(json!({ "sessionId": session_id }))
}
@@ -284,8 +284,8 @@ fn initialize_result(client_protocol_version: Option<u64>) -> Value {
"sessionCapabilities": {}
},
"agentInfo": {
"name": "deepseek",
"title": "DeepSeek TUI",
"name": "codewhale",
"title": "codewhale",
"version": env!("CARGO_PKG_VERSION")
},
"authMethods": []
@@ -423,7 +423,7 @@ mod tests {
let result = initialize_result(Some(1));
assert_eq!(result["protocolVersion"], 1);
assert_eq!(result["agentInfo"]["name"], "deepseek");
assert_eq!(result["agentInfo"]["name"], "codewhale");
assert_eq!(result["agentCapabilities"]["loadSession"], false);
assert_eq!(
result["agentCapabilities"]["promptCapabilities"]["embeddedContext"],
+8 -8
View File
@@ -976,7 +976,7 @@ pub struct AutoRouteSelection {
}
pub const AUTO_MODEL_ROUTER_SYSTEM_PROMPT: &str = "\
You are the DeepSeek TUI auto-routing classifier. Return only compact JSON: \
You are the codewhale auto-routing classifier. Return only compact JSON: \
{\"model\":\"deepseek-v4-flash|deepseek-v4-pro\",\"thinking\":\"off|high|max\"}. \
Use deepseek-v4-flash for trivial, conversational, status, or single-step work. \
Use deepseek-v4-pro for coding, debugging, release work, multi-step tasks, high-risk decisions, \
@@ -1706,7 +1706,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-default-mode-test-{}-{}",
"codewhale-tui-default-mode-test-{}-{}",
std::process::id(),
nanos
));
@@ -1731,7 +1731,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-cost-currency-test-{}-{}",
"codewhale-tui-cost-currency-test-{}-{}",
std::process::id(),
nanos
));
@@ -1757,7 +1757,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-theme-command-test-{}-{}",
"codewhale-tui-theme-command-test-{}-{}",
std::process::id(),
nanos
));
@@ -1780,7 +1780,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-theme-save-test-{}-{}",
"codewhale-tui-theme-save-test-{}-{}",
std::process::id(),
nanos
));
@@ -1884,7 +1884,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-logout-test-{}-{}",
"codewhale-tui-logout-test-{}-{}",
std::process::id(),
nanos
));
@@ -1933,7 +1933,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-statusline-persist-{}-{}",
"codewhale-statusline-persist-{}-{}",
std::process::id(),
nanos
));
@@ -1964,7 +1964,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-statusline-preserve-{}-{}",
"codewhale-statusline-preserve-{}-{}",
std::process::id(),
nanos
));
+2 -2
View File
@@ -782,7 +782,7 @@ mod tests {
let result = home_dashboard(&mut app);
assert!(result.message.is_some());
let msg = result.message.unwrap();
assert!(msg.contains("DeepSeek TUI Home Dashboard"));
assert!(msg.contains("codewhale Home Dashboard"));
assert!(msg.contains("Model:"));
assert!(msg.contains("Mode:"));
assert!(msg.contains("Workspace:"));
@@ -831,7 +831,7 @@ mod tests {
!msg.lines()
.any(|line| line.trim_start().starts_with("/set "))
);
assert!(!msg.contains("/deepseek"));
assert!(!msg.contains("/codewhale"));
}
#[test]
+6 -6
View File
@@ -101,7 +101,7 @@ fn render_session_html(history_json: &str, model: &str, mode: &str) -> String {
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeepSeek TUI Session Export</title>
<title>codewhale Session Export</title>
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -119,14 +119,14 @@ fn render_session_html(history_json: &str, model: &str, mode: &str) -> String {
</style>
</head>
<body>
<h1>DeepSeek TUI Session</h1>
<h1>codewhale Session</h1>
<div class="meta">
<strong>Model:</strong> {escaped_model} · <strong>Mode:</strong> {escaped_mode}<br>
<strong>Exported:</strong> {timestamp}
</div>
<pre>{escaped_body}</pre>
<div class="footer">
Generated by DeepSeek TUI · https://github.com/Hmbown/DeepSeek-TUI
Generated by codewhale · https://github.com/Hmbown/DeepSeek-TUI
</div>
</body>
</html>"#,
@@ -145,7 +145,7 @@ fn html_escape(s: &str) -> String {
/// Write HTML to a secure temp file and keep it alive for upload.
fn write_temp_html(html: &str) -> Result<tempfile::NamedTempFile, String> {
let mut tmp = tempfile::Builder::new()
.prefix("deepseek-share-")
.prefix("codewhale-share-")
.suffix(".html")
.tempfile()
.map_err(|e| format!("{e}"))?;
@@ -164,7 +164,7 @@ async fn upload_gist(path: &Path) -> Result<String, String> {
"--filename",
"session-export.html",
"--desc",
"DeepSeek TUI Session Export",
"codewhale Session Export",
])
.output()
.await
@@ -194,7 +194,7 @@ mod tests {
assert!(html.contains("deepseek-v4-pro"));
assert!(html.contains("agent"));
assert!(html.contains("[{}]"));
assert!(html.contains("DeepSeek TUI"));
assert!(html.contains("codewhale"));
}
#[test]
+2 -2
View File
@@ -18,7 +18,7 @@ fn format_status(app: &App) -> String {
let mut out = String::new();
let (context_used, context_max, context_percent) = context_usage(app);
let _ = writeln!(out, "DeepSeek TUI Status");
let _ = writeln!(out, "codewhale Status");
let _ = writeln!(out, "===================");
let _ = writeln!(out);
push_row(&mut out, "Version:", env!("CARGO_PKG_VERSION"));
@@ -227,7 +227,7 @@ mod tests {
let result = status(&mut app);
let msg = result.message.expect("status message");
assert!(msg.contains("DeepSeek TUI Status"));
assert!(msg.contains("codewhale Status"));
assert!(msg.contains("Provider:"));
assert!(msg.contains("Model:"));
assert!(msg.contains("Directory:"));
+73 -73
View File
@@ -1,4 +1,4 @@
//! Configuration loading and defaults for DeepSeek TUI.
//! Configuration loading and defaults for codewhale.
use std::collections::HashMap;
use std::fmt::Write;
@@ -969,7 +969,7 @@ pub struct Config {
#[serde(default)]
pub hooks: Option<HooksConfig>,
/// Provider-specific credentials and defaults shared with the `deepseek` facade.
/// Provider-specific credentials and defaults shared with the `codewhale` facade.
#[serde(default)]
pub providers: Option<ProvidersConfig>,
@@ -1024,7 +1024,7 @@ pub struct Config {
#[serde(default)]
pub subagents: Option<SubagentsConfig>,
/// Runtime API server tuning (`deepseek serve --http`). Currently only
/// Runtime API server tuning (`codewhale serve --http`). Currently only
/// hosts the CORS allow-list extension (whalescale#255 / #561). When the
/// table is absent, the daemon ships with localhost:3000 / localhost:1420
/// / tauri://localhost as the only allowed dev origins.
@@ -1656,7 +1656,7 @@ impl Config {
}
// 1. Config file (provider-scoped slot). This intentionally wins
// over ambient env so `deepseek auth set` fixes stale shell exports.
// over ambient env so `codewhale auth set` fixes stale shell exports.
if let Some(configured) = self
.provider_config_for(provider)
.and_then(|provider| provider.api_key.clone())
@@ -1683,7 +1683,7 @@ impl Config {
\n\
1. Get a key: https://platform.deepseek.com/api_keys\n\
2. Save it (works in every folder, no OS prompts):\n\
deepseek auth set --provider deepseek\n\
codewhale auth set --provider deepseek\n\
\n\
Alternatives:\n\
• export DEEPSEEK_API_KEY=<your-key> (current shell only;\n\
@@ -1692,33 +1692,33 @@ impl Config {
• api_key = \"<your-key>\" in ~/.deepseek/config.toml"
),
ApiProvider::NvidiaNim => anyhow::bail!(
"NVIDIA NIM API key not found. Run 'deepseek auth set --provider nvidia-nim', \
"NVIDIA NIM API key not found. Run 'codewhale auth set --provider nvidia-nim', \
set NVIDIA_API_KEY/NVIDIA_NIM_API_KEY, or save api_key in ~/.deepseek/config.toml \
with provider = \"nvidia-nim\"."
),
ApiProvider::Openai => anyhow::bail!(
"OpenAI-compatible API key not found. Run 'deepseek auth set --provider openai', \
"OpenAI-compatible API key not found. Run 'codewhale auth set --provider openai', \
set OPENAI_API_KEY, or add [providers.openai] api_key in ~/.deepseek/config.toml."
),
ApiProvider::Atlascloud => anyhow::bail!(
"AtlasCloud API key not found. Run 'deepseek auth set --provider atlascloud', \
"AtlasCloud API key not found. Run 'codewhale auth set --provider atlascloud', \
set ATLASCLOUD_API_KEY, or add [providers.atlascloud] api_key in ~/.deepseek/config.toml."
),
ApiProvider::WanjieArk => anyhow::bail!(
"Wanjie Ark API key not found. Run 'deepseek auth set --provider wanjie-ark', \
"Wanjie Ark API key not found. Run 'codewhale auth set --provider wanjie-ark', \
set WANJIE_ARK_API_KEY/WANJIE_API_KEY/WANJIE_MAAS_API_KEY, or add \
[providers.wanjie_ark] api_key in ~/.deepseek/config.toml."
),
ApiProvider::Openrouter => anyhow::bail!(
"OpenRouter API key not found. Run 'deepseek auth set --provider openrouter', \
"OpenRouter API key not found. Run 'codewhale auth set --provider openrouter', \
set OPENROUTER_API_KEY, or add [providers.openrouter] api_key in ~/.deepseek/config.toml."
),
ApiProvider::Novita => anyhow::bail!(
"Novita API key not found. Run 'deepseek auth set --provider novita', \
"Novita API key not found. Run 'codewhale auth set --provider novita', \
set NOVITA_API_KEY, or add [providers.novita] api_key in ~/.deepseek/config.toml."
),
ApiProvider::Fireworks => anyhow::bail!(
"Fireworks AI API key not found. Run 'deepseek auth set --provider fireworks', \
"Fireworks AI API key not found. Run 'codewhale auth set --provider fireworks', \
set FIREWORKS_API_KEY, or add [providers.fireworks] api_key in ~/.deepseek/config.toml."
),
// Self-hosted deployments commonly run without auth on localhost.
@@ -2120,7 +2120,7 @@ fn resolve_load_config_path(path: Option<PathBuf>) -> Option<PathBuf> {
/// Create an inspectable config file on first interactive launch.
///
/// The file intentionally omits `api_key`; onboarding or `deepseek auth set`
/// The file intentionally omits `api_key`; onboarding or `codewhale auth set`
/// writes that field after the user supplies a key.
pub fn ensure_config_file_exists(path: Option<PathBuf>) -> Result<Option<PathBuf>> {
let config_path = path
@@ -2133,9 +2133,9 @@ pub fn ensure_config_file_exists(path: Option<PathBuf>) -> Result<Option<PathBuf
ensure_parent_dir(&config_path)?;
let content = format!(
r#"# DeepSeek TUI Configuration
r#"# codewhale Configuration
# Get your API key from https://platform.deepseek.com
# Save it with: deepseek auth set --provider deepseek
# Save it with: codewhale auth set --provider deepseek
# Base URL (default: https://api.deepseek.com/beta)
# Set https://api.deepseek.com to opt out of beta features.
@@ -3128,7 +3128,7 @@ pub fn ensure_parent_dir(path: &Path) -> Result<()> {
perms.set_mode(mode & !0o077);
if let Err(err) = fs::set_permissions(parent, perms) {
tracing::warn!(
target: "deepseek::config",
target: "codewhale::config",
path = %parent.display(),
error = %err,
"could not tighten parent dir permissions; \
@@ -3166,7 +3166,7 @@ fn write_config_file_secure(path: &Path, content: &str) -> Result<()> {
// system's native ACL model is doing the access control.
if let Err(err) = file.set_permissions(fs::Permissions::from_mode(0o600)) {
tracing::warn!(
target: "deepseek::config",
target: "codewhale::config",
path = %path.display(),
error = %err,
"could not enforce 0o600 on config file; filesystem may \
@@ -3186,7 +3186,7 @@ fn write_config_file_secure(path: &Path, content: &str) -> Result<()> {
/// the caller can show a confirmation message without leaking the key.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SavedCredential {
/// Stored in **both** the OS keyring and the deepseek config file.
/// Stored in **both** the OS keyring and the codewhale config file.
/// This is the default outcome on platforms with a working keyring
/// backend: writing both layers defeats the
/// `keyring → env → config-file` resolution-order shadow that
@@ -3201,7 +3201,7 @@ pub enum SavedCredential {
/// Absolute path to the config file that was also updated.
path: PathBuf,
},
/// Stored in the deepseek config file only. Fallback when no
/// Stored in the codewhale config file only. Fallback when no
/// keyring backend is reachable, or under `cfg(test)` so unit
/// tests don't pollute the host keyring.
ConfigFile(PathBuf),
@@ -3324,7 +3324,7 @@ fn save_api_key_to_config_file(api_key: &str) -> Result<PathBuf> {
} else {
// Create new minimal config
format!(
r#"# DeepSeek TUI Configuration
r#"# codewhale Configuration
# Get your API key from https://platform.deepseek.com
# Or set DEEPSEEK_API_KEY environment variable
@@ -4063,7 +4063,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-test-{}-{}",
"codewhale-tui-test-{}-{}",
std::process::id(),
nanos
));
@@ -4099,7 +4099,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-first-run-config-{}-{}",
"codewhale-tui-first-run-config-{}-{}",
std::process::id(),
nanos
));
@@ -4125,7 +4125,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-workspace-trust-{}-{}",
"codewhale-tui-workspace-trust-{}-{}",
std::process::id(),
nanos
));
@@ -4161,7 +4161,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-existing-project-trust-{}-{}",
"codewhale-tui-existing-project-trust-{}-{}",
std::process::id(),
nanos
));
@@ -4364,7 +4364,7 @@ mod tests {
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-clear-{}-{}",
"codewhale-tui-clear-{}-{}",
std::process::id(),
nanos
));
@@ -4397,7 +4397,7 @@ api_key = "old-openrouter-key"
);
assert!(
!after.contains("old-provider-key"),
"provider-scoped deepseek key must be stripped: {after}"
"provider-scoped codewhale key must be stripped: {after}"
);
assert!(
!after.contains("old-openrouter-key"),
@@ -4420,7 +4420,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-override-{}-{}",
"codewhale-tui-override-{}-{}",
std::process::id(),
nanos
));
@@ -4446,7 +4446,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-config-over-env-{}-{}",
"codewhale-tui-config-over-env-{}-{}",
std::process::id(),
nanos
));
@@ -4471,7 +4471,7 @@ api_key = "old-openrouter-key"
fn active_provider_detects_env_only_api_key() -> Result<()> {
let _lock = lock_test_env();
let temp_root =
env::temp_dir().join(format!("deepseek-tui-env-only-key-{}", std::process::id()));
env::temp_dir().join(format!("codewhale-tui-env-only-key-{}", std::process::id()));
fs::create_dir_all(&temp_root)?;
let _guard = EnvGuard::new(&temp_root);
@@ -4501,7 +4501,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-sentinel-{}-{}",
"codewhale-tui-sentinel-{}-{}",
std::process::id(),
nanos
));
@@ -4529,7 +4529,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-tilde-test-{}-{}",
"codewhale-tui-tilde-test-{}-{}",
std::process::id(),
nanos
));
@@ -4558,7 +4558,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-load-tilde-test-{}-{}",
"codewhale-tui-load-tilde-test-{}-{}",
std::process::id(),
nanos
));
@@ -4587,7 +4587,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-load-fallback-test-{}-{}",
"codewhale-tui-load-fallback-test-{}-{}",
std::process::id(),
nanos
));
@@ -4646,7 +4646,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-api-key-test-{}-{}",
"codewhale-tui-api-key-test-{}-{}",
std::process::id(),
nanos
));
@@ -4693,7 +4693,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-empty-key-{}-{}",
"codewhale-tui-empty-key-{}-{}",
std::process::id(),
nanos
));
@@ -4726,7 +4726,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-env-key-not-config-{}-{}",
"codewhale-tui-env-key-not-config-{}-{}",
std::process::id(),
nanos
));
@@ -4839,7 +4839,7 @@ api_key = "old-openrouter-key"
#[test]
fn normalize_model_name_rejects_invalid_or_non_deepseek_ids() {
assert!(normalize_model_name("gpt-4o").is_none());
assert!(normalize_model_name("deepseek v4").is_none());
assert!(normalize_model_name("codewhale v4").is_none());
assert!(normalize_model_name("").is_none());
}
@@ -4980,7 +4980,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-model-env-test-{}-{}",
"codewhale-tui-model-env-test-{}-{}",
std::process::id(),
nanos
));
@@ -5009,7 +5009,7 @@ api_key = "old-openrouter-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-http-headers-root-{}-{}",
"codewhale-tui-http-headers-root-{}-{}",
std::process::id(),
nanos
));
@@ -5073,7 +5073,7 @@ http_headers = { "X-Model-Provider-Id" = "tongyi" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-http-headers-env-{}-{}",
"codewhale-tui-http-headers-env-{}-{}",
std::process::id(),
nanos
));
@@ -5127,7 +5127,7 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-nim-model-alias-test-{}-{}",
"codewhale-tui-nim-model-alias-test-{}-{}",
std::process::id(),
nanos
));
@@ -5171,7 +5171,7 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-nim-env-test-{}-{}",
"codewhale-tui-nim-env-test-{}-{}",
std::process::id(),
nanos
));
@@ -5200,7 +5200,7 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-nim-base-url-alias-test-{}-{}",
"codewhale-tui-nim-base-url-alias-test-{}-{}",
std::process::id(),
nanos
));
@@ -5227,7 +5227,7 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-nim-forwarded-base-url-test-{}-{}",
"codewhale-tui-nim-forwarded-base-url-test-{}-{}",
std::process::id(),
nanos
));
@@ -5285,7 +5285,7 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-atlascloud-env-test-{}-{}",
"codewhale-tui-atlascloud-env-test-{}-{}",
std::process::id(),
nanos
));
@@ -5329,7 +5329,7 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-wanjie-env-test-{}-{}",
"codewhale-tui-wanjie-env-test-{}-{}",
std::process::id(),
nanos
));
@@ -5359,7 +5359,7 @@ http_headers = { "X-Model-Provider-Id" = "from-file" }
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-wanjie-table-{}-{}",
"codewhale-tui-wanjie-table-{}-{}",
std::process::id(),
nanos
));
@@ -5398,7 +5398,7 @@ model = "account-model-id"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-openai-table-{}-{}",
"codewhale-tui-openai-table-{}-{}",
std::process::id(),
nanos
));
@@ -5429,7 +5429,7 @@ model = "glm-5"
Ok(())
}
// Regression for issue #1714: `deepseek --provider openai --model
// Regression for issue #1714: `codewhale --provider openai --model
// MiniMax-M2.7` forwards the choice via DEEPSEEK_MODEL (never
// OPENAI_MODEL) and uses the DEFAULT base_url. The explicit custom model
// must pass through verbatim instead of silently becoming a
@@ -5442,7 +5442,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-1714-passthrough-{}-{}",
"codewhale-tui-1714-passthrough-{}-{}",
std::process::id(),
nanos
));
@@ -5495,7 +5495,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-openai-env-test-{}-{}",
"codewhale-tui-openai-env-test-{}-{}",
std::process::id(),
nanos
));
@@ -5529,7 +5529,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-openai-forwarded-base-url-test-{}-{}",
"codewhale-tui-openai-forwarded-base-url-test-{}-{}",
std::process::id(),
nanos
));
@@ -5563,7 +5563,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-or-defaults-{}-{}",
"codewhale-tui-or-defaults-{}-{}",
std::process::id(),
nanos
));
@@ -5589,7 +5589,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-novita-defaults-{}-{}",
"codewhale-tui-novita-defaults-{}-{}",
std::process::id(),
nanos
));
@@ -5615,7 +5615,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-fireworks-defaults-{}-{}",
"codewhale-tui-fireworks-defaults-{}-{}",
std::process::id(),
nanos
));
@@ -5641,7 +5641,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-sglang-defaults-{}-{}",
"codewhale-tui-sglang-defaults-{}-{}",
std::process::id(),
nanos
));
@@ -5669,7 +5669,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-ollama-defaults-{}-{}",
"codewhale-tui-ollama-defaults-{}-{}",
std::process::id(),
nanos
));
@@ -5697,7 +5697,7 @@ model = "glm-5"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-ollama-model-test-{}-{}",
"codewhale-tui-ollama-model-test-{}-{}",
std::process::id(),
nanos
));
@@ -5731,7 +5731,7 @@ model = "qwen2.5-coder:7b"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-self-hosted-base-url-test-{}-{}",
"codewhale-tui-self-hosted-base-url-test-{}-{}",
std::process::id(),
nanos
));
@@ -5766,7 +5766,7 @@ model = "qwen2.5-coder:7b"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-ollama-env-test-{}-{}",
"codewhale-tui-ollama-env-test-{}-{}",
std::process::id(),
nanos
));
@@ -5795,7 +5795,7 @@ model = "qwen2.5-coder:7b"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-or-env-key-{}-{}",
"codewhale-tui-or-env-key-{}-{}",
std::process::id(),
nanos
));
@@ -5822,7 +5822,7 @@ model = "qwen2.5-coder:7b"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-novita-env-key-{}-{}",
"codewhale-tui-novita-env-key-{}-{}",
std::process::id(),
nanos
));
@@ -5849,7 +5849,7 @@ model = "qwen2.5-coder:7b"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-or-base-url-{}-{}",
"codewhale-tui-or-base-url-{}-{}",
std::process::id(),
nanos
));
@@ -5876,7 +5876,7 @@ model = "qwen2.5-coder:7b"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-or-table-{}-{}",
"codewhale-tui-or-table-{}-{}",
std::process::id(),
nanos
));
@@ -5910,7 +5910,7 @@ base_url = "https://or-table.example/v1"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-or-custom-model-{}-{}",
"codewhale-tui-or-custom-model-{}-{}",
std::process::id(),
nanos
));
@@ -5946,7 +5946,7 @@ model = "DeepSeek-V4-Pro"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-novita-table-{}-{}",
"codewhale-tui-novita-table-{}-{}",
std::process::id(),
nanos
));
@@ -5979,7 +5979,7 @@ api_key = "novita-table-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-has-key-{}-{}",
"codewhale-tui-has-key-{}-{}",
std::process::id(),
nanos
));
@@ -6036,7 +6036,7 @@ api_key = "novita-table-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-has-key-cn-{}-{}",
"codewhale-tui-has-key-cn-{}-{}",
std::process::id(),
nanos
));
@@ -6073,7 +6073,7 @@ api_key = "novita-table-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-save-key-or-{}-{}",
"codewhale-tui-save-key-or-{}-{}",
std::process::id(),
nanos
));
@@ -6161,7 +6161,7 @@ api_key = "novita-table-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-save-key-cn-{}-{}",
"codewhale-tui-save-key-cn-{}-{}",
std::process::id(),
nanos
));
@@ -6188,7 +6188,7 @@ api_key = "novita-table-key"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-nim-provider-table-test-{}-{}",
"codewhale-tui-nim-provider-table-test-{}-{}",
std::process::id(),
nanos
));
@@ -6227,7 +6227,7 @@ model = "deepseek-v4-pro"
.unwrap()
.as_nanos();
let temp_root = env::temp_dir().join(format!(
"deepseek-tui-nim-root-key-precedence-test-{}-{}",
"codewhale-tui-nim-root-key-precedence-test-{}-{}",
std::process::id(),
nanos
));
@@ -6238,7 +6238,7 @@ model = "deepseek-v4-pro"
ensure_parent_dir(&config_path)?;
fs::write(
&config_path,
r#"api_key = "deepseek-root-key"
r#"api_key = "codewhale-root-key"
provider = "nvidia-nim"
[providers.nvidia_nim]
+7 -7
View File
@@ -283,7 +283,7 @@ pub enum StatusItemValue {
pub fn parse_mode(arg: Option<&str>) -> Result<ConfigUiMode, String> {
let raw = arg.unwrap_or("").trim();
// Bare `/config` opens the legacy native modal — it matches the rest
// of the deepseek-tui navy chrome out of the box. Power users can
// of the codewhale-tui navy chrome out of the box. Power users can
// opt into the schemaui-driven editor with `/config tui`, or the
// browser surface with `/config web` (web feature only).
if raw.is_empty() || raw.eq_ignore_ascii_case("native") {
@@ -348,7 +348,7 @@ pub fn build_document(app: &App, config: &Config) -> Result<ConfigUiDocument> {
pub fn build_schema() -> Value {
let mut schema = serde_json::to_value(schema_for!(ConfigUiDocument)).expect("config ui schema");
schema["title"] = Value::String("DeepSeek TUI Config".to_string());
schema["title"] = Value::String("codewhale Config".to_string());
schema["description"] =
Value::String("Edit runtime and persisted TUI configuration.".to_string());
schema
@@ -359,7 +359,7 @@ pub fn run_tui_editor(app: &App, config: &Config) -> Result<ConfigUiDocument> {
let document = build_document(app, config)?;
let value = SchemaUI::new(serde_json::to_value(document.clone())?)
.with_schema(build_schema())
.with_title("DeepSeek TUI Config")
.with_title("codewhale Config")
.with_description("Edit persisted settings and live runtime knobs.")
.run(FrontendOptions::Tui(
UiOptions::default()
@@ -377,7 +377,7 @@ pub async fn start_web_editor(app: &App, config: &Config) -> Result<WebConfigSes
let initial = serde_json::to_value(build_document(app, config)?)?;
let session = WebSessionBuilder::new(build_schema())
.with_initial_data(initial)
.with_title("DeepSeek TUI Config")
.with_title("codewhale Config")
.with_description("Save updates the browser draft. Exit commits changes back to the TUI.")
.build()?;
let bound = bind_session(session, ServeOptions::default()).await?;
@@ -1082,7 +1082,7 @@ mod tests {
.expect("clock")
.as_nanos();
let temp_root = std::env::temp_dir().join(format!(
"deepseek-config-ui-cost-currency-{}-{}",
"codewhale-config-ui-cost-currency-{}-{}",
std::process::id(),
nanos
));
@@ -1126,7 +1126,7 @@ cost_currency = "cny"
.expect("clock")
.as_nanos();
let temp_root = std::env::temp_dir().join(format!(
"deepseek-config-ui-background-color-{}-{}",
"codewhale-config-ui-background-color-{}-{}",
std::process::id(),
nanos
));
@@ -1208,7 +1208,7 @@ background_color = "#1A1B26"
.expect("clock")
.as_nanos();
let temp_root = std::env::temp_dir().join(format!(
"deepseek-config-ui-session-only-{}-{}",
"codewhale-config-ui-session-only-{}-{}",
std::process::id(),
nanos
));
+3 -3
View File
@@ -298,7 +298,7 @@ pub struct Engine {
/// can fan completion events back into the engine.
tx_subagent_completion: mpsc::UnboundedSender<SubAgentCompletion>,
/// Receiver paired with `tx_subagent_completion`. Drained at the
/// turn-loop's empty-tool_uses branch to surface `<deepseek:subagent.done>`
/// turn-loop's empty-tool_uses branch to surface `<codewhale:subagent.done>`
/// sentinels into the parent's transcript before deciding to end the turn.
pub(super) rx_subagent_completion: mpsc::UnboundedReceiver<SubAgentCompletion>,
cancel_token: CancellationToken,
@@ -378,8 +378,8 @@ impl Engine {
Some(format!(
"The rejected key came from {env_var}; no saved config key is present.\n\
Run `deepseek auth status` to inspect credential sources, then \
`deepseek auth set --provider {provider}` to save a valid key in ~/.deepseek/config.toml, \
Run `codewhale auth status` to inspect credential sources, then \
`codewhale auth set --provider {provider}` to save a valid key in ~/.deepseek/config.toml, \
or remove the stale export and open a fresh shell.",
provider = provider.as_str()
))
+2 -2
View File
@@ -88,7 +88,7 @@ pub(super) fn should_transparently_retry_stream(
pub(crate) const TOOL_CALL_START_MARKERS: [&str; 5] = [
"[TOOL_CALL]",
"<deepseek:tool_call",
"<codewhale:tool_call",
"<tool_call",
"<invoke ",
"<function_calls>",
@@ -96,7 +96,7 @@ pub(crate) const TOOL_CALL_START_MARKERS: [&str; 5] = [
pub(crate) const TOOL_CALL_END_MARKERS: [&str; 5] = [
"[/TOOL_CALL]",
"</deepseek:tool_call>",
"</codewhale:tool_call>",
"</tool_call>",
"</invoke>",
"</function_calls>",
+3 -3
View File
@@ -94,8 +94,8 @@ fn env_only_auth_error_gets_recovery_hint() {
assert!(message.contains("DEEPSEEK_API_KEY"));
assert!(message.contains("no saved config key is present"));
assert!(message.contains("deepseek auth status"));
assert!(message.contains("deepseek auth set --provider deepseek"));
assert!(message.contains("codewhale auth status"));
assert!(message.contains("codewhale auth set --provider deepseek"));
}
#[test]
@@ -1874,7 +1874,7 @@ fn filter_tool_call_delta_strips_bracket_marker() {
fn filter_tool_call_delta_strips_deepseek_xml_marker() {
let mut in_block = false;
let visible = filter_tool_call_delta(
"before <deepseek:tool_call name=\"x\">payload</deepseek:tool_call> after",
"before <codewhale:tool_call name=\"x\">payload</codewhale:tool_call> after",
&mut in_block,
);
assert!(!in_block);
+5 -5
View File
@@ -924,7 +924,7 @@ impl Engine {
// streaming with no tool calls — but if it has direct children
// still running (or completions queued from children that
// finished while we were inferring), surface their
// `<deepseek:subagent.done>` sentinels into the transcript and
// `<codewhale:subagent.done>` sentinels into the transcript and
// resume instead of ending the turn. This fulfils the contract
// already documented in `prompts/base.md`: the parent is
// promised it'll see the sentinel when a child finishes.
@@ -2010,13 +2010,13 @@ fn subagent_completion_runtime_message(payload: &str) -> Message {
role: "system".to_string(),
content: vec![ContentBlock::Text {
text: format!(
"<deepseek:runtime_event kind=\"subagent_completion\" visibility=\"internal\">\n\
"<codewhale:runtime_event kind=\"subagent_completion\" visibility=\"internal\">\n\
This is an internal runtime event, not user input. Use the sub-agent completion \
data below to continue coordinating the current task. Do not tell the user they \
pasted sentinels, do not explain the sentinel protocol, and do not quote the raw \
XML unless the user explicitly asks to debug sub-agent internals.\n\n\
{payload}\n\
</deepseek:runtime_event>"
</codewhale:runtime_event>"
),
cache_control: None,
}],
@@ -2109,7 +2109,7 @@ mod tests {
#[test]
fn subagent_completion_handoff_is_internal_system_message() {
let message = subagent_completion_runtime_message(
"Build passed\n<deepseek:subagent.done>{\"agent_id\":\"agent_a\"}</deepseek:subagent.done>",
"Build passed\n<codewhale:subagent.done>{\"agent_id\":\"agent_a\"}</codewhale:subagent.done>",
);
assert_eq!(message.role, "system");
@@ -2119,7 +2119,7 @@ mod tests {
};
assert!(text.contains("internal runtime event, not user input"));
assert!(text.contains("Do not tell the user they pasted sentinels"));
assert!(text.contains("<deepseek:subagent.done>"));
assert!(text.contains("<codewhale:subagent.done>"));
assert!(text.contains("Build passed"));
}
+6 -6
View File
@@ -12,11 +12,11 @@
//!
//! Or XML-style format:
//! ```text
//! <deepseek:tool_call>
//! <codewhale:tool_call>
//! <invoke name="tool_name">
//! <parameter name="arg">value</parameter>
//! </invoke>
//! </deepseek:tool_call>
//! </codewhale:tool_call>
//! ```
//!
//! This module parses these text patterns into structured tool calls.
@@ -60,8 +60,8 @@ fn get_tool_call_regex() -> &'static Regex {
fn get_xml_tool_call_regex() -> &'static Regex {
XML_TOOL_CALL_REGEX.get_or_init(|| {
// Match <deepseek:tool_call>...</deepseek:tool_call> or similar XML patterns
Regex::new(r"(?s)<(?:deepseek:)?tool_call[^>]*>\s*(.*?)\s*</(?:deepseek:)?tool_call>")
// Match <codewhale:tool_call>...</codewhale:tool_call> or similar XML patterns
Regex::new(r"(?s)<(?:codewhale:)?tool_call[^>]*>\s*(.*?)\s*</(?:codewhale:)?tool_call>")
.expect("XML tool_call regex pattern is valid")
})
}
@@ -108,7 +108,7 @@ pub fn parse_tool_calls(text: &str) -> ParseResult {
clean_text = clean_text.replace(full_match, "");
}
// Parse XML-style <deepseek:tool_call> or <tool_call> format
// Parse XML-style <codewhale:tool_call> or <tool_call> format
let xml_regex = get_xml_tool_call_regex();
for cap in xml_regex.captures_iter(text) {
let (Some(full_match), Some(inner)) = (cap.get(0), cap.get(1)) else {
@@ -443,7 +443,7 @@ fn extract_json_object(text: &str) -> Option<Value> {
/// Check if text contains tool call markers (either format).
pub fn has_tool_call_markers(text: &str) -> bool {
text.contains("[TOOL_CALL]")
|| text.contains("<deepseek:tool_call")
|| text.contains("<codewhale:tool_call")
|| text.contains("<tool_call")
|| text.contains("<invoke ")
}
+1 -1
View File
@@ -219,7 +219,7 @@ mod tests {
fn probe_executable_returns_false_for_unknown_binary() {
// Pick a name we're confident isn't on any developer's PATH.
// If this ever starts failing locally, rename it.
assert!(!probe_executable("deepseek-tui-imaginary-binary-xyz123"));
assert!(!probe_executable("codewhale-tui-imaginary-binary-xyz123"));
}
#[test]
+10 -10
View File
@@ -952,7 +952,7 @@ fn english(id: MessageId) -> &'static str {
MessageId::CmdNoteDescription => "Add, list, edit, or remove workspace notes",
MessageId::CmdThemeDescription => "Switch theme or open the theme picker",
MessageId::CmdProviderDescription => {
"Switch or view the active LLM backend (deepseek | nvidia-nim | ollama)"
"Switch or view the active LLM backend (codewhale | nvidia-nim | ollama)"
}
MessageId::CmdQueueDescription => "View or edit queued messages",
MessageId::CmdRecallDescription => "Search prior cycle archives (BM25 over message text)",
@@ -1131,7 +1131,7 @@ fn english(id: MessageId) -> &'static str {
MessageId::LinksTip => "Tip: API keys are available in the dashboard console.",
MessageId::SubagentsFetching => "Fetching sub-agent status...",
MessageId::HelpUnknownCommand => "Unknown command: {topic}",
MessageId::HomeDashboardTitle => "DeepSeek TUI Home Dashboard",
MessageId::HomeDashboardTitle => "codewhale Home Dashboard",
MessageId::HomeModel => "Model:",
MessageId::HomeMode => "Mode:",
MessageId::HomeWorkspace => "Workspace:",
@@ -1336,7 +1336,7 @@ fn japanese(id: MessageId) -> Option<&'static str> {
"テーマを切り替え(ダーク/ライト/グレースケール/システム)"
}
MessageId::CmdProviderDescription => {
"現在の LLM バックエンドを切り替え・確認(deepseek | nvidia-nim | ollama"
"現在の LLM バックエンドを切り替え・確認(codewhale | nvidia-nim | ollama"
}
MessageId::CmdQueueDescription => "キューされたメッセージを確認・編集",
MessageId::CmdRecallDescription => {
@@ -1516,7 +1516,7 @@ fn japanese(id: MessageId) -> Option<&'static str> {
MessageId::LinksTip => "ヒント: API キーはダッシュボードコンソールで取得できます。",
MessageId::SubagentsFetching => "サブエージェントの状態を取得中...",
MessageId::HelpUnknownCommand => "不明なコマンド: {topic}",
MessageId::HomeDashboardTitle => "DeepSeek TUI ホームダッシュボード",
MessageId::HomeDashboardTitle => "codewhale ホームダッシュボード",
MessageId::HomeModel => "モデル:",
MessageId::HomeMode => "モード:",
MessageId::HomeWorkspace => "ワークスペース:",
@@ -1679,7 +1679,7 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
MessageId::CmdNoteDescription => "添加、列出、编辑或删除工作区笔记",
MessageId::CmdThemeDescription => "切换主题:深色、浅色、灰度或系统",
MessageId::CmdProviderDescription => {
"切换或查看当前 LLM 后端(deepseek | nvidia-nim | ollama"
"切换或查看当前 LLM 后端(codewhale | nvidia-nim | ollama"
}
MessageId::CmdQueueDescription => "查看或编辑已排队的消息",
MessageId::CmdRecallDescription => "搜索此前的循环归档(基于消息文本的 BM25 检索)",
@@ -1833,7 +1833,7 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
MessageId::LinksTip => "提示:API 密钥可在控制台中获取。",
MessageId::SubagentsFetching => "正在获取子代理状态...",
MessageId::HelpUnknownCommand => "未知命令:{topic}",
MessageId::HomeDashboardTitle => "DeepSeek TUI 主面板",
MessageId::HomeDashboardTitle => "codewhale 主面板",
MessageId::HomeModel => "模型:",
MessageId::HomeMode => "模式:",
MessageId::HomeWorkspace => "工作区:",
@@ -2006,7 +2006,7 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
MessageId::CmdNoteDescription => "Adicionar, listar, editar ou remover notas do workspace",
MessageId::CmdThemeDescription => "Alternar tema: escuro, claro, tons de cinza ou sistema",
MessageId::CmdProviderDescription => {
"Trocar ou exibir o backend LLM ativo (deepseek | nvidia-nim | ollama)"
"Trocar ou exibir o backend LLM ativo (codewhale | nvidia-nim | ollama)"
}
MessageId::CmdQueueDescription => "Ver ou editar mensagens enfileiradas",
MessageId::CmdRecallDescription => {
@@ -2198,7 +2198,7 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
MessageId::LinksTip => "Dica: chaves de API estão disponíveis no console do painel.",
MessageId::SubagentsFetching => "Buscando status dos sub-agentes...",
MessageId::HelpUnknownCommand => "Comando desconhecido: {topic}",
MessageId::HomeDashboardTitle => "Painel Inicial do DeepSeek TUI",
MessageId::HomeDashboardTitle => "Painel Inicial do codewhale",
MessageId::HomeModel => "Modelo:",
MessageId::HomeMode => "Modo:",
MessageId::HomeWorkspace => "Workspace:",
@@ -2393,7 +2393,7 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> {
MessageId::CmdNoteDescription => "Agregar nota al archivo persistente (.deepseek/notes.md)",
MessageId::CmdThemeDescription => "Alternar entre tema claro y oscuro",
MessageId::CmdProviderDescription => {
"Cambiar o mostrar el backend LLM activo (deepseek | nvidia-nim | ollama)"
"Cambiar o mostrar el backend LLM activo (codewhale | nvidia-nim | ollama)"
}
MessageId::CmdQueueDescription => "Ver o editar mensajes en cola",
MessageId::CmdRecallDescription => {
@@ -2591,7 +2591,7 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> {
MessageId::LinksTip => "Tip: las claves de API están disponibles en la consola del panel.",
MessageId::SubagentsFetching => "Obteniendo estado de los sub-agentes...",
MessageId::HelpUnknownCommand => "Comando desconocido: {topic}",
MessageId::HomeDashboardTitle => "Panel Inicial de DeepSeek TUI",
MessageId::HomeDashboardTitle => "Panel Inicial de codewhale",
MessageId::HomeModel => "Modelo:",
MessageId::HomeMode => "Modo:",
MessageId::HomeWorkspace => "Workspace:",
+1 -1
View File
@@ -67,7 +67,7 @@ mod tests {
#[test]
fn log_value_parser_accepts_common_rust_log_directives() {
assert!(log_value_enables_verbose("debug"));
assert!(log_value_enables_verbose("deepseek_cli=debug"));
assert!(log_value_enables_verbose("codewhale_cli=debug"));
assert!(log_value_enables_verbose(
"warn,codewhale_tui::client=trace"
));
+93 -93
View File
@@ -101,12 +101,12 @@ fn configure_windows_console_utf8() {}
#[derive(Parser, Debug)]
#[command(
name = "deepseek-tui",
bin_name = "deepseek-tui",
name = "codewhale-tui",
bin_name = "codewhale-tui",
author,
version = env!("DEEPSEEK_BUILD_VERSION"),
about = "DeepSeek TUI/CLI for DeepSeek models",
long_about = "Terminal-native TUI and CLI for DeepSeek models.\n\nRun 'deepseek' to start.\n\nNot affiliated with DeepSeek Inc."
about = "codewhale/CLI for DeepSeek models",
long_about = "Terminal-native TUI and CLI for DeepSeek models.\n\nRun 'codewhale' to start.\n\nNot affiliated with DeepSeek Inc."
)]
struct Cli {
/// Subcommand to run
@@ -372,7 +372,7 @@ fn resolve_exec_resume_session_id(args: &ExecArgs, workspace: &Path) -> Result<O
latest_session_id_for_workspace(workspace)?.map_or_else(
|| {
bail!(
"No saved sessions found for workspace {}. Use `deepseek sessions` to list sessions, or pass `deepseek exec --resume <SESSION_ID> ...`.",
"No saved sessions found for workspace {}. Use `codewhale sessions` to list sessions, or pass `codewhale exec --resume <SESSION_ID> ...`.",
workspace.display()
)
},
@@ -590,15 +590,15 @@ enum McpCommand {
Validate,
/// Register this DeepSeek binary as a local MCP stdio server.
///
/// This adds a config entry that runs `deepseek serve --mcp` (stdio protocol).
/// For the HTTP/SSE runtime API, use `deepseek serve --http` directly instead.
/// 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 `deepseek 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 `deepseek serve --http` instead if you need the HTTP/SSE runtime API."
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."
)]
AddSelf {
/// Server name in mcp.json (default: "deepseek")
#[arg(long, default_value = "deepseek")]
/// Server name in mcp.json (default: "codewhale")
#[arg(long, default_value = "codewhale")]
name: String,
/// Workspace directory for the MCP server
#[arg(long)]
@@ -894,7 +894,7 @@ async fn main() -> Result<()> {
return run_one_shot(&config, &model, &prompt).await;
}
// Handle session resume. Plain `deepseek` starts fresh: interrupted
// Handle session resume. Plain `codewhale` starts fresh: interrupted
// snapshots are preserved for explicit resume, but never auto-attached.
let resume_session_id = if cli.continue_session {
let workspace = resolve_workspace(&cli);
@@ -1087,7 +1087,7 @@ fn init_skills_dir(skills_dir: &Path, force: bool) -> Result<(PathBuf, WriteStat
fn tools_readme_template() -> &'static str {
"# Local tools\n\n\
Drop self-describing scripts here so they can be discovered by\n\
`deepseek-tui setup --status` and surfaced in `deepseek-tui doctor`.\n\n\
`codewhale-tui setup --status` and surfaced in `codewhale-tui doctor`.\n\n\
Each script should start with a frontmatter-style header so the\n\
description is visible without executing the file:\n\n\
```\n\
@@ -1105,7 +1105,7 @@ fn tools_example_script() -> &'static str {
# name: example\n\
# description: Print a confirmation that local tool discovery works\n\
# usage: example [name]\n\
printf 'deepseek-tui local tool ok: %s\\n' \"${1:-world}\"\n"
printf 'codewhale-tui local tool ok: %s\\n' \"${1:-world}\"\n"
}
fn init_tools_dir(tools_dir: &Path, force: bool) -> Result<(PathBuf, WriteStatus, WriteStatus)> {
@@ -1166,7 +1166,7 @@ fn init_plugins_dir(
Ok((readme_path, example_path, readme_status, example_status))
}
/// Resolve the user-supplied CORS origins for `deepseek serve --http`.
/// Resolve the user-supplied CORS origins for `codewhale serve --http`.
///
/// Sources, in priority order (later sources extend earlier ones):
/// 1. `--cors-origin URL` flags (repeatable)
@@ -1291,7 +1291,9 @@ fn run_setup(config: &Config, workspace: &Path, args: SetupArgs) -> Result<()> {
println!(" · MCP config already exists at {}", mcp_path.display());
}
}
println!(" Next: edit the file, then run `deepseek mcp list` or `deepseek mcp tools`.");
println!(
" Next: edit the file, then run `codewhale mcp list` or `codewhale mcp tools`."
);
}
if run_skills {
@@ -1463,45 +1465,45 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
let (env_var, login_hint) = match config.api_provider() {
crate::config::ApiProvider::NvidiaNim => (
"NVIDIA_API_KEY",
"deepseek auth set --provider nvidia-nim --api-key \"...\"",
"codewhale auth set --provider nvidia-nim --api-key \"...\"",
),
crate::config::ApiProvider::Openai => (
"OPENAI_API_KEY",
"deepseek auth set --provider openai --api-key \"...\"",
"codewhale auth set --provider openai --api-key \"...\"",
),
crate::config::ApiProvider::Atlascloud => (
"ATLASCLOUD_API_KEY",
"deepseek auth set --provider atlascloud --api-key \"...\"",
"codewhale auth set --provider atlascloud --api-key \"...\"",
),
crate::config::ApiProvider::WanjieArk => (
"WANJIE_ARK_API_KEY",
"deepseek auth set --provider wanjie-ark --api-key \"...\"",
"codewhale auth set --provider wanjie-ark --api-key \"...\"",
),
crate::config::ApiProvider::Openrouter => (
"OPENROUTER_API_KEY",
"deepseek auth set --provider openrouter --api-key \"...\"",
"codewhale auth set --provider openrouter --api-key \"...\"",
),
crate::config::ApiProvider::Novita => (
"NOVITA_API_KEY",
"deepseek auth set --provider novita --api-key \"...\"",
"codewhale auth set --provider novita --api-key \"...\"",
),
crate::config::ApiProvider::Fireworks => (
"FIREWORKS_API_KEY",
"deepseek auth set --provider fireworks --api-key \"...\"",
"codewhale auth set --provider fireworks --api-key \"...\"",
),
crate::config::ApiProvider::Sglang => (
"SGLANG_API_KEY",
"deepseek auth set --provider sglang --api-key \"...\"",
"codewhale auth set --provider sglang --api-key \"...\"",
),
crate::config::ApiProvider::Vllm => (
"VLLM_API_KEY",
"deepseek auth set --provider vllm --api-key \"...\"",
"codewhale auth set --provider vllm --api-key \"...\"",
),
crate::config::ApiProvider::Ollama => {
("OLLAMA_API_KEY", "deepseek auth set --provider ollama")
("OLLAMA_API_KEY", "codewhale auth set --provider ollama")
}
crate::config::ApiProvider::Deepseek | crate::config::ApiProvider::DeepseekCN => {
("DEEPSEEK_API_KEY", "deepseek auth set --provider deepseek")
("DEEPSEEK_API_KEY", "codewhale auth set --provider deepseek")
}
};
println!(
@@ -1596,7 +1598,7 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
println!(" {} {}", "·".dimmed(), dotenv_status_line(workspace));
println!();
println!("Run `deepseek doctor --json` for a machine-readable check.");
println!("Run `codewhale doctor --json` for a machine-readable check.");
Ok(())
}
@@ -1664,16 +1666,14 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
println!(
"{}",
"DeepSeek TUI Doctor"
.truecolor(blue_r, blue_g, blue_b)
.bold()
"codewhale Doctor".truecolor(blue_r, blue_g, blue_b).bold()
);
println!("{}", "==================".truecolor(sky_r, sky_g, sky_b));
println!();
// Version info
println!("{}", "Version Information:".bold());
println!(" deepseek-tui: {}", env!("DEEPSEEK_BUILD_VERSION"));
println!(" codewhale-tui: {}", env!("DEEPSEEK_BUILD_VERSION"));
println!(" rust: {}", rustc_version());
println!();
@@ -1836,7 +1836,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
"".truecolor(red_r, red_g, red_b)
);
println!(
" Run 'deepseek auth set --provider <name>' to save a key to ~/.deepseek/config.toml."
" Run 'codewhale auth set --provider <name>' to save a key to ~/.deepseek/config.toml."
);
false
};
@@ -1888,21 +1888,21 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
);
if error_msg.contains("401") || error_msg.contains("Unauthorized") {
println!(
" Invalid API key. Check `deepseek auth status`, DEEPSEEK_API_KEY, or config.toml"
" Invalid API key. Check `codewhale auth status`, DEEPSEEK_API_KEY, or config.toml"
);
if matches!(api_key_source, ApiKeySource::Keyring) {
println!(
" The rejected key came from the OS keyring via the dispatcher."
);
println!(
" Run `deepseek auth status` to inspect config/keyring/env sources."
" Run `codewhale auth status` to inspect config/keyring/env sources."
);
} else if matches!(api_key_source, ApiKeySource::Env) {
println!(
" The rejected key came from DEEPSEEK_API_KEY; no saved config key is present."
);
println!(
" Run `deepseek auth set --provider deepseek` to save a config key that overrides stale env."
" Run `codewhale auth set --provider deepseek` to save a config key that overrides stale env."
);
}
} else if error_msg.contains("403") || error_msg.contains("Forbidden") {
@@ -2004,7 +2004,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
"·".dimmed(),
crate::utils::display_path(&mcp_config_path)
);
println!(" Run `deepseek mcp init` or `deepseek setup --mcp`.");
println!(" Run `codewhale mcp init` or `codewhale setup --mcp`.");
}
// Skills configuration
@@ -2134,7 +2134,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
.is_some_and(|dir| dir.exists())
&& !global_skills_dir.exists()
{
println!(" Run `deepseek setup --skills` (or add --local for ./skills).");
println!(" Run `codewhale setup --skills` (or add --local for ./skills).");
}
// Tools directory
@@ -2155,7 +2155,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
"·".dimmed(),
crate::utils::display_path(&tools_dir)
);
println!(" Run `deepseek setup --tools` to scaffold a starter dir.");
println!(" Run `codewhale setup --tools` to scaffold a starter dir.");
}
// Plugins directory
@@ -2176,7 +2176,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
"·".dimmed(),
crate::utils::display_path(&plugins_dir)
);
println!(" Run `deepseek setup --plugins` to scaffold a starter dir.");
println!(" Run `codewhale setup --plugins` to scaffold a starter dir.");
}
// Storage surfaces (#422 / #440 / #500)
@@ -2707,7 +2707,7 @@ fn run_doctor_json(
},
"api_connectivity": {
"checked": false,
"note": "Skipped in --json mode; run `deepseek doctor` for a live check.",
"note": "Skipped in --json mode; run `codewhale doctor` for a live check.",
},
"capability": provider_capability_report(config),
});
@@ -2844,7 +2844,7 @@ fn doctor_timeout_recovery_lines(config: &Config) -> Vec<String> {
&& !target.base_url.contains("api.deepseeki.com") =>
{
lines.push(
"If this is a custom DeepSeek-compatible endpoint, set its HTTPS base URL in ~/.deepseek/config.toml and rerun `deepseek doctor`."
"If this is a custom DeepSeek-compatible endpoint, set its HTTPS base URL in ~/.deepseek/config.toml and rerun `codewhale doctor`."
.to_string(),
);
}
@@ -2863,7 +2863,7 @@ fn doctor_timeout_recovery_lines(config: &Config) -> Vec<String> {
}
lines.push(
"Run `deepseek doctor --json` and include `base_url`, `default_text_model`, and `api_connectivity` when filing an issue."
"Run `codewhale doctor --json` and include `base_url`, `default_text_model`, and `api_connectivity` when filing an issue."
.to_string(),
);
lines
@@ -2987,7 +2987,7 @@ fn list_sessions(limit: usize, search: Option<String>) -> Result<()> {
println!("{}", "No sessions found.".truecolor(sky_r, sky_g, sky_b));
println!(
"Start a new session with: {}",
"deepseek".truecolor(blue_r, blue_g, blue_b)
"codewhale".truecolor(blue_r, blue_g, blue_b)
);
return Ok(());
}
@@ -3020,12 +3020,12 @@ fn list_sessions(limit: usize, search: Option<String>) -> Result<()> {
println!();
println!(
"Resume with: {} {}",
"deepseek --resume".truecolor(blue_r, blue_g, blue_b),
"codewhale --resume".truecolor(blue_r, blue_g, blue_b),
"<session-id>".dimmed()
);
println!(
"Continue latest in this workspace: {}",
"deepseek --continue".truecolor(blue_r, blue_g, blue_b)
"codewhale --continue".truecolor(blue_r, blue_g, blue_b)
);
Ok(())
@@ -3062,7 +3062,7 @@ fn init_project() -> Result<()> {
);
println!();
println!("Edit this file to customize how the AI agent works with your project.");
println!("The instructions will be loaded automatically when you run deepseek.");
println!("The instructions will be loaded automatically when you run codewhale.");
}
Err(e) => {
println!(
@@ -3126,7 +3126,7 @@ fn resolve_session_id(session_id: Option<String>, last: bool, workspace: &Path)
if last {
return latest_session_id_for_workspace(workspace)?.ok_or_else(|| {
anyhow!(
"No saved sessions found for workspace {}. Use `deepseek sessions` to list all sessions, or `deepseek resume <SESSION_ID>` to resume one explicitly.",
"No saved sessions found for workspace {}. Use `codewhale sessions` to list all sessions, or `codewhale resume <SESSION_ID>` to resume one explicitly.",
workspace.display()
)
});
@@ -3289,7 +3289,7 @@ Provide findings ordered by severity with file references, then open questions,
Ok(())
}
/// `deepseek pr <N>` (#451) — fetch a GitHub PR via `gh`, format
/// `codewhale pr <N>` (#451) — fetch a GitHub PR via `gh`, format
/// title + body + diff as the composer's first message, and launch
/// the interactive TUI. Falls back gracefully if `gh` is missing.
async fn run_pr(
@@ -3303,7 +3303,7 @@ async fn run_pr(
bail!(
"`gh` CLI not found on PATH. Install GitHub CLI \
(https://cli.github.com) and authenticate (`gh auth login`) \
so `deepseek pr <N>` can fetch PR metadata and the diff."
so `codewhale pr <N>` can fetch PR metadata and the diff."
);
}
@@ -3583,7 +3583,7 @@ async fn run_mcp_command(config: &Config, command: McpCommand) -> Result<()> {
);
}
}
println!("Edit the file, then run `deepseek mcp list` or `deepseek mcp tools`.");
println!("Edit the file, then run `codewhale mcp list` or `codewhale mcp tools`.");
Ok(())
}
McpCommand::List => {
@@ -3763,7 +3763,7 @@ async fn run_mcp_command(config: &Config, command: McpCommand) -> Result<()> {
let mut cfg = load_mcp_config(&config_path)?;
if cfg.servers.contains_key(&name) {
bail!(
"MCP server '{name}' already exists in {}. Use `deepseek mcp remove {name}` first, or choose a different --name.",
"MCP server '{name}' already exists in {}. Use `codewhale mcp remove {name}` first, or choose a different --name.",
config_path.display()
);
}
@@ -3796,8 +3796,8 @@ async fn run_mcp_command(config: &Config, command: McpCommand) -> Result<()> {
workspace.map_or(String::new(), |ws| format!(" --workspace {ws}"))
);
println!();
println!("Tip: Use `deepseek mcp validate` to test the connection.");
println!(" Use `deepseek serve --http` for the HTTP/SSE runtime API instead.");
println!("Tip: Use `codewhale mcp validate` to test the connection.");
println!(" Use `codewhale serve --http` for the HTTP/SSE runtime API instead.");
Ok(())
}
}
@@ -4119,7 +4119,7 @@ fn checkpoint_age_label(age: std::time::Duration) -> String {
/// **The checkpoint's workspace must also match the resolved launch workspace
/// after canonicalisation.** If the workspace doesn't match, the checkpoint is
/// persisted as a regular session (so the user can find it via
/// `deepseek sessions` / `deepseek resume <id>`) and cleared, but not loaded.
/// `codewhale sessions` / `codewhale resume <id>`) and cleared, but not loaded.
fn recover_interrupted_checkpoint_for_resume(launch_workspace: &Path) -> Option<String> {
let manager = session_manager::SessionManager::default_location().ok()?;
let (session, age) = load_recent_checkpoint(&manager)?;
@@ -4133,7 +4133,7 @@ fn recover_interrupted_checkpoint_for_resume(launch_workspace: &Path) -> Option<
session_manager::workspace_scope_matches(&session_workspace, launch_workspace);
if !workspace_matches {
// Persist the checkpoint so the user can find it via `deepseek
// Persist the checkpoint so the user can find it via `codewhale
// sessions`, then clear it so the next launch in this folder doesn't
// re-trip the nag. Print a one-line notice pointing at the explicit
// resume command — but DO NOT auto-load the session here.
@@ -4141,7 +4141,7 @@ fn recover_interrupted_checkpoint_for_resume(launch_workspace: &Path) -> Option<
let _ = manager.clear_checkpoint();
eprintln!(
"Note: an interrupted session from another workspace ({}) is \
available. Run `deepseek sessions` to list saved sessions. Starting \
available. Run `codewhale sessions` to list saved sessions. Starting \
fresh in {}.",
session_workspace.display(),
launch_workspace.display(),
@@ -4166,7 +4166,7 @@ fn recover_interrupted_checkpoint_for_resume(launch_workspace: &Path) -> Option<
}
/// Preserve an interrupted checkpoint on a normal fresh launch without
/// attaching it to the new TUI instance. This keeps "open another deepseek in
/// attaching it to the new TUI instance. This keeps "open another codewhale in
/// the same folder" from re-entering the previous in-flight session while still
/// leaving an explicit resume path.
fn preserve_interrupted_checkpoint_for_explicit_resume(launch_workspace: &Path) {
@@ -4185,12 +4185,12 @@ fn preserve_interrupted_checkpoint_for_explicit_resume(launch_workspace: &Path)
if session_manager::workspace_scope_matches(&session_workspace, launch_workspace) {
eprintln!(
"Found an in-flight session snapshot ({age_str}). Starting a new \
session. Run `deepseek --continue` to resume it."
session. Run `codewhale --continue` to resume it."
);
} else {
eprintln!(
"Note: an interrupted session from another workspace ({}) is \
available. Run `deepseek sessions` to list saved sessions. Starting \
available. Run `codewhale sessions` to list saved sessions. Starting \
fresh in {}.",
session_workspace.display(),
launch_workspace.display(),
@@ -5205,7 +5205,7 @@ mod doctor_endpoint_tests {
assert!(text.contains("api.deepseek.com"));
assert!(text.contains("custom DeepSeek-compatible endpoint"));
assert!(!text.contains("provider = \"deepseek-cn\""));
assert!(text.contains("deepseek doctor --json"));
assert!(text.contains("codewhale doctor --json"));
}
#[test]
@@ -5234,19 +5234,19 @@ mod terminal_mode_tests {
#[test]
fn prompt_flag_accepts_split_prompt_words_for_windows_cmd_shims() {
let cli = parse_cli(&["deepseek", "-p", "hello", "world"]);
let cli = parse_cli(&["codewhale", "-p", "hello", "world"]);
assert_eq!(cli.prompt, vec!["hello", "world"]);
}
#[test]
fn companion_binary_reports_its_own_name() {
assert_eq!(Cli::command().get_name(), "deepseek-tui");
assert_eq!(Cli::command().get_name(), "codewhale-tui");
}
#[test]
fn exec_accepts_split_prompt_words_for_windows_cmd_shims() {
let cli = parse_cli(&["deepseek", "exec", "hello", "world"]);
let cli = parse_cli(&["codewhale", "exec", "hello", "world"]);
let Some(Commands::Exec(args)) = cli.command else {
panic!("expected exec command");
};
@@ -5256,7 +5256,7 @@ mod terminal_mode_tests {
#[test]
fn exec_keeps_flags_before_split_prompt_words() {
let cli = parse_cli(&["deepseek", "exec", "--json", "hello", "world"]);
let cli = parse_cli(&["codewhale", "exec", "--json", "hello", "world"]);
let Some(Commands::Exec(args)) = cli.command else {
panic!("expected exec command");
};
@@ -5268,7 +5268,7 @@ mod terminal_mode_tests {
#[test]
fn exec_accepts_resume_session_flags_for_harnesses() {
let cli = parse_cli(&[
"deepseek",
"codewhale",
"exec",
"--resume",
"abc123",
@@ -5287,7 +5287,7 @@ mod terminal_mode_tests {
#[test]
fn exec_accepts_session_id_alias() {
let cli = parse_cli(&["deepseek", "exec", "--session-id", "abc123", "follow up"]);
let cli = parse_cli(&["codewhale", "exec", "--session-id", "abc123", "follow up"]);
let Some(Commands::Exec(args)) = cli.command else {
panic!("expected exec command");
};
@@ -5298,7 +5298,7 @@ mod terminal_mode_tests {
#[test]
fn exec_accepts_continue_for_latest_workspace_session() {
let cli = parse_cli(&["deepseek", "exec", "--continue", "follow up"]);
let cli = parse_cli(&["codewhale", "exec", "--continue", "follow up"]);
let Some(Commands::Exec(args)) = cli.command else {
panic!("expected exec command");
};
@@ -5309,7 +5309,7 @@ mod terminal_mode_tests {
#[test]
fn exec_json_conflicts_with_stream_json_output() {
let err = Cli::try_parse_from([
"deepseek",
"codewhale",
"exec",
"--json",
"--output-format",
@@ -5337,7 +5337,7 @@ mod terminal_mode_tests {
#[test]
fn alternate_screen_defaults_on_in_auto_mode() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config::default();
assert!(should_use_alt_screen(&cli, &config));
@@ -5345,7 +5345,7 @@ mod terminal_mode_tests {
#[test]
fn no_alt_screen_flag_is_accepted_but_keeps_alternate_screen() {
let cli = parse_cli(&["deepseek", "--no-alt-screen"]);
let cli = parse_cli(&["codewhale", "--no-alt-screen"]);
let config = Config::default();
assert!(should_use_alt_screen(&cli, &config));
@@ -5353,7 +5353,7 @@ mod terminal_mode_tests {
#[test]
fn config_never_is_accepted_but_keeps_alternate_screen() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config {
tui: Some(crate::config::TuiConfig {
alternate_screen: Some("never".to_string()),
@@ -5373,7 +5373,7 @@ mod terminal_mode_tests {
#[test]
#[cfg(not(windows))]
fn mouse_capture_defaults_on_when_alternate_screen_is_active() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(
@@ -5387,7 +5387,7 @@ mod terminal_mode_tests {
// Legacy conhost (no `WT_SESSION` and no `ConEmuPID`) keeps the
// v0.8.x default-off behavior: mouse-mode reporting on legacy console
// can leak SGR escapes into the composer.
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config::default();
assert!(!should_use_mouse_capture_with(
@@ -5403,7 +5403,7 @@ mod terminal_mode_tests {
#[test]
#[cfg(windows)]
fn mouse_capture_defaults_on_in_windows_terminal() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(
@@ -5421,7 +5421,7 @@ mod terminal_mode_tests {
#[test]
#[cfg(windows)]
fn mouse_capture_defaults_on_in_conemu() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(
@@ -5436,7 +5436,7 @@ mod terminal_mode_tests {
#[test]
fn no_mouse_capture_flag_disables_mouse_capture() {
let cli = parse_cli(&["deepseek", "--no-mouse-capture"]);
let cli = parse_cli(&["codewhale", "--no-mouse-capture"]);
let config = Config::default();
assert!(!should_use_mouse_capture_with(
@@ -5446,7 +5446,7 @@ mod terminal_mode_tests {
#[test]
fn config_can_disable_default_mouse_capture() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config {
tui: Some(crate::config::TuiConfig {
alternate_screen: None,
@@ -5467,7 +5467,7 @@ mod terminal_mode_tests {
#[test]
fn mouse_capture_flag_enables_mouse_capture() {
let cli = parse_cli(&["deepseek", "--mouse-capture"]);
let cli = parse_cli(&["codewhale", "--mouse-capture"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(
@@ -5477,7 +5477,7 @@ mod terminal_mode_tests {
#[test]
fn config_can_enable_mouse_capture() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config {
tui: Some(crate::config::TuiConfig {
alternate_screen: None,
@@ -5498,7 +5498,7 @@ mod terminal_mode_tests {
#[test]
fn mouse_capture_is_off_without_alternate_screen() {
let cli = parse_cli(&["deepseek", "--mouse-capture"]);
let cli = parse_cli(&["codewhale", "--mouse-capture"]);
let config = Config::default();
assert!(!should_use_mouse_capture_with(
@@ -5515,7 +5515,7 @@ mod terminal_mode_tests {
#[test]
fn mouse_capture_defaults_off_in_jetbrains_jediterm() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config::default();
assert!(!should_use_mouse_capture_with(
@@ -5530,7 +5530,7 @@ mod terminal_mode_tests {
#[test]
fn jetbrains_default_off_is_case_insensitive() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config::default();
// JetBrains has occasionally varied the casing across releases;
@@ -5547,7 +5547,7 @@ mod terminal_mode_tests {
#[test]
fn mouse_capture_flag_overrides_jetbrains_default() {
let cli = parse_cli(&["deepseek", "--mouse-capture"]);
let cli = parse_cli(&["codewhale", "--mouse-capture"]);
let config = Config::default();
assert!(should_use_mouse_capture_with(
@@ -5562,7 +5562,7 @@ mod terminal_mode_tests {
#[test]
fn config_mouse_capture_true_overrides_jetbrains_default() {
let cli = parse_cli(&["deepseek"]);
let cli = parse_cli(&["codewhale"]);
let config = Config {
tui: Some(crate::config::TuiConfig {
alternate_screen: None,
@@ -5782,24 +5782,24 @@ max_subagents = -3
fn project_overlay_skips_missing_config_file() {
let tmp = tempdir().expect("tempdir");
let mut config = Config {
provider: Some("deepseek".to_string()),
provider: Some("codewhale".to_string()),
..Config::default()
};
merge_project_config(&mut config, tmp.path());
// Untouched.
assert_eq!(config.provider.as_deref(), Some("deepseek"));
assert_eq!(config.provider.as_deref(), Some("codewhale"));
}
#[test]
fn project_overlay_skips_malformed_toml() {
let tmp = workspace_with_project_config("this is not valid TOML !!");
let mut config = Config {
provider: Some("deepseek".to_string()),
provider: Some("codewhale".to_string()),
..Config::default()
};
merge_project_config(&mut config, tmp.path());
// Untouched on parse error — better to fall back to global than crash.
assert_eq!(config.provider.as_deref(), Some("deepseek"));
assert_eq!(config.provider.as_deref(), Some("codewhale"));
}
#[test]
@@ -5811,13 +5811,13 @@ model = ""
"#,
);
let mut config = Config {
provider: Some("deepseek".to_string()),
provider: Some("codewhale".to_string()),
default_text_model: Some("deepseek-v4-pro".to_string()),
..Config::default()
};
merge_project_config(&mut config, tmp.path());
// Empty strings are ignored — they're rarely a deliberate override.
assert_eq!(config.provider.as_deref(), Some("deepseek"));
assert_eq!(config.provider.as_deref(), Some("codewhale"));
assert_eq!(
config.default_text_model.as_deref(),
Some("deepseek-v4-pro")
@@ -5958,7 +5958,7 @@ mod doctor_mcp_tests {
#[test]
fn test_self_hosted_absolute_is_ok() {
let server = make_server(Some("/usr/local/bin/deepseek"), &["serve", "--mcp"], None);
let server = make_server(Some("/usr/local/bin/codewhale"), &["serve", "--mcp"], None);
match doctor_check_mcp_server(&server) {
McpServerDoctorStatus::Ok(detail) | McpServerDoctorStatus::Error(detail) => {
// On systems where the path doesn't exist, this will be Error.
@@ -5976,7 +5976,7 @@ mod doctor_mcp_tests {
#[test]
fn test_self_hosted_relative_is_warning() {
let server = make_server(Some("deepseek"), &["serve", "--mcp"], None);
let server = make_server(Some("codewhale"), &["serve", "--mcp"], None);
match doctor_check_mcp_server(&server) {
McpServerDoctorStatus::Warning(detail) => {
assert!(detail.contains("relative"));
@@ -6461,7 +6461,7 @@ mod pr_prompt_tests {
// A deliberately-implausible name to confirm the negative
// branch — `--version` on this would exec(3) → ENOENT.
assert!(
!is_command_available("this-command-cannot-exist-deepseek-tui-test-ENOENT-marker"),
!is_command_available("this-command-cannot-exist-codewhale-tui-test-ENOENT-marker"),
"missing command should return false, not panic"
);
}
+2 -2
View File
@@ -1328,7 +1328,7 @@ impl McpConnection {
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "deepseek-tui",
"name": "codewhale-tui",
"version": env!("CARGO_PKG_VERSION")
},
"capabilities": {
@@ -3343,7 +3343,7 @@ mod tests {
r#"{
"mcpServers": {
"broken": {
"command": "deepseek-tui-test-this-binary-does-not-exist-9f8e7d6c5b4a",
"command": "codewhale-tui-test-this-binary-does-not-exist-9f8e7d6c5b4a",
"args": []
}
}
+7 -7
View File
@@ -277,7 +277,7 @@ pub(crate) fn locale_reinforcement_closer(locale_tag: &str) -> Option<&'static s
}
const LOCALE_PREAMBLE_ZH_HANS: &str = "## 语言要求\n\n\
DeepSeek TUI \
codewhale \
\
`reasoning_content`\
`read_file``exec_shell` URL \
@@ -286,7 +286,7 @@ const LOCALE_PREAMBLE_ZH_HANS: &str = "## 语言要求\n\n\
\"think in English\"),则覆盖此规则。";
const LOCALE_PREAMBLE_JA: &str = "## 言語要件\n\n\
DeepSeek TUI \
codewhale \
\
`reasoning_content`\
`read_file`\
@@ -297,7 +297,7 @@ DeepSeek TUI を実行しています。タスクコンテキスト(コード
\"think in English\")はこのルールを上書きします。";
const LOCALE_PREAMBLE_PT_BR: &str = "## Requisito de Idioma\n\n\
Você está rodando dentro do DeepSeek TUI. Escreva tanto \
Você está rodando dentro do codewhale. Escreva tanto \
`reasoning_content` (seu pensamento interno) quanto a resposta final \
em português do Brasil, mesmo quando o contexto da tarefa (código, \
logs de erro, nomes de arquivos) estiver em inglês e mesmo quando o \
@@ -888,7 +888,7 @@ mod tests {
SystemPrompt::Blocks(_) => panic!("expected text system prompt"),
};
let preamble_marker = "## 语言要求";
let base_marker = "You are DeepSeek TUI";
let base_marker = "You are codewhale";
let preamble_pos = text
.find(preamble_marker)
.expect("zh-Hans preamble should be present");
@@ -1262,7 +1262,7 @@ mod tests {
fn compose_prompt_includes_all_layers() {
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
// Base layer
assert!(prompt.contains("You are DeepSeek TUI"));
assert!(prompt.contains("You are codewhale"));
// Personality layer
assert!(prompt.contains("Personality: Calm"));
// Mode layer
@@ -1321,7 +1321,7 @@ mod tests {
#[test]
fn compose_prompt_deterministic_order() {
let prompt = compose_prompt(AppMode::Yolo, Personality::Calm);
let base_pos = prompt.find("You are DeepSeek TUI").unwrap();
let base_pos = prompt.find("You are codewhale").unwrap();
let personality_pos = prompt.find("Personality: Calm").unwrap();
let mode_pos = prompt.find("Mode: YOLO").unwrap();
let approval_pos = prompt.find("Approval Policy: Auto").unwrap();
@@ -1581,7 +1581,7 @@ mod tests {
fn subagent_done_sentinel_section_present() {
let prompt = compose_prompt(AppMode::Agent, Personality::Calm);
assert!(prompt.contains("Internal Sub-agent Completion Events"));
assert!(prompt.contains("<deepseek:subagent.done>"));
assert!(prompt.contains("<codewhale:subagent.done>"));
assert!(prompt.contains("not user input"));
assert!(prompt.contains("Integration protocol"));
assert!(prompt.contains("Do not tell the user they pasted sentinels"));
+1 -1
View File
@@ -11,6 +11,6 @@ flow.
## Sub-agent completion sentinel
When you open a sub-agent via `agent_open`, the child runs independently.
You will receive a `<deepseek:subagent.done>` element in the transcript when it finishes.
You will receive a `<codewhale:subagent.done>` element in the transcript when it finishes.
Read its `summary` field and integrate the work — do not re-do what the child already did.
You can also call `agent_eval` with the agent name or id to pull the current structured projection or transcript handle.
+5 -5
View File
@@ -1,4 +1,4 @@
You are DeepSeek TUI. You're already running inside it. Do not launch a nested interactive `deepseek` or `deepseek-tui` session unless the user explicitly asks. Using `deepseek` CLI subcommands such as `deepseek --version`, `deepseek -p`, `deepseek doctor`, or `deepseek auth status` is allowed when it directly helps the task.
You are codewhale. You're already running inside it. Do not launch a nested interactive `codewhale` or `codewhale-tui` session unless the user explicitly asks. Using `codewhale` CLI subcommands such as `codewhale --version`, `codewhale -p`, `codewhale doctor`, or `codewhale auth status` is allowed when it directly helps the task.
## Language
@@ -14,7 +14,7 @@ Code, file paths, identifiers, tool names, environment variables, command-line f
## Runtime Identity
If the user asks what DeepSeek TUI version you are running, use the `deepseek_version` field in the `## Environment` section as the runtime version. Workspace files such as `Cargo.toml` describe the checkout you are inspecting; they may be stale, dirty, or intentionally different from the installed runtime. If those disagree, report both instead of replacing the runtime version with the workspace version.
If the user asks what codewhale version you are running, use the `deepseek_version` field in the `## Environment` section as the runtime version. Workspace files such as `Cargo.toml` describe the checkout you are inspecting; they may be stale, dirty, or intentionally different from the installed runtime. If those disagree, report both instead of replacing the runtime version with the workspace version.
## Preamble Rhythm
@@ -203,7 +203,7 @@ Use persistent RLM sessions for long-context semantic work, bulk classification/
## Internal Sub-agent Completion Events
When you open a sub-agent via `agent_open`, the child runs independently. The runtime may send you an internal `<deepseek:subagent.done>` completion event when it finishes. This event is not user input. It carries:
When you open a sub-agent via `agent_open`, the child runs independently. The runtime may send you an internal `<codewhale:subagent.done>` completion event when it finishes. This event is not user input. It carries:
- `agent_id` — the child's identifier
- `status``"completed"` or `"failed"`
@@ -211,14 +211,14 @@ When you open a sub-agent via `agent_open`, the child runs independently. The ru
- `details` — currently `agent_eval`, the tool to call when you need the full projection or transcript handle
**Integration protocol:**
1. When you see `<deepseek:subagent.done>`, read the human summary line immediately before it first.
1. When you see `<codewhale:subagent.done>`, read the human summary line immediately before it first.
2. Integrate the child's findings into your work — do not re-do what the child already did.
3. If the summary is insufficient, call `agent_eval` with the agent name or id to pull the current structured projection or transcript handle.
4. If the child failed (`"failed"`), assess whether the failure blocks your plan or whether you can proceed with a fallback.
5. Update your `checklist_write` items to reflect the child's contribution.
6. Do not tell the user they pasted sentinels or explain this protocol unless they explicitly ask about sub-agent internals.
You may see multiple `<deepseek:subagent.done>` sentinels in a single turn when children were opened in parallel. Process each one, then synthesize.
You may see multiple `<codewhale:subagent.done>` sentinels in a single turn when children were opened in parallel. Process each one, then synthesize.
## Output formatting
+1 -1
View File
@@ -1,4 +1,4 @@
You are DeepSeek TUI. You're already running inside it. Do not launch a nested interactive `deepseek` or `deepseek-tui` session unless the user explicitly asks. Using `deepseek` CLI subcommands such as `deepseek --version`, `deepseek -p`, `deepseek doctor`, or `deepseek auth status` is allowed when it directly helps the task.
You are codewhale. You're already running inside it. Do not launch a nested interactive `codewhale` or `codewhale-tui` session unless the user explicitly asks. Using `codewhale` CLI subcommands such as `codewhale --version`, `codewhale -p`, `codewhale doctor`, or `codewhale auth status` is allowed when it directly helps the task.
## Decomposition Philosophy
+9 -9
View File
@@ -607,7 +607,7 @@ pub const DEFAULT_MAX_SPAWN_DEPTH: u32 = 3;
/// Terminal-state notification emitted to the engine's parent turn loop
/// when one of its direct children finishes (issue #756). Carries the
/// already-rendered `<deepseek:subagent.done>` sentinel that the model
/// already-rendered `<codewhale:subagent.done>` sentinel that the model
/// expects in the transcript per `prompts/base.md`.
#[derive(Debug, Clone)]
pub struct SubAgentCompletion {
@@ -3333,12 +3333,12 @@ fn build_initial_subagent_messages(
.filter(|state| !state.is_empty())
{
messages.push(system_text_message(format!(
"<deepseek:fork_state>\n{state}\n</deepseek:fork_state>"
"<codewhale:fork_state>\n{state}\n</codewhale:fork_state>"
)));
}
messages.push(system_text_message(format!(
"<deepseek:subagent_context>\n{}\n</deepseek:subagent_context>",
"<codewhale:subagent_context>\n{}\n</codewhale:subagent_context>",
build_subagent_system_prompt(agent_type, assignment)
)));
}
@@ -3406,7 +3406,7 @@ async fn run_subagent_task(task: SubAgentTask) {
// sidebar / cell) AND a structured sentinel the model can recognize
// on its next turn. Format: human summary on the first line,
// sentinel on the second. The sentinel uses an opaque tag
// (`deepseek:subagent.done`) to avoid collision with normal user
// (`codewhale:subagent.done`) to avoid collision with normal user
// text.
let (summary, sentinel) = match &result {
Ok(res) => (
@@ -3473,7 +3473,7 @@ pub(crate) fn emit_parent_completion(
true
}
/// Build a `<deepseek:subagent.done>` JSON sentinel for a successful child.
/// Build a `<codewhale:subagent.done>` JSON sentinel for a successful child.
/// Intended to surface in the parent's transcript so the model recognizes
/// child completion and can decide whether to read the full result via
/// `agent_eval`.
@@ -3490,10 +3490,10 @@ fn subagent_done_sentinel(agent_id: &str, res: &SubAgentResult) -> String {
"summary_location": "previous_line",
"details": "agent_eval",
});
format!("<deepseek:subagent.done>{payload}</deepseek:subagent.done>")
format!("<codewhale:subagent.done>{payload}</codewhale:subagent.done>")
}
/// Build a `<deepseek:subagent.done>` sentinel for a failed child.
/// Build a `<codewhale:subagent.done>` sentinel for a failed child.
fn subagent_failed_sentinel(agent_id: &str, _err: &str) -> String {
let payload = json!({
"agent_id": agent_id,
@@ -3501,7 +3501,7 @@ fn subagent_failed_sentinel(agent_id: &str, _err: &str) -> String {
"error_location": "previous_line",
"details": "agent_eval",
});
format!("<deepseek:subagent.done>{payload}</deepseek:subagent.done>")
format!("<codewhale:subagent.done>{payload}</codewhale:subagent.done>")
}
#[allow(clippy::too_many_arguments)]
@@ -4437,7 +4437,7 @@ async fn subagent_flash_router(
}
const SUBAGENT_ROUTER_SYSTEM_PROMPT: &str = "\
You are the DeepSeek TUI sub-agent routing manager. Return only compact JSON: \
You are the codewhale sub-agent routing manager. Return only compact JSON: \
{\"model\":\"deepseek-v4-flash|deepseek-v4-pro\",\"thinking\":\"off|high|max\"}. \
Treat each child assignment like a customer request entering a team queue: decide the least \
sufficient worker and thinking budget for that assignment. Do not treat being a sub-agent as \
+13 -13
View File
@@ -472,9 +472,9 @@ fn forked_subagent_messages_preserve_parent_prefix_then_append_task() {
assert_eq!(messages.first(), Some(&parent_message));
assert_eq!(messages.len(), 4);
assert_eq!(messages[1].role, "system");
assert!(message_text(&messages[1]).contains("<deepseek:fork_state>"));
assert!(message_text(&messages[1]).contains("<codewhale:fork_state>"));
assert_eq!(messages[2].role, "system");
assert!(message_text(&messages[2]).contains("<deepseek:subagent_context>"));
assert!(message_text(&messages[2]).contains("<codewhale:subagent_context>"));
assert_eq!(messages[3].role, "user");
assert!(message_text(&messages[3]).contains("inspect parser"));
}
@@ -1209,13 +1209,13 @@ fn build_subagent_system_prompt_skips_role_when_blank() {
fn subagent_done_sentinel_format_is_well_formed() {
let res = make_snapshot(SubAgentStatus::Completed);
let sentinel = subagent_done_sentinel("agent_xyz", &res);
assert!(sentinel.starts_with("<deepseek:subagent.done>"));
assert!(sentinel.ends_with("</deepseek:subagent.done>"));
assert!(sentinel.starts_with("<codewhale:subagent.done>"));
assert!(sentinel.ends_with("</codewhale:subagent.done>"));
// The inner JSON parses and carries the expected fields.
let inner = sentinel
.trim_start_matches("<deepseek:subagent.done>")
.trim_end_matches("</deepseek:subagent.done>");
.trim_start_matches("<codewhale:subagent.done>")
.trim_end_matches("</codewhale:subagent.done>");
let parsed: serde_json::Value = serde_json::from_str(inner).expect("inner JSON parses");
assert_eq!(parsed["agent_id"], "agent_xyz");
assert_eq!(parsed["status"], "completed");
@@ -1231,8 +1231,8 @@ fn subagent_done_sentinel_format_is_well_formed() {
fn subagent_failed_sentinel_format_is_well_formed() {
let sentinel = subagent_failed_sentinel("agent_zzz", "boom");
let inner = sentinel
.trim_start_matches("<deepseek:subagent.done>")
.trim_end_matches("</deepseek:subagent.done>");
.trim_start_matches("<codewhale:subagent.done>")
.trim_end_matches("</codewhale:subagent.done>");
let parsed: serde_json::Value = serde_json::from_str(inner).expect("inner JSON parses");
assert_eq!(parsed["agent_id"], "agent_zzz");
assert_eq!(parsed["status"], "failed");
@@ -1708,7 +1708,7 @@ fn persisted_non_empty_allowed_tools_loads_as_narrow() {
fn stub_runtime() -> SubAgentRuntime {
use tokio_util::sync::CancellationToken;
let workspace = std::env::temp_dir().join("deepseek-test-stub");
let workspace = std::env::temp_dir().join("codewhale-test-stub");
let context = ToolContext::new(workspace.clone());
SubAgentRuntime {
client: stub_client(),
@@ -2080,7 +2080,7 @@ fn child_runtime_preserves_step_api_timeout() {
#[test]
fn subagent_completion_payload_carries_existing_sentinel_format() {
// The payload format is the same one already documented in
// prompts/base.md: human summary on line 1, `<deepseek:subagent.done>`
// prompts/base.md: human summary on line 1, `<codewhale:subagent.done>`
// sentinel on line 2. This test pins the format so future refactors
// don't silently break the model's parsing contract.
let mut snap = make_snapshot(SubAgentStatus::Completed);
@@ -2094,14 +2094,14 @@ fn subagent_completion_payload_carries_existing_sentinel_format() {
let first = lines.next().expect("first line is summary");
let second = lines.next().expect("second line is sentinel");
assert!(
!first.starts_with("<deepseek:subagent.done>"),
!first.starts_with("<codewhale:subagent.done>"),
"summary should not be the sentinel itself"
);
assert!(
second.starts_with("<deepseek:subagent.done>"),
second.starts_with("<codewhale:subagent.done>"),
"second line is the sentinel"
);
assert!(second.ends_with("</deepseek:subagent.done>"));
assert!(second.ends_with("</codewhale:subagent.done>"));
assert!(
second.contains("\"agent_id\":\"agent_test\""),
"sentinel JSON includes agent_id"
+11 -11
View File
@@ -143,7 +143,7 @@ fn build_escape(method: Method, in_tmux: bool, msg: &str) -> Vec<u8> {
}
Method::Ghostty => {
// Ghostty notification: OSC 777 ; notify ; title ; message BEL
let seq = format!("\x1b]777;notify;DeepSeek TUI;{msg}\x07");
let seq = format!("\x1b]777;notify;codewhale;{msg}\x07");
wrap_for_multiplexer(&seq, in_tmux).into_bytes()
}
// Auto and Off should not reach build_escape.
@@ -332,18 +332,18 @@ pub fn completed_turn_message(
) -> String {
let mut msg = text_summary(current_streaming_text)
.or_else(|| latest_assistant_text(&app.api_messages))
.unwrap_or_else(|| "deepseek: turn complete".to_string());
.unwrap_or_else(|| "codewhale: turn complete".to_string());
if include_summary {
let human = humanize_duration(turn_elapsed);
let summary = match turn_cost {
Some(c) => {
let cost = crate::pricing::format_cost_estimate(c, app.cost_currency);
format!("deepseek: turn complete ({human}, {cost})")
format!("codewhale: turn complete ({human}, {cost})")
}
None => format!("deepseek: turn complete ({human})"),
None => format!("codewhale: turn complete ({human})"),
};
if msg == "deepseek: turn complete" {
if msg == "codewhale: turn complete" {
msg = summary;
} else {
msg.push('\n');
@@ -366,16 +366,16 @@ pub fn subagent_completion_message(
let result_line = result
.lines()
.map(str::trim)
.find(|line| !line.is_empty() && !line.starts_with("<deepseek:subagent.done>"));
.find(|line| !line.is_empty() && !line.starts_with("<codewhale:subagent.done>"));
let mut msg = result_line
.and_then(text_summary)
.map(|summary| format!("sub-agent {id}: {summary}"))
.unwrap_or_else(|| format!("deepseek: sub-agent {id} complete"));
.unwrap_or_else(|| format!("codewhale: sub-agent {id} complete"));
if include_summary {
let human = humanize_duration(elapsed);
msg.push('\n');
msg.push_str(&format!("deepseek: sub-agent complete ({human})"));
msg.push_str(&format!("codewhale: sub-agent complete ({human})"));
}
msg
@@ -471,8 +471,8 @@ mod tests {
#[test]
fn osc9_body_format() {
let out = capture(Method::Osc9, false, "deepseek: done", 0, 1);
assert_eq!(out, b"\x1b]9;deepseek: done\x07");
let out = capture(Method::Osc9, false, "codewhale: done", 0, 1);
assert_eq!(out, b"\x1b]9;codewhale: done\x07");
}
#[test]
@@ -501,7 +501,7 @@ mod tests {
let out = capture(Method::Ghostty, false, "done", 0, 1);
let s = String::from_utf8(out).unwrap();
assert!(
s.contains("777;notify;DeepSeek TUI;done"),
s.contains("777;notify;codewhale;done"),
"should have ghostty seq"
);
}
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::palette;
pub fn lines() -> Vec<Line<'static>> {
vec![
Line::from(Span::styled(
"DeepSeek TUI",
"codewhale",
Style::default()
.fg(palette::DEEPSEEK_BLUE)
.add_modifier(Modifier::BOLD),
+2 -2
View File
@@ -2051,7 +2051,7 @@ mod tests {
.push(HistoryCell::Tool(ToolCell::Generic(GenericToolCell {
name: "read_file".to_string(),
status: ToolStatus::Success,
input_summary: Some("deepseek-tui/CHANGELOG.md".to_string()),
input_summary: Some("codewhale-tui/CHANGELOG.md".to_string()),
output: Some("done".to_string()),
prompts: None,
spillover_path: None,
@@ -2328,7 +2328,7 @@ mod tests {
fn tasks_panel_failed_shell_rows_point_to_activity_details() {
let mut app = create_test_app();
app.history.push(HistoryCell::Tool(ToolCell::Exec(ExecCell {
command: "cargo test -p deepseek-tui".to_string(),
command: "cargo test -p codewhale-tui".to_string(),
status: ToolStatus::Failed,
output: Some("test failed".to_string()),
started_at: None,
+1 -1
View File
@@ -576,7 +576,7 @@ fn should_show_resume_hint(session_id: Option<&str>) -> bool {
}
fn resume_hint_text() -> &'static str {
"To continue this session, execute deepseek run --continue"
"To continue this session, execute codewhale run --continue"
}
fn terminal_probe_timeout(config: &Config) -> Duration {
+8 -8
View File
@@ -116,7 +116,7 @@ impl Drop for SettingsHomeGuard {
fn resume_hint_uses_canonical_resume_command() {
assert_eq!(
resume_hint_text(),
"To continue this session, execute deepseek run --continue"
"To continue this session, execute codewhale run --continue"
);
assert!(should_show_resume_hint(Some(
"019dd9d6-4f44-7c83-9863-59674a12b827"
@@ -1952,7 +1952,7 @@ fn init_git_repo() -> TempDir {
let commit = Command::new("git")
.args([
"-c",
"user.name=DeepSeek TUI Tests",
"user.name=codewhale Tests",
"-c",
"user.email=tests@example.com",
"commit",
@@ -2864,7 +2864,7 @@ fn visible_slash_menu_entries_excludes_removed_commands() {
assert!(entries.iter().any(|entry| entry.name == "/config"));
assert!(entries.iter().any(|entry| entry.name == "/links"));
assert!(!entries.iter().any(|entry| entry.name == "/set"));
assert!(!entries.iter().any(|entry| entry.name == "/deepseek"));
assert!(!entries.iter().any(|entry| entry.name == "/codewhale"));
}
#[test]
@@ -5970,7 +5970,7 @@ fn completed_turn_notification_falls_back_to_default_when_empty() {
Duration::from_secs(5),
None,
);
assert_eq!(msg, "deepseek: turn complete");
assert_eq!(msg, "codewhale: turn complete");
}
#[test]
@@ -5993,13 +5993,13 @@ fn completed_turn_notification_truncates_long_text() {
fn subagent_completion_notification_uses_summary_line_not_sentinel() {
let msg = crate::tui::notifications::subagent_completion_message(
"agent_live",
"Finished the docs audit.\n<deepseek:subagent.done>{}</deepseek:subagent.done>",
"Finished the docs audit.\n<codewhale:subagent.done>{}</codewhale:subagent.done>",
false,
Duration::from_secs(42),
);
assert_eq!(msg, "sub-agent agent_live: Finished the docs audit.");
assert!(!msg.contains("deepseek:subagent.done"));
assert!(!msg.contains("codewhale:subagent.done"));
}
#[test]
@@ -6011,8 +6011,8 @@ fn subagent_completion_notification_can_include_elapsed_summary() {
Duration::from_secs(65),
);
assert!(msg.contains("deepseek: sub-agent agent_live complete"));
assert!(msg.contains("deepseek: sub-agent complete (1m 5s)"));
assert!(msg.contains("codewhale: sub-agent agent_live complete"));
assert!(msg.contains("codewhale: sub-agent complete (1m 5s)"));
}
#[test]
+9 -9
View File
@@ -619,7 +619,7 @@ mod tests {
HeaderData::new(
AppMode::Agent,
"deepseek-v4-pro",
"deepseek-tui",
"codewhale-tui",
false,
palette::DEEPSEEK_INK,
),
@@ -627,7 +627,7 @@ mod tests {
);
assert!(rendered.contains("Agent"));
assert!(rendered.contains("deepseek-tui"));
assert!(rendered.contains("codewhale-tui"));
assert!(rendered.contains("deepseek-v4-pro"));
assert!(!rendered.contains("Plan"));
assert!(!rendered.contains("Yolo"));
@@ -637,12 +637,12 @@ mod tests {
fn header_renders_version_chip_when_width_allows() {
// At a generous width the header must surface the runtime version
// — users repeatedly ask for it in the live UI (vs only via
// `deepseek --version` / `/status`).
// `codewhale --version` / `/status`).
let rendered = render_header(
HeaderData::new(
AppMode::Agent,
"deepseek-v4-pro",
"deepseek-tui",
"codewhale-tui",
false,
palette::DEEPSEEK_INK,
),
@@ -663,7 +663,7 @@ mod tests {
HeaderData::new(
AppMode::Yolo,
"deepseek-v4-pro",
"deepseek-tui",
"codewhale-tui",
true,
palette::DEEPSEEK_INK,
)
@@ -775,7 +775,7 @@ mod tests {
HeaderData::new(
AppMode::Agent,
"deepseek-ai/deepseek-v4-flash",
"deepseek-tui",
"codewhale-tui",
false,
palette::DEEPSEEK_INK,
)
@@ -794,7 +794,7 @@ mod tests {
HeaderData::new(
AppMode::Agent,
"deepseek-v4-pro",
"deepseek-tui",
"codewhale-tui",
false,
palette::DEEPSEEK_INK,
),
@@ -859,7 +859,7 @@ mod tests {
HeaderData::new(
AppMode::Agent,
"deepseek-v4-pro",
"deepseek-tui",
"codewhale-tui",
false,
palette::DEEPSEEK_INK,
)
@@ -890,7 +890,7 @@ mod tests {
HeaderData::new(
AppMode::Agent,
"deepseek-v4-pro",
"deepseek-tui",
"codewhale-tui",
false,
palette::DEEPSEEK_INK,
)
+9 -9
View File
@@ -333,7 +333,7 @@ impl Renderable for ChatWidget {
let area = _area;
// Repaint the full chat area with the deepseek-ink background each
// Repaint the full chat area with the codewhale-ink background each
// frame. Ratatui's `Paragraph` only writes cells that contain text,
// so cells the current frame's paragraph doesn't touch would
// otherwise hold the *previous* frame's contents (the `:24Z`
@@ -1926,7 +1926,7 @@ fn build_empty_state_lines(app: &App, area: Rect) -> Vec<Line<'static>> {
let body = vec![
Line::from(Span::styled(
format!("{inset}>_ DeepSeek TUI (v{})", env!("CARGO_PKG_VERSION")),
format!("{inset}>_ codewhale (v{})", env!("CARGO_PKG_VERSION")),
Style::default().fg(palette::DEEPSEEK_BLUE).bold(),
)),
Line::from(""),
@@ -2557,7 +2557,7 @@ mod tests {
fn slash_completion_hints_exclude_set_and_deepseek_commands() {
let hints = slash_completion_hints("/", 128, &[], Locale::En, None, ApiProvider::Deepseek);
assert!(!hints.iter().any(|hint| hint.name == "/set"));
assert!(!hints.iter().any(|hint| hint.name == "/deepseek"));
assert!(!hints.iter().any(|hint| hint.name == "/codewhale"));
}
#[test]
@@ -2809,7 +2809,7 @@ mod tests {
let mut app = create_test_app();
app.composer_density = ComposerDensity::Comfortable;
app.session_title =
Some("hello could you please take a look at deepseek-tui and all changes".to_string());
Some("hello could you please take a look at codewhale-tui and all changes".to_string());
let slash_menu_entries = Vec::<SlashMenuEntry>::new();
let mention_menu_entries = Vec::<String>::new();
let widget = ComposerWidget::new(&app, 5, &slash_menu_entries, &mention_menu_entries);
@@ -2825,7 +2825,7 @@ mod tests {
let rendered = buffer_text(&buf, area);
assert!(rendered.contains("Composer"));
assert!(!rendered.contains("deepseek-tui"));
assert!(!rendered.contains("codewhale-tui"));
assert!(!rendered.contains("hello could you"));
}
@@ -2951,7 +2951,7 @@ mod tests {
#[test]
fn empty_state_shows_startup_context() {
let mut app = create_test_app();
app.workspace = PathBuf::from("/tmp/deepseek-test-workspace");
app.workspace = PathBuf::from("/tmp/codewhale-test-workspace");
app.model = "deepseek-v4-pro".to_string();
let lines = build_empty_state_lines(&app, Rect::new(0, 0, 100, 20));
@@ -2966,9 +2966,9 @@ mod tests {
.collect::<Vec<_>>()
.join("\n");
assert!(rendered.contains(&format!(">_ DeepSeek TUI (v{})", env!("CARGO_PKG_VERSION"))));
assert!(rendered.contains(&format!(">_ codewhale (v{})", env!("CARGO_PKG_VERSION"))));
assert!(rendered.contains("model: deepseek-v4-pro /model to switch"));
assert!(rendered.contains("directory: /tmp/deepseek-test-workspace"));
assert!(rendered.contains("directory: /tmp/codewhale-test-workspace"));
}
/// Probe: confirm `cell.lines_with_motion` returns no Line whose total
@@ -3449,7 +3449,7 @@ mod tests {
/// pays the wrap cost; subsequent calls at different offsets should hit
/// the per-cell cache and be ~constant time regardless of offset.
///
/// Run with: `cargo test -p deepseek-tui --release bench_transcript_scroll
/// Run with: `cargo test -p codewhale-tui --release bench_transcript_scroll
/// -- --ignored --nocapture`
// Perf bench prints timing rows to stdout — runs in `cargo test`,
// never inside the TUI alt-screen.
+2 -2
View File
@@ -48,7 +48,7 @@ fn any_engine_source_contains(needle: &str) -> bool {
const EXPECTED_START_MARKERS: &[&str] = &[
"[TOOL_CALL]",
"<deepseek:tool_call",
"<codewhale:tool_call",
"<tool_call",
"<invoke ",
"<function_calls>",
@@ -56,7 +56,7 @@ const EXPECTED_START_MARKERS: &[&str] = &[
const EXPECTED_END_MARKERS: &[&str] = &[
"[/TOOL_CALL]",
"</deepseek:tool_call>",
"</codewhale:tool_call>",
"</tool_call>",
"</invoke>",
"</function_calls>",
@@ -227,7 +227,7 @@ impl Harness {
return PathBuf::from(path);
}
// Legacy fallback for callers still referencing the old bin name.
if name == "deepseek-tui"
if name == "codewhale-tui"
&& let Some(path) = option_env!("CARGO_BIN_EXE_deepseek-tui")
{
return PathBuf::from(path);