deepy-cli 0.2.12__tar.gz → 0.2.14__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.12 → deepy_cli-0.2.14}/PKG-INFO +28 -9
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/README.md +25 -8
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/pyproject.toml +3 -1
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/cli.py +259 -29
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/config/__init__.py +44 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/config/settings.py +329 -29
- deepy_cli-0.2.14/src/deepy/data/tools/Search.md +20 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/input_suggestions.py +14 -5
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/provider.py +1 -1
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/thinking.py +13 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/prompts/system.py +2 -1
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/prompts/tool_docs.py +1 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/session_cost.py +8 -2
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/status.py +6 -2
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tools/agents.py +154 -14
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tools/builtin.py +41 -0
- deepy_cli-0.2.14/src/deepy/tools/search.py +643 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/app.py +137 -67
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/screens.py +41 -8
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/widgets.py +91 -12
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/exit_summary.py +11 -2
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/message_view.py +49 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/model_picker.py +91 -12
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/status_footer.py +1 -1
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/terminal.py +299 -43
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/welcome.py +9 -2
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/apply_patch.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/edit_text.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/read_file.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/shell.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/todo_write.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/data/tools/write_file.md +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/errors.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/agent.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/compaction.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/llm/runner.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/mcp.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/prompts/init_agents.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/sessions/jsonl.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/skill_market.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/skills.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/todos.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tools/shell_output.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/commands.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/compat.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/diff.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/runner.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/tui/state.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/types/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/types/sdk.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/types/tool_payloads.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/file_mentions.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/local_command.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/markdown.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/prompt_input.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/skill_picker.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/slash_commands.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/styles.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/theme_picker.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/usage.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.2.12 → deepy_cli-0.2.14}/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.14
|
|
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
|
|
@@ -15,8 +15,10 @@ Classifier: Topic :: Terminals
|
|
|
15
15
|
Requires-Dist: openai-agents>=0.17.0
|
|
16
16
|
Requires-Dist: openai>=2.26,<3
|
|
17
17
|
Requires-Dist: orjson>=3.10,<4
|
|
18
|
+
Requires-Dist: pathspec>=1.1.1
|
|
18
19
|
Requires-Dist: pydantic>=2.12,<3
|
|
19
20
|
Requires-Dist: prompt-toolkit>=3.0,<4
|
|
21
|
+
Requires-Dist: regex>=2026.5.9
|
|
20
22
|
Requires-Dist: pyyaml>=6.0,<7
|
|
21
23
|
Requires-Dist: rich>=14.2,<15
|
|
22
24
|
Requires-Dist: textual>=8.2,<9
|
|
@@ -35,7 +37,7 @@ Description-Content-Type: text/markdown
|
|
|
35
37
|
<h1 align="center">Deepy</h1>
|
|
36
38
|
|
|
37
39
|
<p align="center">
|
|
38
|
-
A terminal coding agent
|
|
40
|
+
A terminal coding agent for DeepSeek and OpenAI-compatible providers.
|
|
39
41
|
<br>
|
|
40
42
|
Read projects, edit files, run commands, search the web, and keep long project context in one recoverable terminal session.
|
|
41
43
|
</p>
|
|
@@ -54,7 +56,8 @@ Description-Content-Type: text/markdown
|
|
|
54
56
|
|
|
55
57
|
## What Deepy Does
|
|
56
58
|
|
|
57
|
-
Deepy is a Python CLI coding agent for DeepSeek
|
|
59
|
+
Deepy is a Python CLI coding agent for DeepSeek and supported
|
|
60
|
+
OpenAI-compatible providers. It
|
|
58
61
|
keeps the working loop inside your terminal: inspect a project, ask questions,
|
|
59
62
|
modify code, run validation commands, search or fetch web pages, and resume the
|
|
60
63
|
same project session later.
|
|
@@ -65,9 +68,10 @@ of hiding tool calls behind chat text.
|
|
|
65
68
|
|
|
66
69
|
## Why Use It
|
|
67
70
|
|
|
68
|
-
- **
|
|
69
|
-
and `reasoning_effort=max`. Use `/model` to switch
|
|
70
|
-
|
|
71
|
+
- **Provider-aware model selection**: starts with DeepSeek `deepseek-v4-pro`,
|
|
72
|
+
thinking enabled, and `reasoning_effort=max`. Use `/model` to switch between
|
|
73
|
+
DeepSeek, OpenRouter MiMo, and Xiaomi MiMo models with provider-specific
|
|
74
|
+
thinking choices.
|
|
71
75
|
- **Project-aware coding tools**: read files, write new files, modify existing
|
|
72
76
|
files with stale-write protection, run shell commands, and review readable
|
|
73
77
|
diffs.
|
|
@@ -145,7 +149,7 @@ uv tool install deepy-cli
|
|
|
145
149
|
|
|
146
150
|
The installed command is `deepy`.
|
|
147
151
|
|
|
148
|
-
4. Configure your
|
|
152
|
+
4. Configure your provider API key and start Deepy:
|
|
149
153
|
|
|
150
154
|
```bash
|
|
151
155
|
deepy config setup
|
|
@@ -248,7 +252,7 @@ uv tool uninstall deepy-cli
|
|
|
248
252
|
Inside an interactive Deepy session:
|
|
249
253
|
|
|
250
254
|
```text
|
|
251
|
-
/model Select model and thinking
|
|
255
|
+
/model Select provider, model, and thinking mode
|
|
252
256
|
/status Show usage, context pressure, and DeepSeek balance
|
|
253
257
|
/resume Resume a previous project session
|
|
254
258
|
/new Start a fresh session
|
|
@@ -277,6 +281,7 @@ Deepy uses TOML configuration at `~/.deepy/config.toml`.
|
|
|
277
281
|
```toml
|
|
278
282
|
[model]
|
|
279
283
|
api_key = "sk-..."
|
|
284
|
+
provider = "deepseek"
|
|
280
285
|
name = "deepseek-v4-pro"
|
|
281
286
|
base_url = "https://api.deepseek.com"
|
|
282
287
|
thinking = true
|
|
@@ -295,10 +300,24 @@ theme = "auto" # auto, dark, or light
|
|
|
295
300
|
Set config without the interactive wizard:
|
|
296
301
|
|
|
297
302
|
```bash
|
|
298
|
-
deepy config init --api-key sk-... --model deepseek-v4-pro
|
|
303
|
+
deepy config init --api-key sk-... --provider deepseek --model deepseek-v4-pro
|
|
304
|
+
deepy config init --api-key sk-or-... --provider openrouter --model xiaomi/mimo-v2.5-pro
|
|
305
|
+
deepy config init --api-key sk-or-... --provider openrouter --model anthropic/claude-sonnet-4.5 --thinking minimal
|
|
306
|
+
deepy config init --api-key sk-... --provider xiaomi --model mimo-v2.5-pro
|
|
299
307
|
deepy config theme light
|
|
300
308
|
```
|
|
301
309
|
|
|
310
|
+
Supported provider/model pairs:
|
|
311
|
+
|
|
312
|
+
- `deepseek`: `deepseek-v4-pro`, `deepseek-v4-flash`; thinking modes `none`,
|
|
313
|
+
`high`, `max`.
|
|
314
|
+
- `openrouter`: UI model selection offers `xiaomi/mimo-v2.5-pro`,
|
|
315
|
+
`xiaomi/mimo-v2.5`; setup/init may also use a model id copied from
|
|
316
|
+
OpenRouter. Thinking modes are `enabled`, `disabled`, `xhigh`, `high`,
|
|
317
|
+
`medium`, `low`, `minimal`, `none`.
|
|
318
|
+
- `xiaomi`: `mimo-v2.5-pro`, `mimo-v2.5`; thinking modes `enabled`,
|
|
319
|
+
`disabled`.
|
|
320
|
+
|
|
302
321
|
WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it:
|
|
303
322
|
|
|
304
323
|
```toml
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">Deepy</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
A terminal coding agent
|
|
8
|
+
A terminal coding agent for DeepSeek and OpenAI-compatible providers.
|
|
9
9
|
<br>
|
|
10
10
|
Read projects, edit files, run commands, search the web, and keep long project context in one recoverable terminal session.
|
|
11
11
|
</p>
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
## What Deepy Does
|
|
26
26
|
|
|
27
|
-
Deepy is a Python CLI coding agent for DeepSeek
|
|
27
|
+
Deepy is a Python CLI coding agent for DeepSeek and supported
|
|
28
|
+
OpenAI-compatible providers. It
|
|
28
29
|
keeps the working loop inside your terminal: inspect a project, ask questions,
|
|
29
30
|
modify code, run validation commands, search or fetch web pages, and resume the
|
|
30
31
|
same project session later.
|
|
@@ -35,9 +36,10 @@ of hiding tool calls behind chat text.
|
|
|
35
36
|
|
|
36
37
|
## Why Use It
|
|
37
38
|
|
|
38
|
-
- **
|
|
39
|
-
and `reasoning_effort=max`. Use `/model` to switch
|
|
40
|
-
|
|
39
|
+
- **Provider-aware model selection**: starts with DeepSeek `deepseek-v4-pro`,
|
|
40
|
+
thinking enabled, and `reasoning_effort=max`. Use `/model` to switch between
|
|
41
|
+
DeepSeek, OpenRouter MiMo, and Xiaomi MiMo models with provider-specific
|
|
42
|
+
thinking choices.
|
|
41
43
|
- **Project-aware coding tools**: read files, write new files, modify existing
|
|
42
44
|
files with stale-write protection, run shell commands, and review readable
|
|
43
45
|
diffs.
|
|
@@ -115,7 +117,7 @@ uv tool install deepy-cli
|
|
|
115
117
|
|
|
116
118
|
The installed command is `deepy`.
|
|
117
119
|
|
|
118
|
-
4. Configure your
|
|
120
|
+
4. Configure your provider API key and start Deepy:
|
|
119
121
|
|
|
120
122
|
```bash
|
|
121
123
|
deepy config setup
|
|
@@ -218,7 +220,7 @@ uv tool uninstall deepy-cli
|
|
|
218
220
|
Inside an interactive Deepy session:
|
|
219
221
|
|
|
220
222
|
```text
|
|
221
|
-
/model Select model and thinking
|
|
223
|
+
/model Select provider, model, and thinking mode
|
|
222
224
|
/status Show usage, context pressure, and DeepSeek balance
|
|
223
225
|
/resume Resume a previous project session
|
|
224
226
|
/new Start a fresh session
|
|
@@ -247,6 +249,7 @@ Deepy uses TOML configuration at `~/.deepy/config.toml`.
|
|
|
247
249
|
```toml
|
|
248
250
|
[model]
|
|
249
251
|
api_key = "sk-..."
|
|
252
|
+
provider = "deepseek"
|
|
250
253
|
name = "deepseek-v4-pro"
|
|
251
254
|
base_url = "https://api.deepseek.com"
|
|
252
255
|
thinking = true
|
|
@@ -265,10 +268,24 @@ theme = "auto" # auto, dark, or light
|
|
|
265
268
|
Set config without the interactive wizard:
|
|
266
269
|
|
|
267
270
|
```bash
|
|
268
|
-
deepy config init --api-key sk-... --model deepseek-v4-pro
|
|
271
|
+
deepy config init --api-key sk-... --provider deepseek --model deepseek-v4-pro
|
|
272
|
+
deepy config init --api-key sk-or-... --provider openrouter --model xiaomi/mimo-v2.5-pro
|
|
273
|
+
deepy config init --api-key sk-or-... --provider openrouter --model anthropic/claude-sonnet-4.5 --thinking minimal
|
|
274
|
+
deepy config init --api-key sk-... --provider xiaomi --model mimo-v2.5-pro
|
|
269
275
|
deepy config theme light
|
|
270
276
|
```
|
|
271
277
|
|
|
278
|
+
Supported provider/model pairs:
|
|
279
|
+
|
|
280
|
+
- `deepseek`: `deepseek-v4-pro`, `deepseek-v4-flash`; thinking modes `none`,
|
|
281
|
+
`high`, `max`.
|
|
282
|
+
- `openrouter`: UI model selection offers `xiaomi/mimo-v2.5-pro`,
|
|
283
|
+
`xiaomi/mimo-v2.5`; setup/init may also use a model id copied from
|
|
284
|
+
OpenRouter. Thinking modes are `enabled`, `disabled`, `xhigh`, `high`,
|
|
285
|
+
`medium`, `low`, `minimal`, `none`.
|
|
286
|
+
- `xiaomi`: `mimo-v2.5-pro`, `mimo-v2.5`; thinking modes `enabled`,
|
|
287
|
+
`disabled`.
|
|
288
|
+
|
|
272
289
|
WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it:
|
|
273
290
|
|
|
274
291
|
```toml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deepy-cli"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.14"
|
|
4
4
|
description = "Deepy - Vibe coding for DeepSeek models in your terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -21,8 +21,10 @@ dependencies = [
|
|
|
21
21
|
"openai-agents>=0.17.0",
|
|
22
22
|
"openai>=2.26,<3",
|
|
23
23
|
"orjson>=3.10,<4",
|
|
24
|
+
"pathspec>=1.1.1",
|
|
24
25
|
"pydantic>=2.12,<3",
|
|
25
26
|
"prompt-toolkit>=3.0,<4",
|
|
27
|
+
"regex>=2026.5.9",
|
|
26
28
|
"pyyaml>=6.0,<7",
|
|
27
29
|
"rich>=14.2,<15",
|
|
28
30
|
"textual>=8.2,<9",
|
|
@@ -10,20 +10,24 @@ import tomli_w
|
|
|
10
10
|
|
|
11
11
|
from . import __version__
|
|
12
12
|
from .config import (
|
|
13
|
+
PROVIDER_CATALOG,
|
|
13
14
|
Settings,
|
|
15
|
+
allows_custom_model_for_provider,
|
|
16
|
+
default_base_url_for_provider,
|
|
17
|
+
default_model_for_provider,
|
|
18
|
+
default_thinking_mode_for_provider,
|
|
19
|
+
is_supported_provider,
|
|
20
|
+
is_valid_thinking_mode_for_provider,
|
|
14
21
|
load_settings,
|
|
22
|
+
provider_info_for,
|
|
15
23
|
settings_to_toml_dict,
|
|
24
|
+
thinking_modes_for_provider,
|
|
16
25
|
ui_theme_from_selection,
|
|
17
26
|
ui_theme_number,
|
|
18
27
|
update_config_theme,
|
|
19
28
|
write_config,
|
|
20
29
|
)
|
|
21
|
-
from .config.settings import
|
|
22
|
-
DEFAULT_BASE_URL,
|
|
23
|
-
DEFAULT_MODEL,
|
|
24
|
-
DEFAULT_UI_THEME,
|
|
25
|
-
UI_THEMES,
|
|
26
|
-
)
|
|
30
|
+
from .config.settings import DEFAULT_UI_THEME, UI_THEMES
|
|
27
31
|
from .errors import format_error_display
|
|
28
32
|
from .llm.provider import build_provider_bundle
|
|
29
33
|
from .llm.runner import DEFAULT_MAX_TURNS, run_prompt_once
|
|
@@ -52,9 +56,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
52
56
|
show_parser.add_argument("--show-secret", action="store_true", help="Show API key.")
|
|
53
57
|
show_parser.add_argument("--json", action="store_true", help="Print JSON instead of TOML.")
|
|
54
58
|
init_parser = config_sub.add_parser("init", help="Create a TOML config file.")
|
|
55
|
-
init_parser.add_argument("--api-key", help="
|
|
56
|
-
init_parser.add_argument("--
|
|
57
|
-
init_parser.add_argument("--
|
|
59
|
+
init_parser.add_argument("--api-key", help="Provider API key.")
|
|
60
|
+
init_parser.add_argument("--provider", default="deepseek", help="Provider: deepseek, openrouter, or xiaomi.")
|
|
61
|
+
init_parser.add_argument("--model", help="Model name.")
|
|
62
|
+
init_parser.add_argument("--base-url", help="OpenAI-compatible base URL.")
|
|
63
|
+
init_parser.add_argument("--thinking", help="Thinking mode for the provider.")
|
|
58
64
|
init_parser.add_argument("--theme", default=DEFAULT_UI_THEME, help="UI theme: auto, dark, or light.")
|
|
59
65
|
init_parser.add_argument("--force", action="store_true", help="Overwrite existing config.")
|
|
60
66
|
setup_parser = config_sub.add_parser("setup", help="Interactively configure Deepy.")
|
|
@@ -118,8 +124,10 @@ def _cmd_config_init(args: argparse.Namespace) -> int:
|
|
|
118
124
|
_write_config(
|
|
119
125
|
config_path,
|
|
120
126
|
api_key=args.api_key or "",
|
|
121
|
-
|
|
127
|
+
provider=args.provider,
|
|
128
|
+
model=args.model or default_model_for_provider(args.provider),
|
|
122
129
|
base_url=args.base_url,
|
|
130
|
+
thinking_mode=args.thinking,
|
|
123
131
|
theme=args.theme,
|
|
124
132
|
)
|
|
125
133
|
print(f"Wrote {config_path}")
|
|
@@ -130,33 +138,88 @@ def _cmd_config_setup(args: argparse.Namespace) -> int:
|
|
|
130
138
|
config_path = args.config.expanduser() if args.config else Path.home() / ".deepy" / "config.toml"
|
|
131
139
|
if config_path.suffix == ".json":
|
|
132
140
|
raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
|
|
141
|
+
previous_text = config_path.read_text(encoding="utf-8") if config_path.exists() else None
|
|
142
|
+
try:
|
|
143
|
+
_run_config_setup(config_path)
|
|
144
|
+
except (KeyboardInterrupt, EOFError, StopIteration):
|
|
145
|
+
print(_setup_cancelled_message(previous_text), file=sys.stderr)
|
|
146
|
+
return 1
|
|
147
|
+
print(f"Wrote {config_path}")
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _run_config_setup(config_path: Path) -> None:
|
|
133
152
|
if config_path.exists():
|
|
134
153
|
existing = load_settings(config_path)
|
|
135
154
|
else:
|
|
136
155
|
existing = Settings(path=config_path)
|
|
137
156
|
|
|
138
|
-
|
|
157
|
+
provider = _prompt_provider_value(default=existing.model.provider)
|
|
158
|
+
provider_info = provider_info_for(provider)
|
|
159
|
+
print(f"{provider_info.label} provider selected.")
|
|
160
|
+
if provider_info.api_key_url:
|
|
161
|
+
print(f"Create an API key at {provider_info.api_key_url}")
|
|
139
162
|
api_key = _prompt_config_value("API key", default=existing.model.api_key or "", is_password=True)
|
|
140
|
-
model =
|
|
141
|
-
|
|
163
|
+
model = _prompt_model_value(provider, default=existing.model.name)
|
|
164
|
+
base_default = (
|
|
165
|
+
existing.model.base_url
|
|
166
|
+
if existing.model.provider == provider
|
|
167
|
+
else default_base_url_for_provider(provider)
|
|
168
|
+
)
|
|
169
|
+
base_url = _prompt_config_value("Base URL", default=base_default)
|
|
170
|
+
thinking_mode = _prompt_thinking_mode_value(provider, default=existing.model.reasoning_mode)
|
|
142
171
|
theme = _prompt_theme_value(default=existing.ui.theme)
|
|
143
|
-
_write_config(
|
|
144
|
-
|
|
145
|
-
|
|
172
|
+
_write_config(
|
|
173
|
+
config_path,
|
|
174
|
+
api_key=api_key,
|
|
175
|
+
provider=provider,
|
|
176
|
+
model=model,
|
|
177
|
+
base_url=base_url,
|
|
178
|
+
thinking_mode=thinking_mode,
|
|
179
|
+
theme=theme,
|
|
180
|
+
)
|
|
146
181
|
|
|
147
182
|
|
|
148
183
|
def _cmd_config_reset(args: argparse.Namespace) -> int:
|
|
149
184
|
config_path = args.config.expanduser() if args.config else Path.home() / ".deepy" / "config.toml"
|
|
150
185
|
if config_path.suffix == ".json":
|
|
151
186
|
raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
|
|
187
|
+
previous_text = config_path.read_text(encoding="utf-8") if config_path.exists() else None
|
|
152
188
|
if config_path.exists():
|
|
153
189
|
config_path.unlink()
|
|
154
190
|
print(f"Removed {config_path}")
|
|
155
191
|
else:
|
|
156
192
|
print(f"No existing config at {config_path}")
|
|
157
193
|
print("Starting Deepy configuration setup...")
|
|
158
|
-
|
|
159
|
-
|
|
194
|
+
try:
|
|
195
|
+
_run_config_setup(config_path)
|
|
196
|
+
except (KeyboardInterrupt, EOFError, StopIteration):
|
|
197
|
+
_restore_config_after_failed_setup(config_path, previous_text)
|
|
198
|
+
print(_setup_cancelled_message(previous_text), file=sys.stderr)
|
|
199
|
+
return 1
|
|
200
|
+
print(f"Wrote {config_path}")
|
|
201
|
+
return 0
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _setup_cancelled_message(previous_text: str | None) -> str:
|
|
205
|
+
if previous_text is None:
|
|
206
|
+
return "Configuration setup cancelled. No config was written."
|
|
207
|
+
return "Configuration setup cancelled. Existing config was left unchanged."
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _restore_config_after_failed_setup(config_path: Path, previous_text: str | None) -> None:
|
|
211
|
+
if previous_text is None:
|
|
212
|
+
try:
|
|
213
|
+
config_path.unlink()
|
|
214
|
+
except FileNotFoundError:
|
|
215
|
+
pass
|
|
216
|
+
return
|
|
217
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
config_path.write_text(previous_text, encoding="utf-8")
|
|
219
|
+
try:
|
|
220
|
+
config_path.chmod(0o600)
|
|
221
|
+
except OSError:
|
|
222
|
+
pass
|
|
160
223
|
|
|
161
224
|
|
|
162
225
|
def _prompt_config_value(label: str, *, default: str, is_password: bool = False) -> str:
|
|
@@ -171,6 +234,151 @@ def _prompt_config_value(label: str, *, default: str, is_password: bool = False)
|
|
|
171
234
|
return value or default
|
|
172
235
|
|
|
173
236
|
|
|
237
|
+
def _prompt_provider_value(*, default: str = "deepseek") -> str:
|
|
238
|
+
print("Provider:")
|
|
239
|
+
for index, provider in enumerate(PROVIDER_CATALOG, 1):
|
|
240
|
+
print(f"{index}. {provider.id} {provider.description}")
|
|
241
|
+
value = _prompt_config_value("Provider number or name", default=_provider_number(default))
|
|
242
|
+
return _provider_from_selection(value, default=default)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _provider_number(provider: str) -> str:
|
|
246
|
+
for index, item in enumerate(PROVIDER_CATALOG, 1):
|
|
247
|
+
if item.id == provider:
|
|
248
|
+
return str(index)
|
|
249
|
+
return "1"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _provider_from_selection(value: str, *, default: str = "deepseek") -> str:
|
|
253
|
+
normalized = value.strip().lower()
|
|
254
|
+
by_number = {str(index): item.id for index, item in enumerate(PROVIDER_CATALOG, 1)}
|
|
255
|
+
if normalized in by_number:
|
|
256
|
+
return by_number[normalized]
|
|
257
|
+
if is_supported_provider(normalized):
|
|
258
|
+
return normalized
|
|
259
|
+
return default if is_supported_provider(default) else "deepseek"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _prompt_model_value(provider: str, *, default: str) -> str:
|
|
263
|
+
provider_info = provider_info_for(provider)
|
|
264
|
+
print("Model:")
|
|
265
|
+
for index, model in enumerate(provider_info.models, 1):
|
|
266
|
+
print(f"{index}. {model.name} {model.description}")
|
|
267
|
+
if allows_custom_model_for_provider(provider):
|
|
268
|
+
print("Or paste any model name copied from the OpenRouter models page.")
|
|
269
|
+
default_value = default if default in {model.name for model in provider_info.models} else provider_info.default_model
|
|
270
|
+
value = _prompt_config_value("Model number or name", default=_model_number(provider, default_value))
|
|
271
|
+
return _model_from_selection(provider, value, default=default_value)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _model_number(provider: str, model: str) -> str:
|
|
275
|
+
for index, item in enumerate(provider_info_for(provider).models, 1):
|
|
276
|
+
if item.name == model:
|
|
277
|
+
return str(index)
|
|
278
|
+
return "1"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _model_from_selection(provider: str, value: str, *, default: str) -> str:
|
|
282
|
+
normalized = value.strip()
|
|
283
|
+
models = provider_info_for(provider).models
|
|
284
|
+
by_number = {str(index): item.name for index, item in enumerate(models, 1)}
|
|
285
|
+
if normalized in by_number:
|
|
286
|
+
return by_number[normalized]
|
|
287
|
+
if normalized in {item.name for item in models}:
|
|
288
|
+
return normalized
|
|
289
|
+
if allows_custom_model_for_provider(provider) and normalized:
|
|
290
|
+
return normalized
|
|
291
|
+
return default_model_for_provider(provider) if not default else default
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _prompt_thinking_mode_value(provider: str, *, default: str) -> str:
|
|
295
|
+
if provider == "openrouter":
|
|
296
|
+
return _prompt_openrouter_thinking_mode(default=default)
|
|
297
|
+
modes = thinking_modes_for_provider(provider)
|
|
298
|
+
print("Thinking:")
|
|
299
|
+
for index, mode in enumerate(modes, 1):
|
|
300
|
+
print(f"{index}. {mode}")
|
|
301
|
+
default_value = default if is_valid_thinking_mode_for_provider(default, provider) else default_thinking_mode_for_provider(provider)
|
|
302
|
+
value = _prompt_config_value("Thinking number or name", default=_thinking_mode_number(provider, default_value))
|
|
303
|
+
return _thinking_mode_from_selection(provider, value, default=default_value)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _thinking_mode_number(provider: str, mode: str) -> str:
|
|
307
|
+
for index, item in enumerate(thinking_modes_for_provider(provider), 1):
|
|
308
|
+
if item == mode:
|
|
309
|
+
return str(index)
|
|
310
|
+
return "1"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _prompt_openrouter_thinking_mode(*, default: str) -> str:
|
|
314
|
+
current_enabled = default not in {"none", "disabled"}
|
|
315
|
+
print("Thinking:")
|
|
316
|
+
print("1. enabled Reasoning enabled")
|
|
317
|
+
print("2. disabled Reasoning disabled")
|
|
318
|
+
state_default = "1" if current_enabled else "2"
|
|
319
|
+
state_value = _prompt_config_value("Thinking number or name", default=state_default)
|
|
320
|
+
state = _openrouter_thinking_state_from_selection(state_value, default="enabled" if current_enabled else "disabled")
|
|
321
|
+
if state == "disabled":
|
|
322
|
+
return "none"
|
|
323
|
+
print("Reasoning effort:")
|
|
324
|
+
print("1. default Use the model default reasoning strength")
|
|
325
|
+
for index, effort in enumerate(("xhigh", "high", "medium", "low", "minimal"), 2):
|
|
326
|
+
print(f"{index}. {effort}")
|
|
327
|
+
effort_default = _openrouter_effort_number(default)
|
|
328
|
+
effort_value = _prompt_config_value("Reasoning effort number or name", default=effort_default)
|
|
329
|
+
return _openrouter_effort_from_selection(effort_value, default=default)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _openrouter_thinking_state_from_selection(value: str, *, default: str) -> str:
|
|
333
|
+
normalized = value.strip().lower()
|
|
334
|
+
if normalized in {"1", "enabled", "enable", "on", "true", "yes"}:
|
|
335
|
+
return "enabled"
|
|
336
|
+
if normalized in {"2", "disabled", "disable", "off", "false", "no", "none"}:
|
|
337
|
+
return "disabled"
|
|
338
|
+
return default
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _openrouter_effort_number(mode: str) -> str:
|
|
342
|
+
return {
|
|
343
|
+
"enabled": "1",
|
|
344
|
+
"xhigh": "2",
|
|
345
|
+
"high": "3",
|
|
346
|
+
"medium": "4",
|
|
347
|
+
"low": "5",
|
|
348
|
+
"minimal": "6",
|
|
349
|
+
}.get(mode, "1")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _openrouter_effort_from_selection(value: str, *, default: str) -> str:
|
|
353
|
+
normalized = value.strip().lower()
|
|
354
|
+
by_number = {
|
|
355
|
+
"1": "enabled",
|
|
356
|
+
"2": "xhigh",
|
|
357
|
+
"3": "high",
|
|
358
|
+
"4": "medium",
|
|
359
|
+
"5": "low",
|
|
360
|
+
"6": "minimal",
|
|
361
|
+
}
|
|
362
|
+
if normalized in by_number:
|
|
363
|
+
return by_number[normalized]
|
|
364
|
+
if normalized in {"default", "enabled"}:
|
|
365
|
+
return "enabled"
|
|
366
|
+
if normalized in {"xhigh", "high", "medium", "low", "minimal"}:
|
|
367
|
+
return normalized
|
|
368
|
+
return default if default in {"enabled", "xhigh", "high", "medium", "low", "minimal"} else "enabled"
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _thinking_mode_from_selection(provider: str, value: str, *, default: str) -> str:
|
|
372
|
+
normalized = value.strip().lower()
|
|
373
|
+
modes = thinking_modes_for_provider(provider)
|
|
374
|
+
by_number = {str(index): mode for index, mode in enumerate(modes, 1)}
|
|
375
|
+
if normalized in by_number:
|
|
376
|
+
return by_number[normalized]
|
|
377
|
+
if normalized in modes:
|
|
378
|
+
return normalized
|
|
379
|
+
return default
|
|
380
|
+
|
|
381
|
+
|
|
174
382
|
def _prompt_theme_value(*, default: str = DEFAULT_UI_THEME) -> str:
|
|
175
383
|
print("UI theme:")
|
|
176
384
|
print("1. auto Detect when possible; falls back to dark")
|
|
@@ -180,8 +388,25 @@ def _prompt_theme_value(*, default: str = DEFAULT_UI_THEME) -> str:
|
|
|
180
388
|
return ui_theme_from_selection(value, default=default)
|
|
181
389
|
|
|
182
390
|
|
|
183
|
-
def _write_config(
|
|
184
|
-
|
|
391
|
+
def _write_config(
|
|
392
|
+
config_path: Path,
|
|
393
|
+
*,
|
|
394
|
+
api_key: str,
|
|
395
|
+
provider: str,
|
|
396
|
+
model: str,
|
|
397
|
+
base_url: str | None,
|
|
398
|
+
theme: str,
|
|
399
|
+
thinking_mode: str | None,
|
|
400
|
+
) -> None:
|
|
401
|
+
write_config(
|
|
402
|
+
config_path,
|
|
403
|
+
api_key=api_key,
|
|
404
|
+
provider=provider,
|
|
405
|
+
model=model,
|
|
406
|
+
base_url=base_url,
|
|
407
|
+
theme=theme,
|
|
408
|
+
thinking_mode=thinking_mode,
|
|
409
|
+
)
|
|
185
410
|
|
|
186
411
|
|
|
187
412
|
def _cmd_config_theme(args: argparse.Namespace) -> int:
|
|
@@ -215,6 +440,7 @@ def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, Any]]:
|
|
|
215
440
|
"configured" if settings.model.api_key else "missing; run `deepy config setup`",
|
|
216
441
|
)
|
|
217
442
|
check("model", bool(settings.model.name), settings.model.name)
|
|
443
|
+
check("provider", bool(settings.model.provider), settings.model.provider)
|
|
218
444
|
check("base_url", bool(settings.model.base_url), settings.model.base_url)
|
|
219
445
|
check(
|
|
220
446
|
"context_window",
|
|
@@ -245,10 +471,11 @@ def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, Any]]:
|
|
|
245
471
|
return 0 if ok else 1, {
|
|
246
472
|
"ok": ok,
|
|
247
473
|
"checks": checks,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
474
|
+
"thinking": {
|
|
475
|
+
"provider": settings.model.provider,
|
|
476
|
+
"enabled": settings.model.thinking_enabled,
|
|
477
|
+
"reasoning_effort": settings.model.reasoning_effort,
|
|
478
|
+
"reasoning_mode": settings.model.reasoning_mode,
|
|
252
479
|
},
|
|
253
480
|
}
|
|
254
481
|
|
|
@@ -278,6 +505,7 @@ async def _doctor_live(settings: Settings) -> dict[str, Any]:
|
|
|
278
505
|
usage = usage_from_run_result(result)
|
|
279
506
|
return {
|
|
280
507
|
"ok": True,
|
|
508
|
+
"provider": settings.model.provider,
|
|
281
509
|
"model": settings.model.name,
|
|
282
510
|
"base_url": settings.model.base_url,
|
|
283
511
|
"api_key": "configured",
|
|
@@ -314,14 +542,15 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
314
542
|
status = "ok" if item["ok"] else "fail"
|
|
315
543
|
print(f"{status:4} {item['name']}: {item['detail']}")
|
|
316
544
|
thinking = report["thinking"]
|
|
317
|
-
print(f"info
|
|
545
|
+
print(f"info provider: {thinking['provider']}")
|
|
546
|
+
print(f"info thinking: mode={thinking['reasoning_mode']}")
|
|
318
547
|
live = report.get("live")
|
|
319
548
|
if isinstance(live, dict):
|
|
320
549
|
if live.get("ok"):
|
|
321
550
|
usage = live.get("usage")
|
|
322
551
|
print(
|
|
323
|
-
|
|
324
|
-
|
|
552
|
+
"ok live: "
|
|
553
|
+
f"provider={live.get('provider')} model={live.get('model')} base_url={live.get('base_url')} "
|
|
325
554
|
f"response={live.get('response_summary')!r} "
|
|
326
555
|
f"{format_usage_line(usage if isinstance(usage, dict) else TokenUsage())}"
|
|
327
556
|
)
|
|
@@ -415,9 +644,10 @@ def _cmd_status(args: argparse.Namespace) -> int:
|
|
|
415
644
|
def _ensure_interactive_settings(args: argparse.Namespace) -> Settings:
|
|
416
645
|
settings = load_settings(args.config)
|
|
417
646
|
if not settings.model.api_key:
|
|
418
|
-
print("Deepy needs a
|
|
647
|
+
print("Deepy needs a provider API key before starting interactive mode.")
|
|
419
648
|
setup_args = argparse.Namespace(config=args.config, force=True)
|
|
420
|
-
_cmd_config_setup(setup_args)
|
|
649
|
+
if _cmd_config_setup(setup_args) != 0:
|
|
650
|
+
raise SystemExit(1)
|
|
421
651
|
settings = load_settings(args.config)
|
|
422
652
|
if settings.path is not None and not settings.ui.theme_configured:
|
|
423
653
|
theme = _prompt_theme_value(default=settings.ui.theme)
|