* feat(tui): show skills in slash command autocomplete menu
- add SlashMenuEntry struct with name, description, is_skill fields
- cache skill names/descriptions in App to avoid per-keystroke disk I/O
- render slash popup as two-column layout with precise Unicode width truncation
- include skill names in Tab autocomplete and visible_slash_menu_entries
- refresh skill cache after install/uninstall
* style(tui): apply rustfmt to slash menu and widgets
* fix: complete skills with valid slash command form
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
Previously, ModelRegistry::resolve() lowercased the requested model name
before looking it up in the alias map, and always returned the registry's
canonical (lowercase) model ID. This broke third-party API providers
that enforce case-sensitive model name matching.
Now when the resolved model ID differs from the requested name only in
case (eq_ignore_ascii_case), the requested casing is preserved.
Closes#729
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
percent_decode in web_run.rs builds the result via `out.push(b as char)`,
mapping each decoded byte to its Latin-1 codepoint. For percent-encoded
multi-byte UTF-8 sequences (e.g. %E4%B8%AA = 个) this produces visible
mojibake: bytes E4 B8 AA become three Latin-1 chars `个` and the final
String is the UTF-8 encoding of those codepoints, not the original
character.
The sister module web_search.rs::percent_decode (line 490) already uses
the correct pattern: collect bytes into Vec<u8>, then finalize with
String::from_utf8_lossy. Align web_run.rs with that implementation.
Affects DuckDuckGo redirect URLs containing non-ASCII paths, where
normalize_search_url -> percent_decode previously corrupted the decoded
URL before it was shown to the model and to the user.
Add a regression test covering percent-encoded CJK input, raw UTF-8
input, and mixed ASCII+UTF-8.
Co-authored-by: Elowen <xrnc@outlook.com>
* fix(docker): remove misleading ENV, add explicit UID/GID, add .dockerignore
- Removed `ENV DEEPSEEK_API_KEY=""` and `ENV DEEPSEEK_NO_COLOR=""`:
API keys should never be baked into image layers, even as empty strings.
Added comments documenting runtime secret passing patterns.
- Added explicit UID/GID (1000:1000) for the `deepseek` user:
Makes filesystem ownership unambiguous when mounting volumes and
avoids the default auto-assigned UID shifting between hosts.
- Added `.dockerignore`:
Prevents accidental inclusion of .env files, local runtime state,
documentation, dev configs, and build artifacts into the build
context, keeping the image smaller and avoiding secret leaks.
* fix(docker): keep nested build inputs in context
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
* feat(commands): add /rename command to set a custom session title
Adds a `/rename <new title>` slash command that lets users set a
human-readable name for the current session. The new title is
persisted immediately to the session JSON file so it appears in
the session picker on the next open.
- Max title length capped at 100 characters (char-count aware, handles CJK)
- Errors on missing/empty arg or no active session
- Inner `rename_with_manager` helper keeps unit tests fully isolated
from ~/.deepseek/sessions
- Localized descriptions in en, ja, zh-Hans, pt-BR
* fix(rename): sync App state before saving to prevent data loss
Use update_session() to merge current in-memory messages and tokens
into the session before writing the renamed title, preventing stale
disk data from overwriting unsaved App state.
* style: format rename command
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
* fix(tools): enforce network policy in web_run
The web_run tool bypassed the network policy that fetch_url and
web_search respect. URL fetches (open/click) now check the configured
network policy before making outbound requests, consistent with other
network tools.
* fix: gate web run fetches by network policy
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
create_test_app() in commands/{core,debug,provider}.rs constructs App
via App::new, which reads system LANG. On a Chinese host this
auto-selects ZhHans for ui_locale (translating asserted strings) and
DeepseekCN for api_provider (which then differs from the
ApiProvider::Deepseek that tests pass to provider()).
Pin both fields after construction, matching the existing pattern in
home_dashboard_localizes_in_zh_hans which already sets app.ui_locale
to test Chinese rendering.
All 17 tests listed in the issue now pass on a host with
LANG=zh_CN.UTF-8.
Closes#791.
Co-authored-by: Elowen <xrnc@outlook.com>
* fix: write config files with restrictive permissions in all paths
Three config-file write paths in the TUI crate used `fs::write()` with
default permissions (typically 0644), leaving API keys world-readable:
- `save_api_key_to_config_file` (initial key storage)
- `save_api_key_for` (provider-specific key storage)
- `clear_api_keys_from_config_file` (logout/credential wipe)
Add a `write_config_file_secure` helper that uses `OpenOptions` with
mode 0o600 on Unix, and route all config writes through it. Also harden
`ensure_parent_dir` to strip group/other permissions from the config
directory.
* fix: harden tui config file permissions
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
* fix: save config with restrictive permissions and improve secret redaction
- Config files containing API keys were written with default permissions
(typically 0644), making them world-readable on multi-user systems. Use
OpenOptions with mode 0o600 on Unix to restrict access to the file owner.
- `redact_secret` threshold raised from 8 to 16 characters — previously a
9-character secret would leak 8 of its 9 characters (4 prefix + 4
suffix). Now secrets up to 16 chars are fully masked with "********".
* fix(config): keep secret saves warning-free on windows
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
Three `unreachable!()` calls panicked if `save_api_key_for` or the API key
apply path ever received a DeepSeek/DeepseekCN variant past the early-return
guard. Replace them with explicit `Err` returns so a future refactor that
breaks the guard produces a recoverable error instead of a crash.
* fix(command_safety): fix path_escapes_workspace false positive for ".." in names
The path_escapes_workspace function used `path.contains("..")` to detect
directory traversal, which incorrectly flagged paths containing consecutive
dots in file or directory names (e.g., `foo..bar`, `dir..name/file.txt`).
Replace the substring matching with a component-level walk that tracks
nesting depth. Each `..` path component decrements the depth; if depth
ever goes negative the path has escaped the workspace. Non-`..` components
increment depth. This correctly:
- Flags genuine traversal like `../outside` and `./sub/../../etc/passwd`
- Ignores names with embedded double-dots like `some..file.txt`
- Allows safe intra-workspace `..` usage like `./subdir/../other`
* test(command_safety): cover absolute traversal paths
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
The `execute` method of `ShellWaitTool` is async, but used
`std::thread::sleep` which blocks the tokio worker thread. Replace with
`tokio::time::sleep().await` so other tasks can make progress during the
50ms poll interval.
* feat(tui): support Shift+Enter to insert newline in composer
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(tui): cover shift-enter newline handling
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Hunter Bown <hmbown@gmail.com>
* fix(web_search): complete HTML entity decoding with numeric character references
The decode_html_entities function only handled 7 named entities and lacked
support for decimal (&#NN;) and hex (&#xHH;) numeric character references,
which are common in search engine result snippets. This caused garbled text.
Replace the hard-coded replace chains with a regex-based decoder that
handles:
- All named entities (amp, lt, gt, quot, apos, nbsp, copy, reg, mdash,
ndash, lsquo, rsquo, ldquo, rdquo, hellip)
- Decimal numeric references (A → A)
- Hex numeric references (A → A)
- Unknown entities are passed through unchanged
* style(web_search): apply rustfmt
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
* fix(snapshot): refuse to snapshot home directory (#793)
When the TUI is launched from $HOME, the snapshot system would run
`git add -A` on the entire home directory, consuming unbounded CPU and
disk. This manifests as a multi-GB side-repo under ~/.deepseek/snapshots
and makes the TUI appear frozen.
Add a guard in SnapshotRepo::open_or_init that compares the canonical
workspace path against the canonical home directory and returns an error
if they match. The error is non-fatal (snapshots are a safety net, not a
correctness gate) so the turn loop continues without snapshots.
Closes#793
* test(snapshot): fix home guard test portability
* test(snapshot): avoid env-dependent home guard test
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
Allow model IDs like accounts/fireworks/models/deepseek-v4-flash
by checking for /deepseek in addition to starts_with(deepseek).
Fixes#753
Signed-off-by: wucm667 <stevenwucongmin@gmail.com>
* Revise DeepSeek Pro pricing and service details
Update pricing and service notes for DeepSeek Pro.
* Update README with DeepSeek-V4-Pro pricing note
Added note about DeepSeek-V4-Pro service throughput and expected price reduction.
* Update README.zh-CN.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* Update README.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* Revise DeepSeek-V4-Pro pricing and service notes
Updated pricing and service throughput information for DeepSeek-V4-Pro.
* Update README.zh-CN.md
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* Add shell completion setup examples
Show actionable bash, zsh, and fish completion setup commands in the completion subcommand help, and cover them in the CLI help
surface test.
* docs(cli): clarify completion setup examples
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
Keep auto as a local routing mode, resolve concrete model/thinking before API requests, and wire auto routing through CLI, TUI, runtime threads, and subagents.
- Fix Ctrl-S: stash current draft, not reverse history search
- Add Alt-R: search prompt history
- Note bare Up/Down arrows now scroll transcript when composer is empty (v0.8.13)
- Remove phantom Alt+Up from audit notes
- Note tui.toml wiring still deferred
- Remove dated 'v0.8.11 follow-up' reference in header
The should_scroll_with_arrows gate was hardcoded to false since the
initial commit, which meant bare Up/Down arrows always navigated
composer history — they never scrolled the transcript view. Users in
virtual terminals (Ghostty, Codex, Kitty-protocol terminals) were
especially affected because they couldn't use the specialized Cmd+Up
/ Alt+Up shortcuts.
The gate now returns true when the composer input is empty (or
whitespace-only), so bare arrows scroll the transcript. When text
is present, arrows still navigate composer history to recall
previous prompts.
Added 3 tests covering empty, whitespace-only, and text-filled
composer states.