fix: restore API key config resolution and clean runtime artifacts
This commit is contained in:
@@ -55,3 +55,7 @@ AI_HANDOFF.md
|
||||
.codex/
|
||||
docs/rlm-paper.txt
|
||||
.context/
|
||||
|
||||
# Local runtime state
|
||||
.deepseek/
|
||||
session_*.json
|
||||
|
||||
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.3.17] - 2026-02-16
|
||||
|
||||
### Fixed
|
||||
- Config loading now expands `~` in `DEEPSEEK_CONFIG_PATH` and `--config` paths.
|
||||
- When `DEEPSEEK_CONFIG_PATH` points to a missing file, config loading now falls back to `~/.deepseek/config.toml` if it exists.
|
||||
|
||||
### Changed
|
||||
- Removed committed transient runtime artifacts (`session_*.json`, `.deepseek/trusted`) and added ignore rules to prevent re-commit.
|
||||
|
||||
## [0.3.16] - 2026-02-15
|
||||
|
||||
### Added
|
||||
|
||||
Generated
+1
-1
@@ -726,7 +726,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-tui"
|
||||
version = "0.3.16"
|
||||
version = "0.3.17"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arboard",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deepseek-tui"
|
||||
version = "0.3.16"
|
||||
version = "0.3.17"
|
||||
edition = "2024"
|
||||
description = "Unofficial DeepSeek CLI - Just run 'deepseek' to start chatting"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "9f574f3d-c6f6-436e-9967-aaaba56148a2",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T15:52:42.502684Z",
|
||||
"updated_at": "2026-02-16T15:52:42.502684Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpTtSsZw",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "a9cab85f-7ea0-44f8-a6dc-88aee23462ab",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T15:59:08.213118Z",
|
||||
"updated_at": "2026-02-16T15:59:08.213118Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpT6EfcG",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "fa7764c1-15ba-4b5a-bce8-e4f939e1f838",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:00:56.854336Z",
|
||||
"updated_at": "2026-02-16T16:00:56.854336Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpe0lg3x",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "8d3f306c-70d3-46d9-8007-10c638966197",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:16:55.890410Z",
|
||||
"updated_at": "2026-02-16T16:16:55.890410Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpUidsSQ",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "159c08f3-9aab-435c-9f2b-370a40667710",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:21:28.631837Z",
|
||||
"updated_at": "2026-02-16T16:21:28.631837Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpeUxI8I",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "4e6f3623-30d7-431e-8b11-d8549dac8c13",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:30:41.499125Z",
|
||||
"updated_at": "2026-02-16T16:30:41.499125Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpjykcjh",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "0c1e5eb4-e426-4d8a-a622-80b1939e5083",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:34:48.304398Z",
|
||||
"updated_at": "2026-02-16T16:34:48.304398Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmp1bRsZy",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "ce96ab6c-6b3c-45fb-8c69-f7659e50cb03",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:45:48.643707Z",
|
||||
"updated_at": "2026-02-16T16:45:48.643707Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpkQGlZi",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "3ea22ecd-cb0a-485b-ba2e-9f9e02d1ec74",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:56:57.987857Z",
|
||||
"updated_at": "2026-02-16T16:56:57.987857Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpOWW4ty",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"id": "206fc731-8181-4693-8c27-1fbc9cc8032e",
|
||||
"title": "New Session",
|
||||
"created_at": "2026-02-16T16:57:39.210126Z",
|
||||
"updated_at": "2026-02-16T16:57:39.210126Z",
|
||||
"message_count": 0,
|
||||
"total_tokens": 0,
|
||||
"model": "deepseek-v3.2",
|
||||
"workspace": "/var/folders/gc/lw1tgpk97z51d30mcvbhyb400000gn/T/.tmpTNf6nn",
|
||||
"mode": "AGENT"
|
||||
},
|
||||
"messages": [],
|
||||
"system_prompt": null
|
||||
}
|
||||
+101
-2
@@ -218,7 +218,88 @@ mod tests {
|
||||
use crate::config::Config;
|
||||
use crate::tui::app::{App, TuiOptions};
|
||||
use crate::tui::approval::ApprovalMode;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
struct EnvGuard {
|
||||
home: Option<OsString>,
|
||||
userprofile: Option<OsString>,
|
||||
deepseek_config_path: Option<OsString>,
|
||||
}
|
||||
|
||||
impl EnvGuard {
|
||||
fn new(home: &Path) -> Self {
|
||||
let home_str = OsString::from(home.as_os_str());
|
||||
let config_path = home.join(".deepseek").join("config.toml");
|
||||
let config_str = OsString::from(config_path.as_os_str());
|
||||
let home_prev = env::var_os("HOME");
|
||||
let userprofile_prev = env::var_os("USERPROFILE");
|
||||
let deepseek_config_prev = env::var_os("DEEPSEEK_CONFIG_PATH");
|
||||
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var("HOME", &home_str);
|
||||
env::set_var("USERPROFILE", &home_str);
|
||||
env::set_var("DEEPSEEK_CONFIG_PATH", &config_str);
|
||||
}
|
||||
|
||||
Self {
|
||||
home: home_prev,
|
||||
userprofile: userprofile_prev,
|
||||
deepseek_config_path: deepseek_config_prev,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(value) = self.home.take() {
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var("HOME", value);
|
||||
}
|
||||
} else {
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::remove_var("HOME");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = self.userprofile.take() {
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var("USERPROFILE", value);
|
||||
}
|
||||
} else {
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::remove_var("USERPROFILE");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = self.deepseek_config_path.take() {
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var("DEEPSEEK_CONFIG_PATH", value);
|
||||
}
|
||||
} else {
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::remove_var("DEEPSEEK_CONFIG_PATH");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn env_lock() -> &'static Mutex<()> {
|
||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
LOCK.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
fn create_test_app() -> App {
|
||||
let options = TuiOptions {
|
||||
@@ -364,15 +445,33 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_logout_clears_api_key_state() {
|
||||
let _lock = env_lock().lock().unwrap();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"deepseek-cli-logout-test-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root).unwrap();
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let config_path = temp_root.join(".deepseek").join("config.toml");
|
||||
fs::create_dir_all(config_path.parent().unwrap()).unwrap();
|
||||
fs::write(&config_path, "api_key = \"test-key\"\n").unwrap();
|
||||
|
||||
let mut app = create_test_app();
|
||||
// Note: This test may fail if API key is not set in environment
|
||||
// but the state changes should still occur
|
||||
let result = logout(&mut app);
|
||||
assert!(result.message.is_some());
|
||||
assert_eq!(app.onboarding, OnboardingState::ApiKey);
|
||||
assert!(app.onboarding_needs_api_key);
|
||||
assert!(app.api_key_input.is_empty());
|
||||
assert_eq!(app.api_key_cursor, 0);
|
||||
|
||||
let updated = fs::read_to_string(config_path).unwrap();
|
||||
assert!(!updated.contains("api_key"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+105
-6
@@ -123,7 +123,7 @@ impl Config {
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn load(path: Option<PathBuf>, profile: Option<&str>) -> Result<Self> {
|
||||
let path = path.or_else(default_config_path);
|
||||
let path = resolve_load_config_path(path);
|
||||
let mut config = if let Some(path) = path.as_ref() {
|
||||
if path.exists() {
|
||||
let contents = fs::read_to_string(path)
|
||||
@@ -337,14 +337,52 @@ impl Config {
|
||||
// === Defaults ===
|
||||
|
||||
fn default_config_path() -> Option<PathBuf> {
|
||||
if let Ok(path) = std::env::var("DEEPSEEK_CONFIG_PATH")
|
||||
&& !path.trim().is_empty()
|
||||
{
|
||||
return Some(PathBuf::from(path));
|
||||
}
|
||||
env_config_path().or_else(home_config_path)
|
||||
}
|
||||
|
||||
fn home_config_path() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|home| home.join(".deepseek").join("config.toml"))
|
||||
}
|
||||
|
||||
fn env_config_path() -> Option<PathBuf> {
|
||||
if let Ok(path) = std::env::var("DEEPSEEK_CONFIG_PATH") {
|
||||
let trimmed = path.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(expand_path(trimmed));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn expand_pathbuf(path: PathBuf) -> PathBuf {
|
||||
if let Some(raw) = path.to_str() {
|
||||
return expand_path(raw);
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
fn resolve_load_config_path(path: Option<PathBuf>) -> Option<PathBuf> {
|
||||
if let Some(path) = path {
|
||||
return Some(expand_pathbuf(path));
|
||||
}
|
||||
|
||||
if let Some(path) = env_config_path() {
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
if let Some(home_path) = home_config_path()
|
||||
&& home_path.exists()
|
||||
{
|
||||
return Some(home_path);
|
||||
}
|
||||
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
home_config_path()
|
||||
}
|
||||
|
||||
fn default_managed_config_path() -> Option<PathBuf> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -850,6 +888,67 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_uses_tilde_expanded_deepseek_config_path() -> Result<()> {
|
||||
let _lock = env_lock().lock().unwrap();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"deepseek-cli-load-tilde-test-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let config_path = temp_root.join(".custom-deepseek").join("config.toml");
|
||||
ensure_parent_dir(&config_path)?;
|
||||
fs::write(&config_path, "api_key = \"test-key\"\n")?;
|
||||
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var("DEEPSEEK_CONFIG_PATH", "~/.custom-deepseek/config.toml");
|
||||
}
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_key.as_deref(), Some("test-key"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_falls_back_to_home_config_when_env_path_missing() -> Result<()> {
|
||||
let _lock = env_lock().lock().unwrap();
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let temp_root = env::temp_dir().join(format!(
|
||||
"deepseek-cli-load-fallback-test-{}-{}",
|
||||
std::process::id(),
|
||||
nanos
|
||||
));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
let _guard = EnvGuard::new(&temp_root);
|
||||
|
||||
let home_config = temp_root.join(".deepseek").join("config.toml");
|
||||
ensure_parent_dir(&home_config)?;
|
||||
fs::write(&home_config, "api_key = \"home-key\"\n")?;
|
||||
|
||||
// Safety: test-only environment mutation guarded by a global mutex.
|
||||
unsafe {
|
||||
env::set_var(
|
||||
"DEEPSEEK_CONFIG_PATH",
|
||||
temp_root.join("missing-config.toml").as_os_str(),
|
||||
);
|
||||
}
|
||||
|
||||
let config = Config::load(None, None)?;
|
||||
assert_eq!(config.api_key.as_deref(), Some("home-key"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonexistent_profile_error() {
|
||||
let mut profiles = HashMap::new();
|
||||
|
||||
Reference in New Issue
Block a user