deepy-cli 0.2.18__tar.gz → 0.2.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/PKG-INFO +9 -3
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/README.md +8 -2
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/pyproject.toml +1 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/cli.py +5 -6
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/config/settings.py +11 -5
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/sessions/jsonl.py +0 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/app.py +2 -3
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/commands.py +20 -8
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/screens.py +1 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/widgets.py +8 -4
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/prompt_input.py +41 -4
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/slash_commands.py +106 -4
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/status_footer.py +1 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/terminal.py +583 -349
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/theme_picker.py +1 -2
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/background_tasks.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/config/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/Search.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/apply_patch.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/edit_text.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/read_file.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/task_list.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/task_output.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/task_stop.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/test_shell.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/todo_write.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/data/tools/write_file.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/input_suggestions.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/agent.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/provider.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/runner.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/mcp.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/prompts/system.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/prompts/tool_docs.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/session_cost.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/skills.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/status.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/subagents.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/todos.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/agents.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/builtin.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/search.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tools/test_shell.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/compat.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/diff.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/runner.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/tui/state.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/types/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/types/sdk.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/types/tool_payloads.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/exit_summary.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/local_command.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/message_view.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/model_picker.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/skill_picker.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/ui/welcome.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.20}/src/deepy/utils/notify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: deepy-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.20
|
|
4
4
|
Summary: Deepy - Vibe coding for DeepSeek models in your terminal
|
|
5
5
|
Keywords: deepseek,coding-agent,terminal,cli,agents
|
|
6
6
|
Author: kirineko
|
|
@@ -43,7 +43,9 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
</p>
|
|
44
44
|
|
|
45
45
|
<p align="center">
|
|
46
|
-
<a href="https://kirineko.
|
|
46
|
+
<a href="https://deepy.kirineko.tech/"><strong>Install Website</strong></a>
|
|
47
|
+
·
|
|
48
|
+
<a href="https://kirineko.github.io/deepy/">GitHub Pages</a>
|
|
47
49
|
·
|
|
48
50
|
<a href="README.zh-CN.md">中文文档</a>
|
|
49
51
|
·
|
|
@@ -54,6 +56,8 @@ Description-Content-Type: text/markdown
|
|
|
54
56
|
|
|
55
57
|

|
|
56
58
|
|
|
59
|
+
> Install and setup guide: **https://deepy.kirineko.tech/**
|
|
60
|
+
|
|
57
61
|
## What Deepy Does
|
|
58
62
|
|
|
59
63
|
Deepy is a Python CLI coding agent for DeepSeek and supported
|
|
@@ -113,6 +117,8 @@ for direct local commands.
|
|
|
113
117
|
|
|
114
118
|
## Quick Start
|
|
115
119
|
|
|
120
|
+
For the guided installation page, open **https://deepy.kirineko.tech/**.
|
|
121
|
+
|
|
116
122
|
1. Install `uv`:
|
|
117
123
|
|
|
118
124
|
```bash
|
|
@@ -300,7 +306,7 @@ reserved_context_tokens = 50000
|
|
|
300
306
|
compact_preserve_recent_messages = 2
|
|
301
307
|
|
|
302
308
|
[ui]
|
|
303
|
-
theme = "
|
|
309
|
+
theme = "dark" # dark or light
|
|
304
310
|
```
|
|
305
311
|
|
|
306
312
|
Set config without the interactive wizard:
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
|
-
<a href="https://kirineko.
|
|
14
|
+
<a href="https://deepy.kirineko.tech/"><strong>Install Website</strong></a>
|
|
15
|
+
·
|
|
16
|
+
<a href="https://kirineko.github.io/deepy/">GitHub Pages</a>
|
|
15
17
|
·
|
|
16
18
|
<a href="README.zh-CN.md">中文文档</a>
|
|
17
19
|
·
|
|
@@ -22,6 +24,8 @@
|
|
|
22
24
|
|
|
23
25
|

|
|
24
26
|
|
|
27
|
+
> Install and setup guide: **https://deepy.kirineko.tech/**
|
|
28
|
+
|
|
25
29
|
## What Deepy Does
|
|
26
30
|
|
|
27
31
|
Deepy is a Python CLI coding agent for DeepSeek and supported
|
|
@@ -81,6 +85,8 @@ for direct local commands.
|
|
|
81
85
|
|
|
82
86
|
## Quick Start
|
|
83
87
|
|
|
88
|
+
For the guided installation page, open **https://deepy.kirineko.tech/**.
|
|
89
|
+
|
|
84
90
|
1. Install `uv`:
|
|
85
91
|
|
|
86
92
|
```bash
|
|
@@ -268,7 +274,7 @@ reserved_context_tokens = 50000
|
|
|
268
274
|
compact_preserve_recent_messages = 2
|
|
269
275
|
|
|
270
276
|
[ui]
|
|
271
|
-
theme = "
|
|
277
|
+
theme = "dark" # dark or light
|
|
272
278
|
```
|
|
273
279
|
|
|
274
280
|
Set config without the interactive wizard:
|
|
@@ -61,13 +61,13 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
61
61
|
init_parser.add_argument("--model", help="Model name.")
|
|
62
62
|
init_parser.add_argument("--base-url", help="OpenAI-compatible base URL.")
|
|
63
63
|
init_parser.add_argument("--thinking", help="Thinking mode for the provider.")
|
|
64
|
-
init_parser.add_argument("--theme", default=DEFAULT_UI_THEME, help="UI theme:
|
|
64
|
+
init_parser.add_argument("--theme", default=DEFAULT_UI_THEME, help="UI theme: dark or light.")
|
|
65
65
|
init_parser.add_argument("--force", action="store_true", help="Overwrite existing config.")
|
|
66
66
|
setup_parser = config_sub.add_parser("setup", help="Interactively configure Deepy.")
|
|
67
67
|
setup_parser.add_argument("--force", action="store_true", help="Overwrite existing config.")
|
|
68
68
|
config_sub.add_parser("reset", help="Delete local config and run interactive setup again.")
|
|
69
69
|
theme_parser = config_sub.add_parser("theme", help="Show or update terminal UI theme.")
|
|
70
|
-
theme_parser.add_argument("theme", nargs="?", help="Theme to save:
|
|
70
|
+
theme_parser.add_argument("theme", nargs="?", help="Theme to save: dark or light.")
|
|
71
71
|
|
|
72
72
|
doctor_parser = subparsers.add_parser("doctor", help="Validate local Deepy setup.")
|
|
73
73
|
doctor_parser.add_argument("--json", action="store_true", help="Print JSON diagnostics.")
|
|
@@ -381,9 +381,8 @@ def _thinking_mode_from_selection(provider: str, value: str, *, default: str) ->
|
|
|
381
381
|
|
|
382
382
|
def _prompt_theme_value(*, default: str = DEFAULT_UI_THEME) -> str:
|
|
383
383
|
print("UI theme:")
|
|
384
|
-
print("1.
|
|
385
|
-
print("2.
|
|
386
|
-
print("3. light Optimized for light terminal backgrounds")
|
|
384
|
+
print("1. dark Optimized for dark terminal backgrounds")
|
|
385
|
+
print("2. light Optimized for light terminal backgrounds")
|
|
387
386
|
value = _prompt_config_value("UI theme number", default=ui_theme_number(default))
|
|
388
387
|
return ui_theme_from_selection(value, default=default)
|
|
389
388
|
|
|
@@ -417,7 +416,7 @@ def _cmd_config_theme(args: argparse.Namespace) -> int:
|
|
|
417
416
|
print(f"resolved: {palette.name}")
|
|
418
417
|
return 0
|
|
419
418
|
if args.theme not in UI_THEMES:
|
|
420
|
-
print("Invalid theme. Usage: deepy config theme [
|
|
419
|
+
print("Invalid theme. Usage: deepy config theme [dark|light]", file=sys.stderr)
|
|
421
420
|
return 1
|
|
422
421
|
config_path = settings.path or (args.config.expanduser() if args.config else Path.home() / ".deepy" / "config.toml")
|
|
423
422
|
update_config_theme(config_path, args.theme)
|
|
@@ -16,7 +16,7 @@ DEFAULT_COMPACT_TRIGGER_RATIO = 0.8
|
|
|
16
16
|
DEFAULT_RESERVED_CONTEXT_TOKENS = 50_000
|
|
17
17
|
DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES = 2
|
|
18
18
|
DEFAULT_WEB_SEARCH_SEARXNG_URL = "https://s.kirineko.tech/"
|
|
19
|
-
DEFAULT_UI_THEME = "
|
|
19
|
+
DEFAULT_UI_THEME = "dark"
|
|
20
20
|
DEFAULT_MCP_ENABLED = True
|
|
21
21
|
DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS = 10.0
|
|
22
22
|
DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS = 10.0
|
|
@@ -45,8 +45,8 @@ SWITCH_ONLY_THINKING_MODES = ("disabled", "enabled")
|
|
|
45
45
|
REASONING_MODES = set(DEEPSEEK_REASONING_MODES)
|
|
46
46
|
THINKING_MODES = set(DEEPSEEK_REASONING_MODES) | set(SWITCH_ONLY_THINKING_MODES) | OPENROUTER_REASONING_EFFORTS
|
|
47
47
|
PROVIDERS = {"deepseek", "openrouter", "xiaomi"}
|
|
48
|
-
UI_THEMES = {"
|
|
49
|
-
UI_THEME_OPTIONS = (("1", "
|
|
48
|
+
UI_THEMES = {"dark", "light"}
|
|
49
|
+
UI_THEME_OPTIONS = (("1", "dark"), ("2", "light"))
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
@dataclass(frozen=True)
|
|
@@ -570,6 +570,12 @@ class UiConfig:
|
|
|
570
570
|
raw.get("input_suggestions_enabled"),
|
|
571
571
|
DEFAULT_INPUT_SUGGESTIONS_ENABLED,
|
|
572
572
|
)
|
|
573
|
+
if isinstance(theme, str) and theme.strip() == "auto":
|
|
574
|
+
return cls(
|
|
575
|
+
theme=DEFAULT_UI_THEME,
|
|
576
|
+
theme_configured=True,
|
|
577
|
+
input_suggestions_enabled=input_suggestions_enabled,
|
|
578
|
+
)
|
|
573
579
|
if isinstance(theme, str) and theme.strip() in UI_THEMES:
|
|
574
580
|
return cls(
|
|
575
581
|
theme=theme.strip(),
|
|
@@ -690,7 +696,7 @@ def write_config(
|
|
|
690
696
|
thinking_mode: str | None = None,
|
|
691
697
|
) -> None:
|
|
692
698
|
if not is_valid_ui_theme(theme):
|
|
693
|
-
raise ValueError("UI theme must be one of:
|
|
699
|
+
raise ValueError("UI theme must be one of: dark, light.")
|
|
694
700
|
if not is_supported_provider(provider):
|
|
695
701
|
raise ValueError("Provider must be one of: deepseek, openrouter, xiaomi.")
|
|
696
702
|
provider_info = provider_info_for(provider)
|
|
@@ -816,7 +822,7 @@ def update_config_model_settings(
|
|
|
816
822
|
|
|
817
823
|
def update_config_theme(config_path: Path, theme: str) -> None:
|
|
818
824
|
if not is_valid_ui_theme(theme):
|
|
819
|
-
raise ValueError("UI theme must be one of:
|
|
825
|
+
raise ValueError("UI theme must be one of: dark, light.")
|
|
820
826
|
path = config_path.expanduser()
|
|
821
827
|
if path.suffix == ".json":
|
|
822
828
|
raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
|
|
@@ -143,7 +143,6 @@ class DeepyJsonlSession:
|
|
|
143
143
|
state = self.context_token_state(records)
|
|
144
144
|
self._touch_index(
|
|
145
145
|
active_tokens=state.active_tokens,
|
|
146
|
-
latest_context_window_tokens=state.active_tokens,
|
|
147
146
|
last_usage_tokens=state.last_usage_tokens,
|
|
148
147
|
pending_tokens=state.pending_tokens,
|
|
149
148
|
last_usage_record_count=state.last_usage_record_count,
|
|
@@ -823,7 +823,6 @@ class DeepyTuiApp(App[None]):
|
|
|
823
823
|
ChoiceScreen(
|
|
824
824
|
"Select theme",
|
|
825
825
|
[
|
|
826
|
-
Choice("auto", "auto", "Follow saved/default theme behavior"),
|
|
827
826
|
Choice("dark", "dark", "Use Textual dark theme"),
|
|
828
827
|
Choice("light", "light", "Use Textual light theme"),
|
|
829
828
|
],
|
|
@@ -833,7 +832,7 @@ class DeepyTuiApp(App[None]):
|
|
|
833
832
|
self._update_status("Theme unchanged")
|
|
834
833
|
return
|
|
835
834
|
if not is_valid_ui_theme(theme):
|
|
836
|
-
await self._append_block(ErrorBlock("Usage: /theme
|
|
835
|
+
await self._append_block(ErrorBlock("Usage: /theme dark|light"))
|
|
837
836
|
return
|
|
838
837
|
if self.settings.path is None:
|
|
839
838
|
await self._append_block(ErrorBlock("Cannot persist theme: config path is unknown."))
|
|
@@ -2040,7 +2039,7 @@ def _reset_config_validation_error(result: ResetConfigResult) -> str:
|
|
|
2040
2039
|
if not result.theme:
|
|
2041
2040
|
return "Theme is required."
|
|
2042
2041
|
if not is_valid_ui_theme(result.theme):
|
|
2043
|
-
return "Usage: theme must be
|
|
2042
|
+
return "Usage: theme must be dark|light"
|
|
2044
2043
|
return ""
|
|
2045
2044
|
|
|
2046
2045
|
|
|
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
|
|
7
7
|
from textual.command import DiscoveryHit, Hit, Hits, Provider
|
|
8
8
|
|
|
9
|
+
from deepy.ui.slash_commands import slash_command_priority
|
|
10
|
+
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from deepy.tui.app import DeepyTuiApp
|
|
11
13
|
|
|
@@ -59,24 +61,34 @@ def command_by_name(name: str) -> TuiCommand | None:
|
|
|
59
61
|
return next((command for command in TUI_COMMANDS if command.name == name), None)
|
|
60
62
|
|
|
61
63
|
|
|
64
|
+
def ranked_tui_commands() -> list[TuiCommand]:
|
|
65
|
+
return sorted(TUI_COMMANDS, key=lambda command: (slash_command_priority(command.name), command.name))
|
|
66
|
+
|
|
67
|
+
|
|
62
68
|
class DeepyCommandProvider(Provider):
|
|
63
69
|
async def search(self, query: str) -> Hits:
|
|
64
70
|
matcher = self.matcher(query)
|
|
65
71
|
app = self.app
|
|
66
|
-
|
|
72
|
+
matches = []
|
|
73
|
+
for command in ranked_tui_commands():
|
|
67
74
|
candidate = f"{command.label} {command.description} {command.group}"
|
|
68
75
|
score = matcher.match(candidate)
|
|
69
76
|
if score > 0:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
matches.append((score, command))
|
|
78
|
+
for score, command in sorted(
|
|
79
|
+
matches,
|
|
80
|
+
key=lambda item: (-item[0], slash_command_priority(item[1].name), item[1].name),
|
|
81
|
+
):
|
|
82
|
+
yield Hit(
|
|
83
|
+
score,
|
|
84
|
+
matcher.highlight(command.label),
|
|
85
|
+
partial(app.invoke_tui_command, command.name),
|
|
86
|
+
help=f"{command.group}: {command.description}",
|
|
87
|
+
)
|
|
76
88
|
|
|
77
89
|
async def discover(self) -> Hits:
|
|
78
90
|
app = self.app
|
|
79
|
-
for command in
|
|
91
|
+
for command in ranked_tui_commands():
|
|
80
92
|
yield DiscoveryHit(
|
|
81
93
|
command.label,
|
|
82
94
|
partial(app.invoke_tui_command, command.name),
|
|
@@ -191,7 +191,7 @@ class ResetConfigScreen(ModalScreen[ResetConfigResult | None]):
|
|
|
191
191
|
yield Input(value=self.model, placeholder="Model", id="reset-model")
|
|
192
192
|
yield Input(value=self.base_url, placeholder="Base URL", id="reset-base-url")
|
|
193
193
|
yield Input(value=self.thinking, placeholder="Thinking", id="reset-thinking")
|
|
194
|
-
yield Input(value=self.theme, placeholder="Theme:
|
|
194
|
+
yield Input(value=self.theme, placeholder="Theme: dark|light", id="reset-theme")
|
|
195
195
|
yield Footer()
|
|
196
196
|
|
|
197
197
|
def on_mount(self) -> None:
|
|
@@ -35,7 +35,7 @@ from deepy.ui.message_view import (
|
|
|
35
35
|
from deepy.ui.slash_commands import (
|
|
36
36
|
SlashCommandItem,
|
|
37
37
|
filter_slash_commands,
|
|
38
|
-
|
|
38
|
+
format_slash_command_completion_label,
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
|
|
@@ -244,7 +244,7 @@ class PromptPanel(Vertical):
|
|
|
244
244
|
self._refresh_input_suggestion_display()
|
|
245
245
|
return
|
|
246
246
|
option_list.display = True
|
|
247
|
-
option_list.add_options([Option(suggestion, id=suggestion) for suggestion in suggestions
|
|
247
|
+
option_list.add_options([Option(suggestion, id=suggestion) for suggestion in suggestions])
|
|
248
248
|
option_list.highlighted = 0
|
|
249
249
|
self._refresh_input_suggestion_display()
|
|
250
250
|
|
|
@@ -308,10 +308,14 @@ class PromptPanel(Vertical):
|
|
|
308
308
|
and token == text
|
|
309
309
|
and not any(char.isspace() for char in token)
|
|
310
310
|
):
|
|
311
|
-
if any(
|
|
311
|
+
if any(
|
|
312
|
+
item.label == token
|
|
313
|
+
or (item.kind == "skill" and f"/skill:{item.name}" == token)
|
|
314
|
+
for item in self.slash_commands
|
|
315
|
+
):
|
|
312
316
|
return []
|
|
313
317
|
return [
|
|
314
|
-
f"{
|
|
318
|
+
f"{format_slash_command_completion_label(item, token)} {item.description}"
|
|
315
319
|
for item in filter_slash_commands(self.slash_commands, token)
|
|
316
320
|
]
|
|
317
321
|
mention = extract_file_mention_fragment(text)
|
|
@@ -7,7 +7,7 @@ from unicodedata import normalize
|
|
|
7
7
|
|
|
8
8
|
from prompt_toolkit import PromptSession
|
|
9
9
|
from prompt_toolkit.auto_suggest import AutoSuggest, Suggestion
|
|
10
|
-
from prompt_toolkit.completion import Completer, CompleteEvent,
|
|
10
|
+
from prompt_toolkit.completion import Completer, CompleteEvent, Completion, merge_completers
|
|
11
11
|
from prompt_toolkit.document import Document
|
|
12
12
|
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
|
|
13
13
|
from prompt_toolkit.history import FileHistory
|
|
@@ -18,7 +18,13 @@ from deepy.input_suggestions import InputSuggestionController
|
|
|
18
18
|
from deepy.skills import SkillInfo
|
|
19
19
|
from deepy.ui.file_mentions import FileMentionCompleter
|
|
20
20
|
from deepy.ui.prompt_buffer import PromptBufferState
|
|
21
|
-
from deepy.ui.slash_commands import
|
|
21
|
+
from deepy.ui.slash_commands import (
|
|
22
|
+
SlashCommandItem,
|
|
23
|
+
find_exact_slash_command,
|
|
24
|
+
format_slash_command_completion_label,
|
|
25
|
+
format_slash_command_description,
|
|
26
|
+
rank_slash_commands,
|
|
27
|
+
)
|
|
22
28
|
from deepy.ui.status_footer import StatusFooter
|
|
23
29
|
from deepy.ui.styles import DARK_PALETTE, UiPalette
|
|
24
30
|
|
|
@@ -52,11 +58,10 @@ def create_prompt_session(
|
|
|
52
58
|
path = history_path or DEFAULT_PROMPT_HISTORY
|
|
53
59
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
60
|
path.touch(exist_ok=True)
|
|
55
|
-
labels = [item.label for item in slash_commands or []]
|
|
56
61
|
root = project_root or Path.cwd()
|
|
57
62
|
completer = merge_completers(
|
|
58
63
|
[
|
|
59
|
-
|
|
64
|
+
SlashCommandCompleter(slash_commands or []),
|
|
60
65
|
FileMentionCompleter(root),
|
|
61
66
|
],
|
|
62
67
|
deduplicate=True,
|
|
@@ -109,6 +114,38 @@ class InputSuggestionAwareCompleter(Completer):
|
|
|
109
114
|
yield from self.completer.get_completions(document, complete_event)
|
|
110
115
|
|
|
111
116
|
|
|
117
|
+
class SlashCommandCompleter(Completer):
|
|
118
|
+
def __init__(self, slash_commands: list[SlashCommandItem]) -> None:
|
|
119
|
+
self.slash_commands = slash_commands
|
|
120
|
+
|
|
121
|
+
def get_completions(self, document: Document, complete_event: CompleteEvent):
|
|
122
|
+
del complete_event
|
|
123
|
+
token = _slash_token_before_cursor(document)
|
|
124
|
+
if token is None:
|
|
125
|
+
return
|
|
126
|
+
if find_exact_slash_command(self.slash_commands, token) is not None:
|
|
127
|
+
return
|
|
128
|
+
for item in rank_slash_commands(self.slash_commands, token):
|
|
129
|
+
label = format_slash_command_completion_label(item, token)
|
|
130
|
+
yield Completion(
|
|
131
|
+
label.removesuffix(" *"),
|
|
132
|
+
start_position=-len(token),
|
|
133
|
+
display=label,
|
|
134
|
+
display_meta=format_slash_command_description(item.description),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _slash_token_before_cursor(document: Document) -> str | None:
|
|
139
|
+
before = document.text_before_cursor
|
|
140
|
+
start = len(before)
|
|
141
|
+
while start > 0 and not before[start - 1].isspace():
|
|
142
|
+
start -= 1
|
|
143
|
+
token = before[start:]
|
|
144
|
+
if not token.startswith("/") or not token:
|
|
145
|
+
return None
|
|
146
|
+
return token
|
|
147
|
+
|
|
148
|
+
|
|
112
149
|
def build_prompt_key_bindings(
|
|
113
150
|
*,
|
|
114
151
|
on_interrupt: Callable[[], None] | None = None,
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from dataclasses import dataclass, replace
|
|
5
|
+
from enum import IntEnum
|
|
5
6
|
|
|
6
7
|
from deepy.skills import SkillInfo
|
|
7
8
|
from deepy.subagents import built_in_subagents
|
|
@@ -44,6 +45,36 @@ SUBAGENT_SLASH_COMMANDS = tuple(
|
|
|
44
45
|
)
|
|
45
46
|
BUILTIN_SLASH_COMMAND_NAMES = frozenset(item.name for item in BUILTIN_SLASH_COMMANDS)
|
|
46
47
|
SUBAGENT_SLASH_COMMAND_NAMES = frozenset(item.name for item in SUBAGENT_SLASH_COMMANDS)
|
|
48
|
+
COMMON_WORKFLOW_COMMAND_ORDER = {
|
|
49
|
+
"help": 0,
|
|
50
|
+
"new": 1,
|
|
51
|
+
"resume": 2,
|
|
52
|
+
"model": 3,
|
|
53
|
+
"skills": 4,
|
|
54
|
+
"status": 5,
|
|
55
|
+
"compact": 6,
|
|
56
|
+
"mcp": 7,
|
|
57
|
+
"exit": 8,
|
|
58
|
+
}
|
|
59
|
+
LOW_FREQUENCY_COMMAND_ORDER = {
|
|
60
|
+
"init": 0,
|
|
61
|
+
"theme": 1,
|
|
62
|
+
"input-suggestion": 2,
|
|
63
|
+
"ps": 3,
|
|
64
|
+
"stop": 4,
|
|
65
|
+
"reset": 5,
|
|
66
|
+
}
|
|
67
|
+
SKILL_SCOPE_PRIORITY = {
|
|
68
|
+
"project": 0,
|
|
69
|
+
"user": 1,
|
|
70
|
+
"builtin": 2,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SlashCommandMatch(IntEnum):
|
|
75
|
+
EXACT = 0
|
|
76
|
+
PREFIX = 1
|
|
77
|
+
WEAK = 2
|
|
47
78
|
|
|
48
79
|
|
|
49
80
|
def build_slash_commands(
|
|
@@ -65,16 +96,79 @@ def build_slash_commands(
|
|
|
65
96
|
|
|
66
97
|
|
|
67
98
|
def filter_slash_commands(items: list[SlashCommandItem], token: str) -> list[SlashCommandItem]:
|
|
99
|
+
return rank_slash_commands(items, token)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def rank_slash_commands(items: list[SlashCommandItem], token: str) -> list[SlashCommandItem]:
|
|
68
103
|
if not token.startswith("/"):
|
|
69
104
|
return []
|
|
70
105
|
query = token[1:].lower()
|
|
71
106
|
if not query:
|
|
72
|
-
return items
|
|
73
|
-
|
|
74
|
-
item
|
|
107
|
+
return sorted(items, key=slash_command_sort_key)
|
|
108
|
+
scored = [
|
|
109
|
+
(match, slash_command_sort_key(item), item)
|
|
75
110
|
for item in items
|
|
76
|
-
if
|
|
111
|
+
if (match := slash_command_match(item, query)) is not None
|
|
77
112
|
]
|
|
113
|
+
scored.sort(key=lambda scored_item: (scored_item[0], scored_item[1]))
|
|
114
|
+
return [item for _match, _sort_key, item in scored]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def slash_command_match(item: SlashCommandItem, query: str) -> SlashCommandMatch | None:
|
|
118
|
+
normalized = query.lower()
|
|
119
|
+
name = item.name.lower()
|
|
120
|
+
label = item.label[1:].lower()
|
|
121
|
+
description = item.description.lower()
|
|
122
|
+
legacy_skill_name = f"skill:{name}" if item.kind == "skill" else ""
|
|
123
|
+
values = [name, label]
|
|
124
|
+
if legacy_skill_name:
|
|
125
|
+
values.append(legacy_skill_name)
|
|
126
|
+
if any(value == normalized for value in values):
|
|
127
|
+
return SlashCommandMatch.EXACT
|
|
128
|
+
if any(value.startswith(normalized) for value in values):
|
|
129
|
+
return SlashCommandMatch.PREFIX
|
|
130
|
+
if normalized in name or normalized in label or normalized in description:
|
|
131
|
+
return SlashCommandMatch.WEAK
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def slash_command_sort_key(item: SlashCommandItem) -> tuple[int, int, int, str]:
|
|
136
|
+
return (
|
|
137
|
+
slash_command_priority(item),
|
|
138
|
+
slash_command_loaded_priority(item),
|
|
139
|
+
slash_command_scope_priority(item),
|
|
140
|
+
item.name.lower(),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def slash_command_priority(item: SlashCommandItem | str) -> int:
|
|
145
|
+
if isinstance(item, str):
|
|
146
|
+
name = item
|
|
147
|
+
kind = "builtin"
|
|
148
|
+
else:
|
|
149
|
+
name = item.name
|
|
150
|
+
kind = item.kind
|
|
151
|
+
if name in COMMON_WORKFLOW_COMMAND_ORDER:
|
|
152
|
+
return COMMON_WORKFLOW_COMMAND_ORDER[name]
|
|
153
|
+
if kind == "subagent":
|
|
154
|
+
return 100
|
|
155
|
+
if kind == "skill":
|
|
156
|
+
return 200
|
|
157
|
+
if name in LOW_FREQUENCY_COMMAND_ORDER:
|
|
158
|
+
return 300 + LOW_FREQUENCY_COMMAND_ORDER[name]
|
|
159
|
+
return 250
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def slash_command_loaded_priority(item: SlashCommandItem) -> int:
|
|
163
|
+
if item.kind == "skill" and item.skill and item.skill.is_loaded:
|
|
164
|
+
return 0
|
|
165
|
+
return 1
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def slash_command_scope_priority(item: SlashCommandItem) -> int:
|
|
169
|
+
if item.kind != "skill" or item.skill is None:
|
|
170
|
+
return 0
|
|
171
|
+
return SKILL_SCOPE_PRIORITY.get(item.skill.scope, 3)
|
|
78
172
|
|
|
79
173
|
|
|
80
174
|
def find_exact_slash_command(
|
|
@@ -102,6 +196,14 @@ def format_slash_command_label(item: SlashCommandItem) -> str:
|
|
|
102
196
|
return f"{item.label} *" if item.kind == "skill" and loaded else item.label
|
|
103
197
|
|
|
104
198
|
|
|
199
|
+
def format_slash_command_completion_label(item: SlashCommandItem, token: str = "") -> str:
|
|
200
|
+
label = item.label
|
|
201
|
+
if item.kind == "skill" and token[1:].lower().startswith("skill:"):
|
|
202
|
+
label = f"/skill:{item.name}"
|
|
203
|
+
loaded = bool(item.skill and item.skill.is_loaded)
|
|
204
|
+
return f"{label} *" if item.kind == "skill" and loaded else label
|
|
205
|
+
|
|
206
|
+
|
|
105
207
|
def is_builtin_slash_command(name: str) -> bool:
|
|
106
208
|
return name in BUILTIN_SLASH_COMMAND_NAMES
|
|
107
209
|
|
|
@@ -83,7 +83,7 @@ def _help_parts(help_text: str) -> list[tuple[FooterPartRole, str]]:
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
def _known_title(text: str) -> str | None:
|
|
86
|
-
for title in ("provider", "model", "cwd", "mcp", "bg", "ctx", "newline"):
|
|
86
|
+
for title in ("provider", "model", "cwd", "mcp", "update", "bg", "ctx", "newline"):
|
|
87
87
|
if text == title or text.startswith(f"{title} ") or text.startswith(f"{title}:"):
|
|
88
88
|
return title
|
|
89
89
|
return None
|