deepy-cli 0.2.18__tar.gz → 0.2.19__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.19}/PKG-INFO +8 -2
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/README.md +7 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/pyproject.toml +1 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/sessions/jsonl.py +0 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/commands.py +20 -8
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/widgets.py +8 -4
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/prompt_input.py +41 -4
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/slash_commands.py +106 -4
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/terminal.py +98 -1
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/background_tasks.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/cli.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/config/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/config/settings.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/Search.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/apply_patch.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/edit_text.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/read_file.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/task_list.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/task_output.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/task_stop.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/test_shell.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/todo_write.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/data/tools/write_file.md +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/input_suggestions.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/agent.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/provider.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/runner.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/mcp.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/prompts/system.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/prompts/tool_docs.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/session_cost.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/skills.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/status.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/subagents.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/todos.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/agents.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/builtin.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/search.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tools/test_shell.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/app.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/compat.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/diff.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/runner.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/screens.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/tui/state.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/types/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/types/sdk.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/types/tool_payloads.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/exit_summary.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/local_command.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/message_view.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/model_picker.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/skill_picker.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/status_footer.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/theme_picker.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/ui/welcome.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.2.18 → deepy_cli-0.2.19}/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.19
|
|
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
|
|
@@ -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
|
|
@@ -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,
|
|
@@ -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),
|
|
@@ -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
|
|
|
@@ -2819,6 +2819,13 @@ class _SilentStatus:
|
|
|
2819
2819
|
return None
|
|
2820
2820
|
|
|
2821
2821
|
|
|
2822
|
+
@dataclass(frozen=True)
|
|
2823
|
+
class _RuntimeStatusSegments:
|
|
2824
|
+
prefix: str
|
|
2825
|
+
label: str = ""
|
|
2826
|
+
payload: str = ""
|
|
2827
|
+
|
|
2828
|
+
|
|
2822
2829
|
def _terminal_runtime_status_style(palette: UiPalette) -> str:
|
|
2823
2830
|
foreground = _hex_color(palette.toolbar_background) or "#161821"
|
|
2824
2831
|
background = _hex_color(palette.warning) or "#facc15"
|
|
@@ -2851,6 +2858,18 @@ def _ansi_rgb(prefix: str, color: str) -> str:
|
|
|
2851
2858
|
return f"{prefix};2;{red};{green};{blue}"
|
|
2852
2859
|
|
|
2853
2860
|
|
|
2861
|
+
_ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
|
|
2862
|
+
_CONTROL_CHAR_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]")
|
|
2863
|
+
_STATUS_SEPARATOR = " · "
|
|
2864
|
+
|
|
2865
|
+
|
|
2866
|
+
def _sanitize_status_line(text: str) -> str:
|
|
2867
|
+
stripped = _ANSI_ESCAPE_RE.sub("", text)
|
|
2868
|
+
stripped = stripped.replace("\r", " ").replace("\n", " ").replace("\t", " ")
|
|
2869
|
+
stripped = _CONTROL_CHAR_RE.sub("", stripped)
|
|
2870
|
+
return re.sub(r" {2,}", " ", stripped).strip()
|
|
2871
|
+
|
|
2872
|
+
|
|
2854
2873
|
def _truncate_status_line(text: str, *, max_width: int) -> str:
|
|
2855
2874
|
if cell_len(text) <= max_width:
|
|
2856
2875
|
return text
|
|
@@ -2870,10 +2889,88 @@ def _truncate_status_line(text: str, *, max_width: int) -> str:
|
|
|
2870
2889
|
|
|
2871
2890
|
|
|
2872
2891
|
def _fit_status_line(text: str, *, width: int) -> str:
|
|
2873
|
-
|
|
2892
|
+
width = max(width, 0)
|
|
2893
|
+
sanitized = _sanitize_status_line(text)
|
|
2894
|
+
segments = _parse_runtime_status_segments(sanitized)
|
|
2895
|
+
line = (
|
|
2896
|
+
_fit_runtime_status_segments(segments, width=width)
|
|
2897
|
+
if segments is not None
|
|
2898
|
+
else _truncate_status_line(sanitized, max_width=width)
|
|
2899
|
+
)
|
|
2874
2900
|
return line + (" " * max(0, width - cell_len(line)))
|
|
2875
2901
|
|
|
2876
2902
|
|
|
2903
|
+
def _parse_runtime_status_segments(text: str) -> _RuntimeStatusSegments | None:
|
|
2904
|
+
interrupt = "esc to interrupt"
|
|
2905
|
+
interrupt_index = text.find(interrupt)
|
|
2906
|
+
if interrupt_index < 0:
|
|
2907
|
+
return None
|
|
2908
|
+
|
|
2909
|
+
prefix_end = interrupt_index + len(interrupt)
|
|
2910
|
+
prefix = text[:prefix_end].strip()
|
|
2911
|
+
detail = text[prefix_end:]
|
|
2912
|
+
if detail.startswith(_STATUS_SEPARATOR):
|
|
2913
|
+
detail = detail[len(_STATUS_SEPARATOR) :].strip()
|
|
2914
|
+
else:
|
|
2915
|
+
detail = detail.strip()
|
|
2916
|
+
if not detail:
|
|
2917
|
+
return _RuntimeStatusSegments(prefix=prefix)
|
|
2918
|
+
|
|
2919
|
+
if detail.startswith(f"local command{_STATUS_SEPARATOR}"):
|
|
2920
|
+
payload = detail.removeprefix(f"local command{_STATUS_SEPARATOR}").strip()
|
|
2921
|
+
return _RuntimeStatusSegments(prefix=prefix, label="local command", payload=payload)
|
|
2922
|
+
|
|
2923
|
+
tool_match = re.match(r"(tool \[[^\]]+\])(?:\s+(.*))?$", detail)
|
|
2924
|
+
if tool_match:
|
|
2925
|
+
label, payload = tool_match.groups()
|
|
2926
|
+
return _RuntimeStatusSegments(prefix=prefix, label=label, payload=(payload or "").strip())
|
|
2927
|
+
|
|
2928
|
+
return _RuntimeStatusSegments(prefix=prefix, label=detail)
|
|
2929
|
+
|
|
2930
|
+
|
|
2931
|
+
def _fit_runtime_status_segments(segments: _RuntimeStatusSegments, *, width: int) -> str:
|
|
2932
|
+
if width <= 0:
|
|
2933
|
+
return ""
|
|
2934
|
+
|
|
2935
|
+
full = _runtime_status_segments_text(segments)
|
|
2936
|
+
if cell_len(full) <= width:
|
|
2937
|
+
return full
|
|
2938
|
+
|
|
2939
|
+
if cell_len(segments.prefix) >= width:
|
|
2940
|
+
return _truncate_status_line(segments.prefix, max_width=width)
|
|
2941
|
+
|
|
2942
|
+
if not segments.label:
|
|
2943
|
+
return _truncate_status_line(segments.prefix, max_width=width)
|
|
2944
|
+
|
|
2945
|
+
prefix_label = f"{segments.prefix}{_STATUS_SEPARATOR}{segments.label}"
|
|
2946
|
+
if segments.payload:
|
|
2947
|
+
base = f"{prefix_label}{_STATUS_SEPARATOR}"
|
|
2948
|
+
payload_width = width - cell_len(base)
|
|
2949
|
+
if payload_width > 0:
|
|
2950
|
+
payload = _truncate_status_line(segments.payload, max_width=payload_width)
|
|
2951
|
+
return f"{base}{payload}".rstrip()
|
|
2952
|
+
|
|
2953
|
+
if cell_len(prefix_label) <= width:
|
|
2954
|
+
return prefix_label
|
|
2955
|
+
|
|
2956
|
+
label_base = f"{segments.prefix}{_STATUS_SEPARATOR}"
|
|
2957
|
+
label_width = width - cell_len(label_base)
|
|
2958
|
+
if label_width > 0:
|
|
2959
|
+
label = _truncate_status_line(segments.label, max_width=label_width)
|
|
2960
|
+
return f"{label_base}{label}".rstrip()
|
|
2961
|
+
|
|
2962
|
+
return _truncate_status_line(segments.prefix, max_width=width)
|
|
2963
|
+
|
|
2964
|
+
|
|
2965
|
+
def _runtime_status_segments_text(segments: _RuntimeStatusSegments) -> str:
|
|
2966
|
+
parts = [segments.prefix]
|
|
2967
|
+
if segments.label:
|
|
2968
|
+
parts.append(segments.label)
|
|
2969
|
+
if segments.payload:
|
|
2970
|
+
parts.append(segments.payload)
|
|
2971
|
+
return _STATUS_SEPARATOR.join(parts)
|
|
2972
|
+
|
|
2973
|
+
|
|
2877
2974
|
def _working_status_text(
|
|
2878
2975
|
started_at: float,
|
|
2879
2976
|
detail: str = "",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|