release: v0.3.1
This commit is contained in:
+14
-1
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.1] - 2026-01-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `deepseek setup` to bootstrap MCP config and skills directories
|
||||||
|
- `deepseek mcp init` to generate a template `mcp.json` at the configured path
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `deepseek doctor` now follows the resolved config path and config-derived MCP/skills locations
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Doctor no longer reports missing MCP/skills when paths are overridden via config or env
|
||||||
|
|
||||||
## [0.3.0] - 2026-01-27
|
## [0.3.0] - 2026-01-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -127,7 +139,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Hooks system and config profiles
|
- Hooks system and config profiles
|
||||||
- Example skills and launch assets
|
- Example skills and launch assets
|
||||||
|
|
||||||
[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.3.0...HEAD
|
[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.3.1...HEAD
|
||||||
|
[0.3.1]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.3.0...v0.3.1
|
||||||
[0.3.0]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.2...v0.3.0
|
[0.3.0]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.2...v0.3.0
|
||||||
[0.2.2]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.1...v0.2.2
|
[0.2.2]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.1...v0.2.2
|
||||||
[0.2.1]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.0...v0.2.1
|
[0.2.1]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.0...v0.2.1
|
||||||
|
|||||||
Generated
+1
-1
@@ -646,7 +646,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deepseek-tui"
|
name = "deepseek-tui"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arboard",
|
"arboard",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deepseek-tui"
|
name = "deepseek-tui"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Unofficial DeepSeek CLI - Just run 'deepseek' to start chatting"
|
description = "Unofficial DeepSeek CLI - Just run 'deepseek' to start chatting"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ cargo install deepseek-tui --locked
|
|||||||
# Set your API key
|
# Set your API key
|
||||||
export DEEPSEEK_API_KEY="YOUR_DEEPSEEK_API_KEY"
|
export DEEPSEEK_API_KEY="YOUR_DEEPSEEK_API_KEY"
|
||||||
|
|
||||||
|
# Bootstrap MCP + skills templates (recommended)
|
||||||
|
deepseek setup
|
||||||
|
|
||||||
# Start chatting
|
# Start chatting
|
||||||
deepseek
|
deepseek
|
||||||
```
|
```
|
||||||
@@ -88,6 +91,9 @@ Useful environment variables:
|
|||||||
- `DEEPSEEK_CONFIG_PATH` (override config path)
|
- `DEEPSEEK_CONFIG_PATH` (override config path)
|
||||||
- `DEEPSEEK_MCP_CONFIG`, `DEEPSEEK_SKILLS_DIR`, `DEEPSEEK_NOTES_PATH`, `DEEPSEEK_MEMORY_PATH`, `DEEPSEEK_ALLOW_SHELL`, `DEEPSEEK_MAX_SUBAGENTS`
|
- `DEEPSEEK_MCP_CONFIG`, `DEEPSEEK_SKILLS_DIR`, `DEEPSEEK_NOTES_PATH`, `DEEPSEEK_MEMORY_PATH`, `DEEPSEEK_ALLOW_SHELL`, `DEEPSEEK_MAX_SUBAGENTS`
|
||||||
|
|
||||||
|
To bootstrap MCP and skills at their resolved locations, run `deepseek setup`. To
|
||||||
|
only create an MCP template, run `deepseek mcp init`.
|
||||||
|
|
||||||
See `config.example.toml` and `docs/CONFIGURATION.md` for a full reference.
|
See `config.example.toml` and `docs/CONFIGURATION.md` for a full reference.
|
||||||
|
|
||||||
## 🎮 Modes
|
## 🎮 Modes
|
||||||
@@ -141,7 +147,7 @@ DeepSeek CLI exposes a comprehensive set of tools to the model across 5 categori
|
|||||||
- **Workspace boundary**: File tools are restricted to `--workspace` unless you enable `/trust` (YOLO enables trust automatically).
|
- **Workspace boundary**: File tools are restricted to `--workspace` unless you enable `/trust` (YOLO enables trust automatically).
|
||||||
- **Approvals**: The TUI requests approval depending on mode and tool category (file writes, shell).
|
- **Approvals**: The TUI requests approval depending on mode and tool category (file writes, shell).
|
||||||
- **Web search**: `web_search` uses DuckDuckGo HTML results and is auto‑approved.
|
- **Web search**: `web_search` uses DuckDuckGo HTML results and is auto‑approved.
|
||||||
- **Skills**: Reusable workflows stored as `SKILL.md` directories (default: `~/.deepseek/skills`). Use `/skills` and `/skill <name>`.
|
- **Skills**: Reusable workflows stored as `SKILL.md` directories (default: `~/.deepseek/skills`, or `./skills` per workspace). Use `/skills` and `/skill <name>`. Bootstrap with `deepseek setup --skills` (add `--local` for `./skills`).
|
||||||
- **MCP**: Load external tool servers via `~/.deepseek/mcp.json` (supports `servers` and `mcpServers`). MCP tools currently execute without TUI approval prompts, so only enable servers you trust. See `docs/MCP.md`.
|
- **MCP**: Load external tool servers via `~/.deepseek/mcp.json` (supports `servers` and `mcpServers`). MCP tools currently execute without TUI approval prompts, so only enable servers you trust. See `docs/MCP.md`.
|
||||||
|
|
||||||
## 🧠 RLM (Reasoning & Large‑scale Memory)
|
## 🧠 RLM (Reasoning & Large‑scale Memory)
|
||||||
@@ -240,11 +246,18 @@ Set `DEEPSEEK_BASE_URL` to `https://api.deepseeki.com` (China).
|
|||||||
### Session issues
|
### Session issues
|
||||||
Run `deepseek sessions` and try `deepseek --resume latest`.
|
Run `deepseek sessions` and try `deepseek --resume latest`.
|
||||||
|
|
||||||
|
### Skills missing
|
||||||
|
Run `deepseek setup --skills` to create a global skills directory, or add `--local`
|
||||||
|
to create `./skills` for the current workspace. Then run `deepseek doctor` to see
|
||||||
|
which skills directory is selected.
|
||||||
|
|
||||||
### MCP tools missing
|
### MCP tools missing
|
||||||
Validate `~/.deepseek/mcp.json` (or `DEEPSEEK_MCP_CONFIG`) and restart.
|
Run `deepseek mcp init` (or `deepseek setup --mcp`), then restart. `deepseek doctor`
|
||||||
|
now checks the MCP path resolved from your config/env overrides.
|
||||||
|
|
||||||
### Sandbox errors (macOS)
|
### Sandbox errors (macOS)
|
||||||
Ensure `/usr/bin/sandbox-exec` exists (comes with macOS). For other platforms, sandboxing is limited.
|
Run `deepseek doctor` to confirm sandbox availability. On macOS, ensure
|
||||||
|
`/usr/bin/sandbox-exec` exists. For other platforms, sandboxing is limited.
|
||||||
|
|
||||||
## 📖 Documentation
|
## 📖 Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ Overrides:
|
|||||||
|
|
||||||
If both are set, `--config` wins. Environment variable overrides are applied after the file is loaded.
|
If both are set, `--config` wins. Environment variable overrides are applied after the file is loaded.
|
||||||
|
|
||||||
|
To bootstrap MCP and skills directories at their resolved paths, run `deepseek setup`.
|
||||||
|
To only scaffold MCP, run `deepseek mcp init`.
|
||||||
|
|
||||||
## Profiles
|
## Profiles
|
||||||
|
|
||||||
You can define multiple profiles in the same file:
|
You can define multiple profiles in the same file:
|
||||||
@@ -124,4 +127,9 @@ Use `deepseek features list` to inspect known flags and their effective state.
|
|||||||
|
|
||||||
## Notes On `deepseek doctor`
|
## Notes On `deepseek doctor`
|
||||||
|
|
||||||
`deepseek doctor` checks default locations under `~/.deepseek/` (including `config.toml` and `mcp.json`). If you override paths via `--config` or `DEEPSEEK_MCP_CONFIG`, the doctor output may not reflect those overrides.
|
`deepseek doctor` now follows the same config resolution rules as the rest of the CLI.
|
||||||
|
That means `--config` / `DEEPSEEK_CONFIG_PATH` are respected, and MCP/skills checks
|
||||||
|
use the resolved `mcp_config_path` / `skills_dir` (including env overrides).
|
||||||
|
|
||||||
|
To bootstrap missing MCP/skills paths, run `deepseek setup --all`. You can also
|
||||||
|
run `deepseek setup --skills --local` to create a workspace-local `./skills` dir.
|
||||||
|
|||||||
+14
-3
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
DeepSeek CLI can load additional tools via MCP (Model Context Protocol). MCP servers are local processes that the CLI starts and communicates with over stdio.
|
DeepSeek CLI can load additional tools via MCP (Model Context Protocol). MCP servers are local processes that the CLI starts and communicates with over stdio.
|
||||||
|
|
||||||
|
## Bootstrap MCP Config
|
||||||
|
|
||||||
|
Create a starter MCP config at your resolved MCP path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deepseek mcp init
|
||||||
|
```
|
||||||
|
|
||||||
|
`deepseek setup --mcp` performs the same MCP bootstrap alongside skills setup.
|
||||||
|
|
||||||
## Config File Location
|
## Config File Location
|
||||||
|
|
||||||
Default path:
|
Default path:
|
||||||
@@ -13,6 +23,8 @@ Overrides:
|
|||||||
- Config: `mcp_config_path = "/path/to/mcp.json"`
|
- Config: `mcp_config_path = "/path/to/mcp.json"`
|
||||||
- Env: `DEEPSEEK_MCP_CONFIG=/path/to/mcp.json`
|
- Env: `DEEPSEEK_MCP_CONFIG=/path/to/mcp.json`
|
||||||
|
|
||||||
|
`deepseek mcp init` (and `deepseek setup --mcp`) writes to this resolved path.
|
||||||
|
|
||||||
After editing the file, restart the TUI.
|
After editing the file, restart the TUI.
|
||||||
|
|
||||||
## Tool Naming
|
## Tool Naming
|
||||||
@@ -61,7 +73,6 @@ MCP tools currently execute without TUI approval prompts. Only configure MCP ser
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- Run `deepseek doctor` to confirm whether the default `~/.deepseek/mcp.json` exists.
|
- Run `deepseek doctor` to confirm the MCP config path it resolved and whether it exists.
|
||||||
- If you override `mcp_config_path` / `DEEPSEEK_MCP_CONFIG`, note that `deepseek doctor` still checks `~/.deepseek/mcp.json`.
|
- If the MCP config is missing, run `deepseek mcp init --force` to regenerate it.
|
||||||
- If tools don’t appear, verify the server command works from your shell and that the server supports MCP `tools/list`.
|
- If tools don’t appear, verify the server command works from your shell and that the server supports MCP `tools/list`.
|
||||||
|
|
||||||
|
|||||||
+344
-64
@@ -61,8 +61,9 @@ mod working_set;
|
|||||||
|
|
||||||
use crate::config::{Config, MAX_SUBAGENTS};
|
use crate::config::{Config, MAX_SUBAGENTS};
|
||||||
use crate::eval::{EvalHarness, EvalHarnessConfig, ScenarioStepKind};
|
use crate::eval::{EvalHarness, EvalHarnessConfig, ScenarioStepKind};
|
||||||
|
use crate::features::Feature;
|
||||||
use crate::llm_client::LlmClient;
|
use crate::llm_client::LlmClient;
|
||||||
use crate::mcp::{McpConfig, McpPool};
|
use crate::mcp::{McpConfig, McpPool, McpServerConfig};
|
||||||
use crate::models::{ContentBlock, Message, MessageRequest, SystemPrompt};
|
use crate::models::{ContentBlock, Message, MessageRequest, SystemPrompt};
|
||||||
use crate::session_manager::{SessionManager, create_saved_session};
|
use crate::session_manager::{SessionManager, create_saved_session};
|
||||||
use crate::tui::history::{summarize_tool_args, summarize_tool_output};
|
use crate::tui::history::{summarize_tool_args, summarize_tool_output};
|
||||||
@@ -133,6 +134,8 @@ struct Cli {
|
|||||||
enum Commands {
|
enum Commands {
|
||||||
/// Run system diagnostics and check configuration
|
/// Run system diagnostics and check configuration
|
||||||
Doctor,
|
Doctor,
|
||||||
|
/// Bootstrap MCP config and/or skills directories
|
||||||
|
Setup(SetupArgs),
|
||||||
/// Generate shell completions
|
/// Generate shell completions
|
||||||
Completions {
|
Completions {
|
||||||
/// Shell to generate completions for
|
/// Shell to generate completions for
|
||||||
@@ -214,6 +217,25 @@ struct ExecArgs {
|
|||||||
auto: bool,
|
auto: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug, Clone, Default)]
|
||||||
|
struct SetupArgs {
|
||||||
|
/// Initialize MCP configuration at the configured path
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
mcp: bool,
|
||||||
|
/// Initialize skills directory and an example skill
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
skills: bool,
|
||||||
|
/// Initialize both MCP config and skills (default when no flags provided)
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
all: bool,
|
||||||
|
/// Create a local workspace skills directory (./skills)
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
local: bool,
|
||||||
|
/// Overwrite existing template files
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug, Clone)]
|
#[derive(Args, Debug, Clone)]
|
||||||
struct EvalArgs {
|
struct EvalArgs {
|
||||||
/// Intentionally fail a specific step (list, read, search, edit, patch, shell)
|
/// Intentionally fail a specific step (list, read, search, edit, patch, shell)
|
||||||
@@ -293,6 +315,12 @@ struct ServeArgs {
|
|||||||
enum McpCommand {
|
enum McpCommand {
|
||||||
/// List configured MCP servers
|
/// List configured MCP servers
|
||||||
List,
|
List,
|
||||||
|
/// Create a template MCP config at the configured path
|
||||||
|
Init {
|
||||||
|
/// Overwrite an existing MCP config file
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
force: bool,
|
||||||
|
},
|
||||||
/// Connect to MCP servers and report status
|
/// Connect to MCP servers and report status
|
||||||
Connect {
|
Connect {
|
||||||
/// Optional server name to connect to
|
/// Optional server name to connect to
|
||||||
@@ -378,9 +406,16 @@ async fn main() -> Result<()> {
|
|||||||
if let Some(command) = cli.command.clone() {
|
if let Some(command) = cli.command.clone() {
|
||||||
return match command {
|
return match command {
|
||||||
Commands::Doctor => {
|
Commands::Doctor => {
|
||||||
run_doctor().await;
|
let config = load_config_from_cli(&cli)?;
|
||||||
|
let workspace = resolve_workspace(&cli);
|
||||||
|
run_doctor(&config, &workspace, cli.config.as_deref()).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Commands::Setup(args) => {
|
||||||
|
let config = load_config_from_cli(&cli)?;
|
||||||
|
let workspace = resolve_workspace(&cli);
|
||||||
|
run_setup(&config, &workspace, args)
|
||||||
|
}
|
||||||
Commands::Completions { shell } => {
|
Commands::Completions { shell } => {
|
||||||
generate_completions(shell);
|
generate_completions(shell);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -562,8 +597,175 @@ fn run_eval(args: EvalArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum WriteStatus {
|
||||||
|
Created,
|
||||||
|
Overwritten,
|
||||||
|
SkippedExists,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_parent_dir(path: &Path) -> Result<()> {
|
||||||
|
if let Some(parent) = path.parent()
|
||||||
|
&& !parent.as_os_str().is_empty()
|
||||||
|
{
|
||||||
|
std::fs::create_dir_all(parent).with_context(|| {
|
||||||
|
format!("Failed to create directory for {}", parent.display())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_template_file(path: &Path, contents: &str, force: bool) -> Result<WriteStatus> {
|
||||||
|
ensure_parent_dir(path)?;
|
||||||
|
|
||||||
|
if path.exists() && !force {
|
||||||
|
return Ok(WriteStatus::SkippedExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = if path.exists() {
|
||||||
|
WriteStatus::Overwritten
|
||||||
|
} else {
|
||||||
|
WriteStatus::Created
|
||||||
|
};
|
||||||
|
|
||||||
|
std::fs::write(path, contents)
|
||||||
|
.with_context(|| format!("Failed to write template at {}", path.display()))?;
|
||||||
|
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mcp_template_json() -> Result<String> {
|
||||||
|
let mut cfg = McpConfig::default();
|
||||||
|
cfg.servers.insert(
|
||||||
|
"example".to_string(),
|
||||||
|
McpServerConfig {
|
||||||
|
command: "node".to_string(),
|
||||||
|
args: vec!["./path/to/your-mcp-server.js".to_string()],
|
||||||
|
env: std::collections::HashMap::new(),
|
||||||
|
connect_timeout: None,
|
||||||
|
execute_timeout: None,
|
||||||
|
read_timeout: None,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
serde_json::to_string_pretty(&cfg)
|
||||||
|
.map_err(|e| anyhow!("Failed to render MCP template JSON: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_mcp_config(path: &Path, force: bool) -> Result<WriteStatus> {
|
||||||
|
let template = mcp_template_json()?;
|
||||||
|
write_template_file(path, &template, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skills_template(name: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"\
|
||||||
|
---\n\
|
||||||
|
name: {name}\n\
|
||||||
|
description: Quick repo diagnostics and setup guidance\n\
|
||||||
|
allowed-tools: diagnostics, list_dir, read_file, grep_files, git_status, git_diff\n\
|
||||||
|
---\n\n\
|
||||||
|
When this skill is active:\n\
|
||||||
|
1. Run the diagnostics tool to report workspace and sandbox status.\n\
|
||||||
|
2. Skim key project files (README.md, Cargo.toml, AGENTS.md) before editing.\n\
|
||||||
|
3. Prefer small, validated changes and summarize what you verified.\n\
|
||||||
|
"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_skills_dir(skills_dir: &Path, force: bool) -> Result<(PathBuf, WriteStatus)> {
|
||||||
|
std::fs::create_dir_all(skills_dir)
|
||||||
|
.with_context(|| format!("Failed to create skills dir {}", skills_dir.display()))?;
|
||||||
|
|
||||||
|
let skill_name = "getting-started";
|
||||||
|
let skill_path = skills_dir.join(skill_name).join("SKILL.md");
|
||||||
|
ensure_parent_dir(&skill_path)?;
|
||||||
|
|
||||||
|
let status = write_template_file(&skill_path, &skills_template(skill_name), force)?;
|
||||||
|
Ok((skill_path, status))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_setup(config: &Config, workspace: &Path, args: SetupArgs) -> Result<()> {
|
||||||
|
use crate::palette;
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
let (aqua_r, aqua_g, aqua_b) = palette::DEEPSEEK_SKY_RGB;
|
||||||
|
let (sky_r, sky_g, sky_b) = palette::DEEPSEEK_SKY_RGB;
|
||||||
|
|
||||||
|
let mut run_mcp = args.mcp || args.all;
|
||||||
|
let mut run_skills = args.skills || args.all;
|
||||||
|
if !run_mcp && !run_skills {
|
||||||
|
run_mcp = true;
|
||||||
|
run_skills = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
"DeepSeek Setup"
|
||||||
|
.truecolor(aqua_r, aqua_g, aqua_b)
|
||||||
|
.bold()
|
||||||
|
);
|
||||||
|
println!("{}", "==============".truecolor(sky_r, sky_g, sky_b));
|
||||||
|
println!("Workspace: {}", workspace.display());
|
||||||
|
|
||||||
|
if run_mcp {
|
||||||
|
let mcp_path = config.mcp_config_path();
|
||||||
|
let status = init_mcp_config(&mcp_path, args.force)?;
|
||||||
|
match status {
|
||||||
|
WriteStatus::Created => {
|
||||||
|
println!(" ✓ Created MCP config at {}", mcp_path.display());
|
||||||
|
}
|
||||||
|
WriteStatus::Overwritten => {
|
||||||
|
println!(" ✓ Overwrote MCP config at {}", mcp_path.display());
|
||||||
|
}
|
||||||
|
WriteStatus::SkippedExists => {
|
||||||
|
println!(" · MCP config already exists at {}", mcp_path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(" Next: edit the file, then run `deepseek mcp list` or `deepseek mcp tools`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if run_skills {
|
||||||
|
let skills_dir = if args.local {
|
||||||
|
workspace.join("skills")
|
||||||
|
} else {
|
||||||
|
config.skills_dir()
|
||||||
|
};
|
||||||
|
let (skill_path, status) = init_skills_dir(&skills_dir, args.force)?;
|
||||||
|
match status {
|
||||||
|
WriteStatus::Created => {
|
||||||
|
println!(" ✓ Created example skill at {}", skill_path.display());
|
||||||
|
}
|
||||||
|
WriteStatus::Overwritten => {
|
||||||
|
println!(" ✓ Overwrote example skill at {}", skill_path.display());
|
||||||
|
}
|
||||||
|
WriteStatus::SkippedExists => {
|
||||||
|
println!(" · Example skill already exists at {}", skill_path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if args.local {
|
||||||
|
println!(
|
||||||
|
" Local skills dir enabled for this workspace: {}",
|
||||||
|
skills_dir.display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(" Skills dir: {}", skills_dir.display());
|
||||||
|
}
|
||||||
|
println!(" Next: run the TUI and use `/skills` then `/skill getting-started`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let sandbox = crate::sandbox::get_platform_sandbox();
|
||||||
|
if let Some(kind) = sandbox {
|
||||||
|
println!(" ✓ Sandbox available: {kind}");
|
||||||
|
} else {
|
||||||
|
println!(" · Sandbox not available on this platform (best-effort only).");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Run system diagnostics
|
/// Run system diagnostics
|
||||||
async fn run_doctor() {
|
async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Option<&Path>) {
|
||||||
use crate::palette;
|
use crate::palette;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
@@ -587,24 +789,29 @@ async fn run_doctor() {
|
|||||||
println!(" rust: {}", rustc_version());
|
println!(" rust: {}", rustc_version());
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
// Check configuration
|
// Configuration summary
|
||||||
println!("{}", "Configuration:".bold());
|
println!("{}", "Configuration:".bold());
|
||||||
let config_dir =
|
let default_config_dir =
|
||||||
dirs::home_dir().map_or_else(|| PathBuf::from(".deepseek"), |h| h.join(".deepseek"));
|
dirs::home_dir().map_or_else(|| PathBuf::from(".deepseek"), |h| h.join(".deepseek"));
|
||||||
|
let config_path = config_path_override
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| std::env::var("DEEPSEEK_CONFIG_PATH").ok().map(PathBuf::from))
|
||||||
|
.unwrap_or_else(|| default_config_dir.join("config.toml"));
|
||||||
|
|
||||||
let config_file = config_dir.join("config.toml");
|
if config_path.exists() {
|
||||||
if config_file.exists() {
|
|
||||||
println!(
|
println!(
|
||||||
" {} config.toml found at {}",
|
" {} config.toml found at {}",
|
||||||
"✓".truecolor(aqua_r, aqua_g, aqua_b),
|
"✓".truecolor(aqua_r, aqua_g, aqua_b),
|
||||||
config_file.display()
|
config_path.display()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
" {} config.toml not found (will use defaults)",
|
" {} config.toml not found at {} (using defaults/env)",
|
||||||
"!".truecolor(sky_r, sky_g, sky_b)
|
"!".truecolor(sky_r, sky_g, sky_b),
|
||||||
|
config_path.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
println!(" workspace: {}", workspace.display());
|
||||||
|
|
||||||
// Check API keys
|
// Check API keys
|
||||||
println!();
|
println!();
|
||||||
@@ -615,25 +822,19 @@ async fn run_doctor() {
|
|||||||
"✓".truecolor(aqua_r, aqua_g, aqua_b)
|
"✓".truecolor(aqua_r, aqua_g, aqua_b)
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
|
} else if config.deepseek_api_key().is_ok() {
|
||||||
|
println!(
|
||||||
|
" {} DeepSeek API key found in effective config",
|
||||||
|
"✓".truecolor(aqua_r, aqua_g, aqua_b)
|
||||||
|
);
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
let key_in_config = Config::load(None, None)
|
println!(
|
||||||
.ok()
|
" {} DeepSeek API key not configured",
|
||||||
.and_then(|c| c.deepseek_api_key().ok())
|
"✗".truecolor(red_r, red_g, red_b)
|
||||||
.is_some();
|
);
|
||||||
if key_in_config {
|
println!(" Run 'deepseek' to configure interactively, or set DEEPSEEK_API_KEY");
|
||||||
println!(
|
false
|
||||||
" {} DeepSeek API key found in config",
|
|
||||||
"✓".truecolor(aqua_r, aqua_g, aqua_b)
|
|
||||||
);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
" {} DeepSeek API key not configured",
|
|
||||||
"✗".truecolor(red_r, red_g, red_b)
|
|
||||||
);
|
|
||||||
println!(" Run 'deepseek' to configure interactively, or set DEEPSEEK_API_KEY");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// API connectivity test
|
// API connectivity test
|
||||||
@@ -641,7 +842,6 @@ async fn run_doctor() {
|
|||||||
println!("{}", "API Connectivity:".bold());
|
println!("{}", "API Connectivity:".bold());
|
||||||
if has_api_key {
|
if has_api_key {
|
||||||
print!(" {} Testing connection to DeepSeek API...", "·".dimmed());
|
print!(" {} Testing connection to DeepSeek API...", "·".dimmed());
|
||||||
// Flush to show progress immediately
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
std::io::stdout().flush().ok();
|
std::io::stdout().flush().ok();
|
||||||
|
|
||||||
@@ -659,7 +859,6 @@ async fn run_doctor() {
|
|||||||
"\r {} API connection failed",
|
"\r {} API connection failed",
|
||||||
"✗".truecolor(red_r, red_g, red_b)
|
"✗".truecolor(red_r, red_g, red_b)
|
||||||
);
|
);
|
||||||
// Provide helpful diagnostics based on error type
|
|
||||||
if error_msg.contains("401") || error_msg.contains("Unauthorized") {
|
if error_msg.contains("401") || error_msg.contains("Unauthorized") {
|
||||||
println!(" Invalid API key. Check your DEEPSEEK_API_KEY or config.toml");
|
println!(" Invalid API key. Check your DEEPSEEK_API_KEY or config.toml");
|
||||||
} else if error_msg.contains("403") || error_msg.contains("Forbidden") {
|
} else if error_msg.contains("403") || error_msg.contains("Forbidden") {
|
||||||
@@ -681,68 +880,124 @@ async fn run_doctor() {
|
|||||||
println!(" {} Skipped (no API key configured)", "·".dimmed());
|
println!(" {} Skipped (no API key configured)", "·".dimmed());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check MCP configuration
|
// MCP configuration
|
||||||
println!();
|
println!();
|
||||||
println!("{}", "MCP Servers:".bold());
|
println!("{}", "MCP Servers:".bold());
|
||||||
let mcp_config = config_dir.join("mcp.json");
|
let features = config.features();
|
||||||
if mcp_config.exists() {
|
if features.enabled(Feature::Mcp) {
|
||||||
println!(" {} mcp.json found", "✓".truecolor(aqua_r, aqua_g, aqua_b));
|
println!(" {} MCP feature flag enabled", "✓".truecolor(aqua_r, aqua_g, aqua_b));
|
||||||
if let Ok(content) = std::fs::read_to_string(&mcp_config)
|
} else {
|
||||||
&& let Ok(config) = serde_json::from_str::<crate::mcp::McpConfig>(&content)
|
println!(" {} MCP feature flag disabled", "!".truecolor(sky_r, sky_g, sky_b));
|
||||||
{
|
}
|
||||||
if config.servers.is_empty() {
|
|
||||||
|
let mcp_config_path = config.mcp_config_path();
|
||||||
|
if mcp_config_path.exists() {
|
||||||
|
println!(
|
||||||
|
" {} MCP config found at {}",
|
||||||
|
"✓".truecolor(aqua_r, aqua_g, aqua_b),
|
||||||
|
mcp_config_path.display()
|
||||||
|
);
|
||||||
|
match load_mcp_config(&mcp_config_path) {
|
||||||
|
Ok(cfg) if cfg.servers.is_empty() => {
|
||||||
println!(" {} 0 server(s) configured", "·".dimmed());
|
println!(" {} 0 server(s) configured", "·".dimmed());
|
||||||
} else {
|
}
|
||||||
|
Ok(cfg) => {
|
||||||
println!(
|
println!(
|
||||||
" {} {} server(s) configured",
|
" {} {} server(s) configured",
|
||||||
"·".dimmed(),
|
"·".dimmed(),
|
||||||
config.servers.len()
|
cfg.servers.len()
|
||||||
);
|
);
|
||||||
for name in config.servers.keys() {
|
for name in cfg.servers.keys() {
|
||||||
println!(" - {name}");
|
println!(" - {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!(
|
||||||
|
" {} MCP config parse error: {}",
|
||||||
|
"✗".truecolor(red_r, red_g, red_b),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!(" {} mcp.json not found (no MCP servers)", "·".dimmed());
|
println!(
|
||||||
|
" {} MCP config not found at {}",
|
||||||
|
"·".dimmed(),
|
||||||
|
mcp_config_path.display()
|
||||||
|
);
|
||||||
|
println!(" Run `deepseek mcp init` or `deepseek setup --mcp`.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check skills directory
|
// Skills configuration
|
||||||
println!();
|
println!();
|
||||||
println!("{}", "Skills:".bold());
|
println!("{}", "Skills:".bold());
|
||||||
let skills_dir = config_dir.join("skills");
|
let global_skills_dir = config.skills_dir();
|
||||||
if skills_dir.exists() {
|
let local_skills_dir = workspace.join("skills");
|
||||||
let skill_count = std::fs::read_dir(skills_dir)
|
let selected_skills_dir = if local_skills_dir.exists() {
|
||||||
|
&local_skills_dir
|
||||||
|
} else {
|
||||||
|
&global_skills_dir
|
||||||
|
};
|
||||||
|
|
||||||
|
let describe_dir = |dir: &Path| -> usize {
|
||||||
|
std::fs::read_dir(dir)
|
||||||
.map(|entries| entries.filter_map(std::result::Result::ok).count())
|
.map(|entries| entries.filter_map(std::result::Result::ok).count())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
if local_skills_dir.exists() {
|
||||||
println!(
|
println!(
|
||||||
" {} skills directory found ({} items)",
|
" {} local skills dir found at {} ({} items)",
|
||||||
"✓".truecolor(aqua_r, aqua_g, aqua_b),
|
"✓".truecolor(aqua_r, aqua_g, aqua_b),
|
||||||
skill_count
|
local_skills_dir.display(),
|
||||||
|
describe_dir(&local_skills_dir)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!(" {} skills directory not found", "·".dimmed());
|
println!(
|
||||||
|
" {} local skills dir not found at {}",
|
||||||
|
"·".dimmed(),
|
||||||
|
local_skills_dir.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Platform-specific checks
|
if global_skills_dir.exists() {
|
||||||
|
println!(
|
||||||
|
" {} global skills dir found at {} ({} items)",
|
||||||
|
"✓".truecolor(aqua_r, aqua_g, aqua_b),
|
||||||
|
global_skills_dir.display(),
|
||||||
|
describe_dir(&global_skills_dir)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
" {} global skills dir not found at {}",
|
||||||
|
"·".dimmed(),
|
||||||
|
global_skills_dir.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(" {} selected skills dir: {}", "·".dimmed(), selected_skills_dir.display());
|
||||||
|
if !local_skills_dir.exists() && !global_skills_dir.exists() {
|
||||||
|
println!(" Run `deepseek setup --skills` (or add --local for ./skills).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform and sandbox checks
|
||||||
println!();
|
println!();
|
||||||
println!("{}", "Platform:".bold());
|
println!("{}", "Platform:".bold());
|
||||||
println!(" OS: {}", std::env::consts::OS);
|
println!(" OS: {}", std::env::consts::OS);
|
||||||
println!(" Arch: {}", std::env::consts::ARCH);
|
println!(" Arch: {}", std::env::consts::ARCH);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
let sandbox = crate::sandbox::get_platform_sandbox();
|
||||||
{
|
if let Some(kind) = sandbox {
|
||||||
if std::path::Path::new("/usr/bin/sandbox-exec").exists() {
|
println!(
|
||||||
println!(
|
" {} sandbox available: {}",
|
||||||
" {} macOS sandbox available",
|
"✓".truecolor(aqua_r, aqua_g, aqua_b),
|
||||||
"✓".truecolor(aqua_r, aqua_g, aqua_b)
|
kind
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
" {} macOS sandbox not available",
|
" {} sandbox not available (commands run best-effort)",
|
||||||
"!".truecolor(sky_r, sky_g, sky_b)
|
"!".truecolor(sky_r, sky_g, sky_b)
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
@@ -946,6 +1201,12 @@ fn init_project() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_workspace(cli: &Cli) -> PathBuf {
|
||||||
|
cli.workspace
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
|
||||||
|
}
|
||||||
|
|
||||||
fn load_config_from_cli(cli: &Cli) -> Result<Config> {
|
fn load_config_from_cli(cli: &Cli) -> Result<Config> {
|
||||||
let profile = cli
|
let profile = cli
|
||||||
.profile
|
.profile
|
||||||
@@ -1173,6 +1434,25 @@ fn read_patch_from_stdin() -> Result<String> {
|
|||||||
async fn run_mcp_command(config: &Config, command: McpCommand) -> Result<()> {
|
async fn run_mcp_command(config: &Config, command: McpCommand) -> Result<()> {
|
||||||
let config_path = config.mcp_config_path();
|
let config_path = config.mcp_config_path();
|
||||||
match command {
|
match command {
|
||||||
|
McpCommand::Init { force } => {
|
||||||
|
let status = init_mcp_config(&config_path, force)?;
|
||||||
|
match status {
|
||||||
|
WriteStatus::Created => {
|
||||||
|
println!("Created MCP config at {}", config_path.display());
|
||||||
|
}
|
||||||
|
WriteStatus::Overwritten => {
|
||||||
|
println!("Overwrote MCP config at {}", config_path.display());
|
||||||
|
}
|
||||||
|
WriteStatus::SkippedExists => {
|
||||||
|
println!(
|
||||||
|
"MCP config already exists at {} (use --force to overwrite)",
|
||||||
|
config_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Edit the file, then run `deepseek mcp list` or `deepseek mcp tools`.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
McpCommand::List => {
|
McpCommand::List => {
|
||||||
let cfg = load_mcp_config(&config_path)?;
|
let cfg = load_mcp_config(&config_path)?;
|
||||||
if cfg.servers.is_empty() {
|
if cfg.servers.is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user