deepy-cli 0.2.26__tar.gz → 0.2.27__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.
Files changed (118) hide show
  1. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/PKG-INFO +26 -13
  2. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/README.md +24 -11
  3. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/pyproject.toml +2 -2
  4. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/__init__.py +1 -1
  5. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/audit.py +2 -0
  6. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/cli.py +51 -6
  7. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/config/__init__.py +24 -0
  8. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/config/settings.py +131 -1
  9. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/runner.py +52 -2
  10. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/status.py +4 -0
  11. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/builtin.py +267 -0
  12. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/app.py +1184 -213
  13. deepy_cli-0.2.27/src/deepy/tui/commands.py +86 -0
  14. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/compat.py +1 -1
  15. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/diff.py +53 -9
  16. deepy_cli-0.2.27/src/deepy/tui/interaction_surfaces.py +121 -0
  17. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/runner.py +6 -4
  18. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/screens.py +193 -109
  19. deepy_cli-0.2.27/src/deepy/tui/theme.py +50 -0
  20. deepy_cli-0.2.27/src/deepy/tui/transcript.py +43 -0
  21. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/widgets.py +721 -189
  22. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/audit_approval_panel.py +11 -63
  23. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/local_command.py +1 -0
  24. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/markdown.py +15 -2
  25. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/message_view.py +99 -29
  26. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/slash_commands.py +78 -28
  27. deepy_cli-0.2.27/src/deepy/ui/syntax.py +88 -0
  28. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/terminal.py +175 -6
  29. deepy_cli-0.2.26/src/deepy/tui/commands.py +0 -105
  30. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/__main__.py +0 -0
  31. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/background_tasks.py +0 -0
  32. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/__init__.py +0 -0
  33. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
  34. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
  35. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  36. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/Read.md +0 -0
  37. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/Search.md +0 -0
  38. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/Update.md +0 -0
  39. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/WebFetch.md +0 -0
  40. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/WebSearch.md +0 -0
  41. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/Write.md +0 -0
  42. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/__init__.py +0 -0
  43. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/shell.md +0 -0
  44. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/task_list.md +0 -0
  45. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/task_output.md +0 -0
  46. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/task_stop.md +0 -0
  47. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/test_shell.md +0 -0
  48. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/data/tools/todo_write.md +0 -0
  49. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/errors.py +0 -0
  50. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/input_suggestions.py +0 -0
  51. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/__init__.py +0 -0
  52. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/agent.py +0 -0
  53. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/cache_context.py +0 -0
  54. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/compaction.py +0 -0
  55. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/context.py +0 -0
  56. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/events.py +0 -0
  57. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/model_capabilities.py +0 -0
  58. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/multimodal.py +0 -0
  59. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/provider.py +0 -0
  60. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/replay.py +0 -0
  61. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/llm/thinking.py +0 -0
  62. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/mcp.py +0 -0
  63. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/prompts/__init__.py +0 -0
  64. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/prompts/compact.py +0 -0
  65. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/prompts/init_agents.py +0 -0
  66. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/prompts/rules.py +0 -0
  67. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/prompts/runtime_context.py +0 -0
  68. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/prompts/system.py +0 -0
  69. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/prompts/tool_docs.py +0 -0
  70. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/session_cost.py +0 -0
  71. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/sessions/__init__.py +0 -0
  72. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/sessions/index.py +0 -0
  73. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/sessions/manager.py +0 -0
  74. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/sessions/session.py +0 -0
  75. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/sessions/store_helpers.py +0 -0
  76. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/skill_market.py +0 -0
  77. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/skills.py +0 -0
  78. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/subagents.py +0 -0
  79. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/todos.py +0 -0
  80. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/__init__.py +0 -0
  81. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/agents.py +0 -0
  82. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/file_state.py +0 -0
  83. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/result.py +0 -0
  84. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/search.py +0 -0
  85. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/shell_output.py +0 -0
  86. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/shell_utils.py +0 -0
  87. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tools/test_shell.py +0 -0
  88. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/__init__.py +0 -0
  89. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/tui/state.py +0 -0
  90. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/types/__init__.py +0 -0
  91. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/types/sdk.py +0 -0
  92. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/types/tool_payloads.py +0 -0
  93. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/__init__.py +0 -0
  94. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/app.py +0 -0
  95. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/ask_user_question.py +0 -0
  96. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/audit_approval_picker.py +0 -0
  97. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/exit_summary.py +0 -0
  98. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/file_mentions.py +0 -0
  99. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/image_input.py +0 -0
  100. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/loading_text.py +0 -0
  101. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/model_picker.py +0 -0
  102. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/prompt_buffer.py +0 -0
  103. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/prompt_input.py +0 -0
  104. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/session_list.py +0 -0
  105. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/session_picker.py +0 -0
  106. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/skill_picker.py +0 -0
  107. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/status_footer.py +0 -0
  108. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/styles.py +0 -0
  109. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/theme_picker.py +0 -0
  110. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/thinking_state.py +0 -0
  111. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/ui/welcome.py +0 -0
  112. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/update_check.py +0 -0
  113. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/usage.py +0 -0
  114. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/utils/__init__.py +0 -0
  115. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/utils/debug_logger.py +0 -0
  116. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/utils/error_logger.py +0 -0
  117. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/src/deepy/utils/json.py +0 -0
  118. {deepy_cli-0.2.26 → deepy_cli-0.2.27}/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.26
3
+ Version: 0.2.27
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
@@ -21,7 +21,7 @@ Requires-Dist: prompt-toolkit>=3.0,<4
21
21
  Requires-Dist: regex>=2026.5.9
22
22
  Requires-Dist: pyyaml>=6.0,<7
23
23
  Requires-Dist: rich>=14.2,<15
24
- Requires-Dist: textual>=8.2,<9
24
+ Requires-Dist: textual>=8.2.7,<9
25
25
  Requires-Dist: tiktoken>=0.9,<1
26
26
  Requires-Dist: tomli-w>=1
27
27
  Requires-Python: >=3.12
@@ -162,24 +162,35 @@ for direct local commands.
162
162
 
163
163
  ![Deepy web research workflow](asset/websearch.webp)
164
164
 
165
- ## Stable UI And Experimental TUI
165
+ ## Classic UI And Modern UI
166
166
 
167
- The default `deepy` command starts the stable Rich/prompt-toolkit terminal UI.
168
- The opt-in Textual interface is available with:
167
+ Deepy has two peer terminal interfaces. Classic UI is the Rich/prompt-toolkit
168
+ terminal UI, and Modern UI is the Textual terminal UI. The default `deepy`
169
+ command starts the UI saved in `ui.interface`; missing config defaults to
170
+ Classic UI with the dark theme. The compatibility command below still starts
171
+ Modern UI directly:
169
172
 
170
173
  ```bash
171
174
  deepy tui
172
175
  ```
173
176
 
174
- The TUI has a scrollable transcript, live thinking blocks, richer tool output
175
- blocks, slash-command and `@file` suggestions, status/help screens, and a
176
- Deepy-owned diff view. It remains experimental and may change between releases.
177
-
178
- ![Deepy Textual TUI](asset/deepy-tui.webp)
177
+ Modern UI has a compact scrollable transcript, lightweight status line,
178
+ Textual-native composer, live assistant/tool activity states, compact tool
179
+ summary rows, slash-command and `@file` suggestions, inline audit/model/theme
180
+ choices, status/help screens, and a Deepy-owned diff view. Use `/ui` in either
181
+ interface to change the default UI for the next `deepy` startup.
179
182
 
180
183
  `/status` shows session/project usage, context-window pressure, and DeepSeek
181
- balance in one panel. Exiting the TUI prints the same compact session summary
182
- as the stable terminal UI.
184
+ balance in one panel. Exiting Modern UI prints the same compact session summary
185
+ as Classic UI. Prompt text, image attachments, generated input
186
+ suggestions, slash suggestions, and file suggestions are separate composer
187
+ states, so UI-only attachment labels are not written into the editable prompt
188
+ buffer. Attached images appear in an attachment row with prompt-local keyboard
189
+ shortcuts: press Down to enter attachment selection, Left/Right to choose,
190
+ Backspace to remove, and Up to return to normal input. The shared `dark` /
191
+ `light` setting maps to curated
192
+ Textual themes in Modern UI, with `dark` defaulting to `tokyo-night`; Modern UI can
193
+ also save a Textual-only `ui.textual_theme` override from `/theme`.
183
194
 
184
195
  See [docs/deepy-ui-and-tui.md](docs/deepy-ui-and-tui.md) for the full feature
185
196
  comparison and current limitations.
@@ -263,7 +274,7 @@ MCP inheritance, and troubleshooting.
263
274
  | Tutorial videos | [docs/tutorial-videos.md](docs/tutorial-videos.md) | [docs/tutorial-videos.zh-CN.md](docs/tutorial-videos.zh-CN.md) |
264
275
  | MCP setup and troubleshooting | [docs/mcp.md](docs/mcp.md) | [docs/mcp.zh-CN.md](docs/mcp.zh-CN.md) |
265
276
  | Subagents and custom subagents | [docs/subagents.md](docs/subagents.md) | [docs/subagents.zh-CN.md](docs/subagents.zh-CN.md) |
266
- | Stable UI versus experimental TUI | [docs/deepy-ui-and-tui.md](docs/deepy-ui-and-tui.md) | [docs/deepy-ui-and-tui.zh-CN.md](docs/deepy-ui-and-tui.zh-CN.md) |
277
+ | Classic UI and Modern UI | [docs/deepy-ui-and-tui.md](docs/deepy-ui-and-tui.md) | [docs/deepy-ui-and-tui.zh-CN.md](docs/deepy-ui-and-tui.zh-CN.md) |
267
278
 
268
279
  ## Command Reference
269
280
 
@@ -301,6 +312,7 @@ Inside the interactive terminal:
301
312
  /<name> [request] Invoke a Skill directly
302
313
  /init Create or update project AGENTS.md
303
314
  /theme Show or change terminal UI theme
315
+ /ui Show or change Classic/Modern UI
304
316
  /ps Show managed background shell tasks
305
317
  /stop Choose background shell tasks to stop
306
318
  ```
@@ -331,6 +343,7 @@ compact_preserve_recent_messages = 2
331
343
  mode = "yolo" # normal, auto, or yolo
332
344
 
333
345
  [ui]
346
+ interface = "classic" # classic or modern
334
347
  theme = "dark" # dark or light
335
348
  ```
336
349
 
@@ -130,24 +130,35 @@ for direct local commands.
130
130
 
131
131
  ![Deepy web research workflow](asset/websearch.webp)
132
132
 
133
- ## Stable UI And Experimental TUI
133
+ ## Classic UI And Modern UI
134
134
 
135
- The default `deepy` command starts the stable Rich/prompt-toolkit terminal UI.
136
- The opt-in Textual interface is available with:
135
+ Deepy has two peer terminal interfaces. Classic UI is the Rich/prompt-toolkit
136
+ terminal UI, and Modern UI is the Textual terminal UI. The default `deepy`
137
+ command starts the UI saved in `ui.interface`; missing config defaults to
138
+ Classic UI with the dark theme. The compatibility command below still starts
139
+ Modern UI directly:
137
140
 
138
141
  ```bash
139
142
  deepy tui
140
143
  ```
141
144
 
142
- The TUI has a scrollable transcript, live thinking blocks, richer tool output
143
- blocks, slash-command and `@file` suggestions, status/help screens, and a
144
- Deepy-owned diff view. It remains experimental and may change between releases.
145
-
146
- ![Deepy Textual TUI](asset/deepy-tui.webp)
145
+ Modern UI has a compact scrollable transcript, lightweight status line,
146
+ Textual-native composer, live assistant/tool activity states, compact tool
147
+ summary rows, slash-command and `@file` suggestions, inline audit/model/theme
148
+ choices, status/help screens, and a Deepy-owned diff view. Use `/ui` in either
149
+ interface to change the default UI for the next `deepy` startup.
147
150
 
148
151
  `/status` shows session/project usage, context-window pressure, and DeepSeek
149
- balance in one panel. Exiting the TUI prints the same compact session summary
150
- as the stable terminal UI.
152
+ balance in one panel. Exiting Modern UI prints the same compact session summary
153
+ as Classic UI. Prompt text, image attachments, generated input
154
+ suggestions, slash suggestions, and file suggestions are separate composer
155
+ states, so UI-only attachment labels are not written into the editable prompt
156
+ buffer. Attached images appear in an attachment row with prompt-local keyboard
157
+ shortcuts: press Down to enter attachment selection, Left/Right to choose,
158
+ Backspace to remove, and Up to return to normal input. The shared `dark` /
159
+ `light` setting maps to curated
160
+ Textual themes in Modern UI, with `dark` defaulting to `tokyo-night`; Modern UI can
161
+ also save a Textual-only `ui.textual_theme` override from `/theme`.
151
162
 
152
163
  See [docs/deepy-ui-and-tui.md](docs/deepy-ui-and-tui.md) for the full feature
153
164
  comparison and current limitations.
@@ -231,7 +242,7 @@ MCP inheritance, and troubleshooting.
231
242
  | Tutorial videos | [docs/tutorial-videos.md](docs/tutorial-videos.md) | [docs/tutorial-videos.zh-CN.md](docs/tutorial-videos.zh-CN.md) |
232
243
  | MCP setup and troubleshooting | [docs/mcp.md](docs/mcp.md) | [docs/mcp.zh-CN.md](docs/mcp.zh-CN.md) |
233
244
  | Subagents and custom subagents | [docs/subagents.md](docs/subagents.md) | [docs/subagents.zh-CN.md](docs/subagents.zh-CN.md) |
234
- | Stable UI versus experimental TUI | [docs/deepy-ui-and-tui.md](docs/deepy-ui-and-tui.md) | [docs/deepy-ui-and-tui.zh-CN.md](docs/deepy-ui-and-tui.zh-CN.md) |
245
+ | Classic UI and Modern UI | [docs/deepy-ui-and-tui.md](docs/deepy-ui-and-tui.md) | [docs/deepy-ui-and-tui.zh-CN.md](docs/deepy-ui-and-tui.zh-CN.md) |
235
246
 
236
247
  ## Command Reference
237
248
 
@@ -269,6 +280,7 @@ Inside the interactive terminal:
269
280
  /<name> [request] Invoke a Skill directly
270
281
  /init Create or update project AGENTS.md
271
282
  /theme Show or change terminal UI theme
283
+ /ui Show or change Classic/Modern UI
272
284
  /ps Show managed background shell tasks
273
285
  /stop Choose background shell tasks to stop
274
286
  ```
@@ -299,6 +311,7 @@ compact_preserve_recent_messages = 2
299
311
  mode = "yolo" # normal, auto, or yolo
300
312
 
301
313
  [ui]
314
+ interface = "classic" # classic or modern
302
315
  theme = "dark" # dark or light
303
316
  ```
304
317
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.2.26"
3
+ version = "0.2.27"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -27,7 +27,7 @@ dependencies = [
27
27
  "regex>=2026.5.9",
28
28
  "pyyaml>=6.0,<7",
29
29
  "rich>=14.2,<15",
30
- "textual>=8.2,<9",
30
+ "textual>=8.2.7,<9",
31
31
  "tiktoken>=0.9,<1",
32
32
  "tomli-w>=1",
33
33
  ]
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.2.26"
3
+ __version__ = "0.2.27"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -136,9 +136,11 @@ class PendingApproval:
136
136
  name: str
137
137
  tool_name: str
138
138
  arguments: str
139
+ call_id: str = ""
139
140
  agent_name: str = ""
140
141
  action_kind: str = "tool"
141
142
  server_name: str = ""
143
+ preflight: Mapping[str, Any] | None = None
142
144
 
143
145
 
144
146
  @dataclass(frozen=True)
@@ -22,9 +22,12 @@ from .config import (
22
22
  provider_info_for,
23
23
  settings_to_toml_dict,
24
24
  thinking_modes_for_provider,
25
+ ui_setup_from_selection,
26
+ ui_setup_number,
25
27
  ui_theme_from_selection,
26
28
  ui_theme_number,
27
29
  update_config_theme,
30
+ update_config_ui_choice,
28
31
  write_config,
29
32
  )
30
33
  from .config.settings import DEFAULT_UI_THEME, UI_THEMES
@@ -86,7 +89,7 @@ def _build_parser() -> argparse.ArgumentParser:
86
89
  run_parser.add_argument("--session", help="Resume an existing session id.")
87
90
  run_parser.add_argument("--skill", action="append", default=[], help="Load a skill by name.")
88
91
 
89
- subparsers.add_parser("tui", help="Start the experimental Textual TUI.")
92
+ subparsers.add_parser("tui", help="Start the Modern UI.")
90
93
 
91
94
  sessions_parser = subparsers.add_parser("sessions", help="Inspect project sessions.")
92
95
  sessions_sub = sessions_parser.add_subparsers(dest="sessions_command", required=True)
@@ -131,6 +134,7 @@ def _cmd_config_init(args: argparse.Namespace) -> int:
131
134
  base_url=args.base_url,
132
135
  thinking_mode=args.thinking,
133
136
  theme=args.theme,
137
+ interface="classic",
134
138
  )
135
139
  print(f"Wrote {config_path}")
136
140
  return 0
@@ -170,7 +174,10 @@ def _run_config_setup(config_path: Path) -> None:
170
174
  )
171
175
  base_url = _prompt_config_value("Base URL", default=base_default)
172
176
  thinking_mode = _prompt_thinking_mode_value(provider, default=existing.model.reasoning_mode)
173
- theme = _prompt_theme_value(default=existing.ui.theme)
177
+ interface, theme = _prompt_ui_choice_value(
178
+ default_interface=existing.ui.interface,
179
+ default_theme=existing.ui.theme,
180
+ )
174
181
  _write_config(
175
182
  config_path,
176
183
  api_key=api_key,
@@ -179,6 +186,7 @@ def _run_config_setup(config_path: Path) -> None:
179
186
  base_url=base_url,
180
187
  thinking_mode=thinking_mode,
181
188
  theme=theme,
189
+ interface=interface,
182
190
  )
183
191
 
184
192
 
@@ -389,6 +397,27 @@ def _prompt_theme_value(*, default: str = DEFAULT_UI_THEME) -> str:
389
397
  return ui_theme_from_selection(value, default=default)
390
398
 
391
399
 
400
+ def _prompt_ui_choice_value(
401
+ *,
402
+ default_interface: str = "classic",
403
+ default_theme: str = DEFAULT_UI_THEME,
404
+ ) -> tuple[str, str]:
405
+ print("UI:")
406
+ print("1. Classic UI + dark theme Default terminal UI")
407
+ print("2. Classic UI + light theme")
408
+ print("3. Modern UI + dark theme Textual UI")
409
+ print("4. Modern UI + light theme Textual UI")
410
+ value = _prompt_config_value(
411
+ "UI number",
412
+ default=ui_setup_number(default_interface, default_theme),
413
+ )
414
+ return ui_setup_from_selection(
415
+ value,
416
+ default_interface=default_interface,
417
+ default_theme=default_theme,
418
+ )
419
+
420
+
392
421
  def _write_config(
393
422
  config_path: Path,
394
423
  *,
@@ -397,6 +426,7 @@ def _write_config(
397
426
  model: str,
398
427
  base_url: str | None,
399
428
  theme: str,
429
+ interface: str,
400
430
  thinking_mode: str | None,
401
431
  ) -> None:
402
432
  write_config(
@@ -406,6 +436,7 @@ def _write_config(
406
436
  model=model,
407
437
  base_url=base_url,
408
438
  theme=theme,
439
+ interface=interface,
409
440
  thinking_mode=thinking_mode,
410
441
  )
411
442
 
@@ -670,21 +701,35 @@ def _ensure_interactive_settings(args: argparse.Namespace) -> Settings:
670
701
  raise SystemExit(1)
671
702
  settings = load_settings(args.config)
672
703
  if settings.path is not None and not settings.ui.theme_configured:
673
- theme = _prompt_theme_value(default=settings.ui.theme)
674
- update_config_theme(settings.path, theme)
704
+ interface, theme = _prompt_ui_choice_value(
705
+ default_interface=settings.ui.interface,
706
+ default_theme=settings.ui.theme,
707
+ )
708
+ update_config_ui_choice(settings.path, interface=interface, theme=theme)
675
709
  settings = load_settings(args.config)
676
710
  return settings
677
711
 
678
712
 
679
713
  def _cmd_tui(args: argparse.Namespace) -> int:
680
714
  if not sys.stdin.isatty():
681
- print("experimental TUI requires a TTY; use `deepy` for the stable terminal UI.", file=sys.stderr)
715
+ print("Modern UI requires a TTY; use `deepy run` for non-interactive prompts.", file=sys.stderr)
682
716
  return 1
683
717
  from deepy.tui import run_tui
684
718
 
685
719
  return run_tui(_ensure_interactive_settings(args), project_root=Path.cwd())
686
720
 
687
721
 
722
+ def _cmd_interactive(args: argparse.Namespace) -> int:
723
+ settings = _ensure_interactive_settings(args)
724
+ if settings.ui.interface == "modern":
725
+ if not sys.stdin.isatty():
726
+ raise RuntimeError("Modern UI requires a TTY.")
727
+ from deepy.tui import run_tui
728
+
729
+ return run_tui(settings, project_root=Path.cwd())
730
+ return run_interactive(settings)
731
+
732
+
688
733
  def main(argv: Sequence[str] | None = None) -> int:
689
734
  parser = _build_parser()
690
735
  args = parser.parse_args(argv)
@@ -715,7 +760,7 @@ def main(argv: Sequence[str] | None = None) -> int:
715
760
 
716
761
  if not sys.stdin.isatty():
717
762
  parser.error("interactive mode requires a TTY; use `deepy doctor` or `deepy config show`.")
718
- return run_interactive(_ensure_interactive_settings(args))
763
+ return _cmd_interactive(args)
719
764
 
720
765
 
721
766
  if __name__ == "__main__":
@@ -6,6 +6,7 @@ from .settings import (
6
6
  DEFAULT_BASE_URL,
7
7
  DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES,
8
8
  DEFAULT_INPUT_SUGGESTIONS_ENABLED,
9
+ DEFAULT_UI_INTERFACE,
9
10
  DEFAULT_OPENROUTER_BASE_URL,
10
11
  DEFAULT_PROVIDER,
11
12
  DEFAULT_RESERVED_CONTEXT_TOKENS,
@@ -27,6 +28,9 @@ from .settings import (
27
28
  TestShellToolConfig,
28
29
  UI_THEME_OPTIONS,
29
30
  UI_THEMES,
31
+ UI_INTERFACE_OPTIONS,
32
+ UI_INTERFACES,
33
+ UI_SETUP_OPTIONS,
30
34
  UI_VIEW_MODES,
31
35
  ToolsConfig,
32
36
  UiConfig,
@@ -45,6 +49,7 @@ from .settings import (
45
49
  is_valid_thinking_mode,
46
50
  is_valid_thinking_mode_for_provider,
47
51
  is_valid_ui_theme,
52
+ is_valid_ui_interface,
48
53
  is_valid_ui_view_mode,
49
54
  is_valid_reasoning_mode,
50
55
  is_valid_config_audit_mode,
@@ -57,8 +62,15 @@ from .settings import (
57
62
  update_config_model_settings,
58
63
  update_config_audit_mode,
59
64
  update_config_input_suggestions_enabled,
65
+ update_config_textual_theme,
60
66
  update_config_theme,
67
+ update_config_ui_choice,
68
+ update_config_ui_interface,
61
69
  update_config_view_mode,
70
+ ui_interface_from_selection,
71
+ ui_interface_number,
72
+ ui_setup_from_selection,
73
+ ui_setup_number,
62
74
  ui_theme_from_selection,
63
75
  ui_theme_number,
64
76
  write_config,
@@ -70,6 +82,7 @@ __all__ = [
70
82
  "DEFAULT_BASE_URL",
71
83
  "DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES",
72
84
  "DEFAULT_INPUT_SUGGESTIONS_ENABLED",
85
+ "DEFAULT_UI_INTERFACE",
73
86
  "DEFAULT_OPENROUTER_BASE_URL",
74
87
  "DEFAULT_PROVIDER",
75
88
  "DEFAULT_RESERVED_CONTEXT_TOKENS",
@@ -91,6 +104,9 @@ __all__ = [
91
104
  "TestShellToolConfig",
92
105
  "UI_THEME_OPTIONS",
93
106
  "UI_THEMES",
107
+ "UI_INTERFACE_OPTIONS",
108
+ "UI_INTERFACES",
109
+ "UI_SETUP_OPTIONS",
94
110
  "UI_VIEW_MODES",
95
111
  "ToolsConfig",
96
112
  "UiConfig",
@@ -109,6 +125,7 @@ __all__ = [
109
125
  "is_valid_thinking_mode",
110
126
  "is_valid_thinking_mode_for_provider",
111
127
  "is_valid_ui_theme",
128
+ "is_valid_ui_interface",
112
129
  "is_valid_ui_view_mode",
113
130
  "is_valid_reasoning_mode",
114
131
  "is_valid_config_audit_mode",
@@ -121,8 +138,15 @@ __all__ = [
121
138
  "update_config_model_settings",
122
139
  "update_config_audit_mode",
123
140
  "update_config_input_suggestions_enabled",
141
+ "update_config_textual_theme",
124
142
  "update_config_theme",
143
+ "update_config_ui_choice",
144
+ "update_config_ui_interface",
125
145
  "update_config_view_mode",
146
+ "ui_interface_from_selection",
147
+ "ui_interface_number",
148
+ "ui_setup_from_selection",
149
+ "ui_setup_number",
126
150
  "ui_theme_from_selection",
127
151
  "ui_theme_number",
128
152
  "write_config",
@@ -19,6 +19,7 @@ DEFAULT_RESERVED_CONTEXT_TOKENS = 50_000
19
19
  DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES = 2
20
20
  DEFAULT_WEB_SEARCH_SEARXNG_URL = "https://s.kirineko.tech/"
21
21
  DEFAULT_UI_THEME = "dark"
22
+ DEFAULT_UI_INTERFACE = "classic"
22
23
  DEFAULT_MCP_ENABLED = True
23
24
  DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS = 10.0
24
25
  DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS = 10.0
@@ -50,6 +51,14 @@ THINKING_MODES = set(DEEPSEEK_REASONING_MODES) | set(SWITCH_ONLY_THINKING_MODES)
50
51
  PROVIDERS = {"deepseek", "openrouter", "xiaomi"}
51
52
  UI_THEMES = {"dark", "light"}
52
53
  UI_THEME_OPTIONS = (("1", "dark"), ("2", "light"))
54
+ UI_INTERFACES = {"classic", "modern"}
55
+ UI_INTERFACE_OPTIONS = (("1", "classic"), ("2", "modern"))
56
+ UI_SETUP_OPTIONS = (
57
+ ("1", "classic", "dark"),
58
+ ("2", "classic", "light"),
59
+ ("3", "modern", "dark"),
60
+ ("4", "modern", "light"),
61
+ )
53
62
  UI_VIEW_MODES = {"concise", "full"}
54
63
 
55
64
 
@@ -330,6 +339,10 @@ def _as_str(value: Any, default: str = "") -> str:
330
339
  return value.strip() if isinstance(value, str) and value.strip() else default
331
340
 
332
341
 
342
+ def _as_optional_str(value: Any) -> str | None:
343
+ return value.strip() if isinstance(value, str) and value.strip() else None
344
+
345
+
333
346
  def _as_string_tuple(value: Any) -> tuple[str, ...]:
334
347
  if not isinstance(value, list):
335
348
  return ()
@@ -567,13 +580,19 @@ class McpConfig:
567
580
  @dataclass(frozen=True)
568
581
  class UiConfig:
569
582
  theme: str = DEFAULT_UI_THEME
583
+ interface: str = DEFAULT_UI_INTERFACE
570
584
  theme_configured: bool = False
585
+ textual_theme: str | None = None
571
586
  input_suggestions_enabled: bool = DEFAULT_INPUT_SUGGESTIONS_ENABLED
572
587
  view_mode: str = DEFAULT_UI_VIEW_MODE
573
588
 
574
589
  @classmethod
575
590
  def from_mapping(cls, raw: Mapping[str, Any]) -> Self:
576
591
  theme = raw.get("theme")
592
+ interface = _as_str(raw.get("interface"), DEFAULT_UI_INTERFACE)
593
+ if interface not in UI_INTERFACES:
594
+ interface = DEFAULT_UI_INTERFACE
595
+ textual_theme = _as_optional_str(raw.get("textual_theme"))
577
596
  input_suggestions_enabled = _as_bool(
578
597
  raw.get("input_suggestions_enabled"),
579
598
  DEFAULT_INPUT_SUGGESTIONS_ENABLED,
@@ -584,18 +603,27 @@ class UiConfig:
584
603
  if isinstance(theme, str) and theme.strip() == "auto":
585
604
  return cls(
586
605
  theme=DEFAULT_UI_THEME,
606
+ interface=interface,
587
607
  theme_configured=True,
608
+ textual_theme=textual_theme,
588
609
  input_suggestions_enabled=input_suggestions_enabled,
589
610
  view_mode=view_mode,
590
611
  )
591
612
  if isinstance(theme, str) and theme.strip() in UI_THEMES:
592
613
  return cls(
593
614
  theme=theme.strip(),
615
+ interface=interface,
594
616
  theme_configured=True,
617
+ textual_theme=textual_theme,
595
618
  input_suggestions_enabled=input_suggestions_enabled,
596
619
  view_mode=view_mode,
597
620
  )
598
- return cls(input_suggestions_enabled=input_suggestions_enabled, view_mode=view_mode)
621
+ return cls(
622
+ textual_theme=textual_theme,
623
+ interface=interface,
624
+ input_suggestions_enabled=input_suggestions_enabled,
625
+ view_mode=view_mode,
626
+ )
599
627
 
600
628
 
601
629
  @dataclass(frozen=True)
@@ -672,6 +700,10 @@ def is_valid_ui_theme(value: str) -> bool:
672
700
  return value in UI_THEMES
673
701
 
674
702
 
703
+ def is_valid_ui_interface(value: str) -> bool:
704
+ return value in UI_INTERFACES
705
+
706
+
675
707
  def is_valid_ui_view_mode(value: str) -> bool:
676
708
  return value in UI_VIEW_MODES
677
709
 
@@ -716,6 +748,52 @@ def ui_theme_from_selection(value: str, *, default: str = DEFAULT_UI_THEME) -> s
716
748
  return default if is_valid_ui_theme(default) else DEFAULT_UI_THEME
717
749
 
718
750
 
751
+ def ui_interface_number(interface: str) -> str:
752
+ for number, value in UI_INTERFACE_OPTIONS:
753
+ if value == interface:
754
+ return number
755
+ return "1"
756
+
757
+
758
+ def ui_interface_from_selection(value: str, *, default: str = DEFAULT_UI_INTERFACE) -> str:
759
+ normalized = value.strip().lower()
760
+ if not normalized:
761
+ return default if is_valid_ui_interface(default) else DEFAULT_UI_INTERFACE
762
+ if normalized in UI_INTERFACES:
763
+ return normalized
764
+ by_number = dict(UI_INTERFACE_OPTIONS)
765
+ selected = by_number.get(normalized)
766
+ if selected is not None:
767
+ return selected
768
+ return default if is_valid_ui_interface(default) else DEFAULT_UI_INTERFACE
769
+
770
+
771
+ def ui_setup_number(interface: str, theme: str) -> str:
772
+ for number, option_interface, option_theme in UI_SETUP_OPTIONS:
773
+ if option_interface == interface and option_theme == theme:
774
+ return number
775
+ return "1"
776
+
777
+
778
+ def ui_setup_from_selection(
779
+ value: str,
780
+ *,
781
+ default_interface: str = DEFAULT_UI_INTERFACE,
782
+ default_theme: str = DEFAULT_UI_THEME,
783
+ ) -> tuple[str, str]:
784
+ normalized = value.strip().lower()
785
+ fallback = (
786
+ default_interface if is_valid_ui_interface(default_interface) else DEFAULT_UI_INTERFACE,
787
+ default_theme if is_valid_ui_theme(default_theme) else DEFAULT_UI_THEME,
788
+ )
789
+ if not normalized:
790
+ return fallback
791
+ for number, option_interface, option_theme in UI_SETUP_OPTIONS:
792
+ if normalized in {number, f"{option_interface}-{option_theme}", f"{option_interface} {option_theme}"}:
793
+ return option_interface, option_theme
794
+ return fallback
795
+
796
+
719
797
  def write_config(
720
798
  config_path: Path,
721
799
  *,
@@ -724,10 +802,13 @@ def write_config(
724
802
  model: str,
725
803
  base_url: str | None = None,
726
804
  theme: str,
805
+ interface: str = DEFAULT_UI_INTERFACE,
727
806
  thinking_mode: str | None = None,
728
807
  ) -> None:
729
808
  if not is_valid_ui_theme(theme):
730
809
  raise ValueError("UI theme must be one of: dark, light.")
810
+ if not is_valid_ui_interface(interface):
811
+ raise ValueError("UI interface must be one of: classic, modern.")
731
812
  if not is_supported_provider(provider):
732
813
  raise ValueError("Provider must be one of: deepseek, openrouter, xiaomi.")
733
814
  provider_info = provider_info_for(provider)
@@ -791,6 +872,7 @@ def write_config(
791
872
  },
792
873
  },
793
874
  "ui": {
875
+ "interface": interface,
794
876
  "theme": theme,
795
877
  "input_suggestions_enabled": DEFAULT_INPUT_SUGGESTIONS_ENABLED,
796
878
  "view_mode": DEFAULT_UI_VIEW_MODE,
@@ -866,6 +948,54 @@ def update_config_theme(config_path: Path, theme: str) -> None:
866
948
  ui = raw.get("ui")
867
949
  ui_map = dict(ui) if isinstance(ui, Mapping) else {}
868
950
  ui_map["theme"] = theme
951
+ ui_map.pop("textual_theme", None)
952
+ raw["ui"] = ui_map
953
+ _write_private_toml(path, raw)
954
+
955
+
956
+ def update_config_ui_interface(config_path: Path, interface: str) -> None:
957
+ if not is_valid_ui_interface(interface):
958
+ raise ValueError("UI interface must be one of: classic, modern.")
959
+ path = config_path.expanduser()
960
+ if path.suffix == ".json":
961
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
962
+ raw = _read_toml_mapping(path)
963
+ ui = raw.get("ui")
964
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
965
+ ui_map["interface"] = interface
966
+ raw["ui"] = ui_map
967
+ _write_private_toml(path, raw)
968
+
969
+
970
+ def update_config_ui_choice(config_path: Path, *, interface: str, theme: str) -> None:
971
+ if not is_valid_ui_interface(interface):
972
+ raise ValueError("UI interface must be one of: classic, modern.")
973
+ if not is_valid_ui_theme(theme):
974
+ raise ValueError("UI theme must be one of: dark, light.")
975
+ path = config_path.expanduser()
976
+ if path.suffix == ".json":
977
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
978
+ raw = _read_toml_mapping(path)
979
+ ui = raw.get("ui")
980
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
981
+ ui_map["interface"] = interface
982
+ ui_map["theme"] = theme
983
+ ui_map.pop("textual_theme", None)
984
+ raw["ui"] = ui_map
985
+ _write_private_toml(path, raw)
986
+
987
+
988
+ def update_config_textual_theme(config_path: Path, textual_theme: str) -> None:
989
+ theme = textual_theme.strip()
990
+ if not theme:
991
+ raise ValueError("Textual theme must not be empty.")
992
+ path = config_path.expanduser()
993
+ if path.suffix == ".json":
994
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
995
+ raw = _read_toml_mapping(path)
996
+ ui = raw.get("ui")
997
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
998
+ ui_map["textual_theme"] = theme
869
999
  raw["ui"] = ui_map
870
1000
  _write_private_toml(path, raw)
871
1001