deepy-cli 0.2.28__tar.gz → 0.2.30__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 (238) hide show
  1. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/PKG-INFO +1 -1
  2. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/pyproject.toml +1 -1
  3. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/__init__.py +1 -1
  4. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/cli.py +4 -4
  5. deepy_cli-0.2.30/src/deepy/config/config_io.py +463 -0
  6. deepy_cli-0.2.30/src/deepy/config/providers.py +343 -0
  7. deepy_cli-0.2.30/src/deepy/config/schema.py +357 -0
  8. deepy_cli-0.2.30/src/deepy/config/settings.py +163 -0
  9. deepy_cli-0.2.30/src/deepy/format_tokens.py +23 -0
  10. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/agent.py +1 -1
  11. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/runner.py +14 -493
  12. deepy_cli-0.2.30/src/deepy/llm/runner_approvals.py +175 -0
  13. deepy_cli-0.2.30/src/deepy/llm/runner_errors.py +126 -0
  14. deepy_cli-0.2.30/src/deepy/llm/runner_interrupt.py +209 -0
  15. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/mcp.py +116 -10
  16. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/sessions/session.py +1 -3
  17. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/status.py +1 -12
  18. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/agents.py +11 -332
  19. deepy_cli-0.2.30/src/deepy/tools/arg_repair.py +130 -0
  20. deepy_cli-0.2.30/src/deepy/tools/builtin.py +70 -0
  21. deepy_cli-0.2.30/src/deepy/tools/constants.py +72 -0
  22. deepy_cli-0.2.30/src/deepy/tools/media.py +134 -0
  23. deepy_cli-0.2.30/src/deepy/tools/mutation_policy.py +219 -0
  24. deepy_cli-0.2.30/src/deepy/tools/payload_parsing.py +210 -0
  25. deepy_cli-0.2.30/src/deepy/tools/runtime/__init__.py +0 -0
  26. deepy_cli-0.2.30/src/deepy/tools/runtime/interaction.py +122 -0
  27. deepy_cli-0.2.30/src/deepy/tools/runtime/mutation_apply.py +315 -0
  28. deepy_cli-0.2.30/src/deepy/tools/runtime/mutation_preflight.py +444 -0
  29. deepy_cli-0.2.30/src/deepy/tools/runtime/read.py +234 -0
  30. deepy_cli-0.2.30/src/deepy/tools/runtime/shell.py +213 -0
  31. deepy_cli-0.2.30/src/deepy/tools/runtime/state.py +23 -0
  32. deepy_cli-0.2.30/src/deepy/tools/runtime/tasks.py +83 -0
  33. deepy_cli-0.2.30/src/deepy/tools/runtime/web.py +297 -0
  34. deepy_cli-0.2.30/src/deepy/tools/schema_compat.py +95 -0
  35. deepy_cli-0.2.30/src/deepy/tools/shell_command.py +631 -0
  36. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/test_shell.py +76 -5
  37. deepy_cli-0.2.30/src/deepy/tools/text_io.py +177 -0
  38. deepy_cli-0.2.30/src/deepy/tools/text_match.py +212 -0
  39. deepy_cli-0.2.30/src/deepy/tools/tool_args.py +120 -0
  40. deepy_cli-0.2.30/src/deepy/tools/tool_dataclasses.py +169 -0
  41. deepy_cli-0.2.30/src/deepy/tools/web/__init__.py +0 -0
  42. deepy_cli-0.2.30/src/deepy/tools/web/fetch_html.py +200 -0
  43. deepy_cli-0.2.30/src/deepy/tools/web/query.py +171 -0
  44. deepy_cli-0.2.30/src/deepy/tools/web/search_parse.py +176 -0
  45. deepy_cli-0.2.30/src/deepy/ui/__init__.py +7 -0
  46. deepy_cli-0.2.30/src/deepy/ui/classic/__init__.py +8 -0
  47. deepy_cli-0.2.30/src/deepy/ui/classic/approvals.py +262 -0
  48. deepy_cli-0.2.30/src/deepy/ui/classic/commands/__init__.py +1 -0
  49. deepy_cli-0.2.30/src/deepy/ui/classic/commands/config_choices.py +140 -0
  50. deepy_cli-0.2.30/src/deepy/ui/classic/commands/config_commands.py +137 -0
  51. deepy_cli-0.2.30/src/deepy/ui/classic/commands/config_setup.py +180 -0
  52. deepy_cli-0.2.30/src/deepy/ui/classic/commands/model_commands.py +351 -0
  53. deepy_cli-0.2.30/src/deepy/ui/classic/commands/skill_commands.py +334 -0
  54. deepy_cli-0.2.30/src/deepy/ui/classic/esc_watch.py +138 -0
  55. deepy_cli-0.2.30/src/deepy/ui/classic/exit_summary.py +93 -0
  56. deepy_cli-0.2.30/src/deepy/ui/classic/footer.py +228 -0
  57. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/classic}/markdown.py +2 -2
  58. deepy_cli-0.2.30/src/deepy/ui/classic/pickers/__init__.py +1 -0
  59. deepy_cli-0.2.30/src/deepy/ui/classic/pickers/skill_detail_viewer.py +86 -0
  60. deepy_cli-0.2.30/src/deepy/ui/classic/pickers/skill_install_scope_picker.py +99 -0
  61. deepy_cli-0.2.28/src/deepy/ui/skill_picker.py → deepy_cli-0.2.30/src/deepy/ui/classic/pickers/skill_menu_picker.py +12 -291
  62. deepy_cli-0.2.30/src/deepy/ui/classic/pickers/skill_picker.py +38 -0
  63. deepy_cli-0.2.30/src/deepy/ui/classic/pickers/skill_picker_types.py +137 -0
  64. deepy_cli-0.2.30/src/deepy/ui/classic/printing.py +304 -0
  65. deepy_cli-0.2.30/src/deepy/ui/classic/prompt/__init__.py +1 -0
  66. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/classic/prompt}/prompt_input.py +18 -189
  67. deepy_cli-0.2.30/src/deepy/ui/classic/prompt/prompt_skills.py +32 -0
  68. deepy_cli-0.2.30/src/deepy/ui/classic/prompt/text_measure.py +68 -0
  69. deepy_cli-0.2.30/src/deepy/ui/classic/questions.py +117 -0
  70. deepy_cli-0.2.30/src/deepy/ui/classic/runtime_workers.py +179 -0
  71. deepy_cli-0.2.30/src/deepy/ui/classic/slash_commands.py +416 -0
  72. deepy_cli-0.2.30/src/deepy/ui/classic/startup.py +201 -0
  73. deepy_cli-0.2.30/src/deepy/ui/classic/status/__init__.py +1 -0
  74. deepy_cli-0.2.30/src/deepy/ui/classic/status/approval_render.py +75 -0
  75. deepy_cli-0.2.30/src/deepy/ui/classic/status/background_tasks.py +117 -0
  76. deepy_cli-0.2.30/src/deepy/ui/classic/status/runtime_status.py +311 -0
  77. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/classic/status}/status_footer.py +1 -1
  78. deepy_cli-0.2.30/src/deepy/ui/classic/status/transcript_parse.py +82 -0
  79. deepy_cli-0.2.30/src/deepy/ui/classic/status_display.py +121 -0
  80. deepy_cli-0.2.30/src/deepy/ui/classic/stream_render.py +220 -0
  81. deepy_cli-0.2.30/src/deepy/ui/classic/terminal.py +671 -0
  82. deepy_cli-0.2.30/src/deepy/ui/classic/terminal_bindings.py +41 -0
  83. deepy_cli-0.2.30/src/deepy/ui/classic/terminal_patchable.py +11 -0
  84. deepy_cli-0.2.30/src/deepy/ui/classic/terminal_types.py +33 -0
  85. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern}/__init__.py +1 -1
  86. deepy_cli-0.2.30/src/deepy/ui/modern/app.py +516 -0
  87. deepy_cli-0.2.30/src/deepy/ui/modern/app_bindings.py +34 -0
  88. deepy_cli-0.2.30/src/deepy/ui/modern/app_commands.py +665 -0
  89. deepy_cli-0.2.30/src/deepy/ui/modern/app_helpers.py +212 -0
  90. deepy_cli-0.2.30/src/deepy/ui/modern/app_interaction.py +296 -0
  91. deepy_cli-0.2.30/src/deepy/ui/modern/app_patchable.py +11 -0
  92. deepy_cli-0.2.30/src/deepy/ui/modern/app_sessions.py +174 -0
  93. deepy_cli-0.2.30/src/deepy/ui/modern/app_skills.py +350 -0
  94. deepy_cli-0.2.30/src/deepy/ui/modern/app_state_proto.py +163 -0
  95. deepy_cli-0.2.30/src/deepy/ui/modern/app_status.py +188 -0
  96. deepy_cli-0.2.30/src/deepy/ui/modern/app_streaming.py +275 -0
  97. deepy_cli-0.2.30/src/deepy/ui/modern/app_styles.py +449 -0
  98. deepy_cli-0.2.30/src/deepy/ui/modern/app_transcript.py +226 -0
  99. deepy_cli-0.2.30/src/deepy/ui/modern/app_widgets.py +52 -0
  100. deepy_cli-0.2.30/src/deepy/ui/modern/background_tasks_tui.py +57 -0
  101. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern}/commands.py +3 -15
  102. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern}/interaction_surfaces.py +1 -9
  103. deepy_cli-0.2.30/src/deepy/ui/modern/render/__init__.py +3 -0
  104. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern/render}/diff.py +2 -2
  105. deepy_cli-0.2.30/src/deepy/ui/modern/render/status_format.py +156 -0
  106. deepy_cli-0.2.30/src/deepy/ui/modern/render/tool_format.py +357 -0
  107. deepy_cli-0.2.30/src/deepy/ui/modern/render/transcript_items.py +203 -0
  108. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern}/runner.py +1 -1
  109. deepy_cli-0.2.30/src/deepy/ui/modern/screens/__init__.py +32 -0
  110. deepy_cli-0.2.30/src/deepy/ui/modern/screens/choice.py +158 -0
  111. deepy_cli-0.2.30/src/deepy/ui/modern/screens/config.py +16 -0
  112. deepy_cli-0.2.30/src/deepy/ui/modern/screens/info.py +50 -0
  113. deepy_cli-0.2.30/src/deepy/ui/modern/screens/skills.py +329 -0
  114. deepy_cli-0.2.30/src/deepy/ui/modern/skills_tui.py +85 -0
  115. deepy_cli-0.2.30/src/deepy/ui/modern/widgets/__init__.py +59 -0
  116. deepy_cli-0.2.30/src/deepy/ui/modern/widgets/blocks.py +129 -0
  117. deepy_cli-0.2.30/src/deepy/ui/modern/widgets/decision.py +216 -0
  118. deepy_cli-0.2.30/src/deepy/ui/modern/widgets/diff.py +63 -0
  119. deepy_cli-0.2.30/src/deepy/ui/modern/widgets/prompt.py +551 -0
  120. deepy_cli-0.2.30/src/deepy/ui/modern/widgets/question.py +277 -0
  121. deepy_cli-0.2.30/src/deepy/ui/modern/widgets/tools.py +227 -0
  122. deepy_cli-0.2.30/src/deepy/ui/shared/__init__.py +7 -0
  123. deepy_cli-0.2.30/src/deepy/ui/shared/input/__init__.py +7 -0
  124. deepy_cli-0.2.28/src/deepy/ui/image_input.py → deepy_cli-0.2.30/src/deepy/ui/shared/input/clipboard.py +12 -161
  125. deepy_cli-0.2.30/src/deepy/ui/shared/input/commands.py +19 -0
  126. deepy_cli-0.2.30/src/deepy/ui/shared/input/image_input.py +144 -0
  127. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/input}/slash_commands.py +0 -12
  128. deepy_cli-0.2.30/src/deepy/ui/shared/local_command/__init__.py +277 -0
  129. deepy_cli-0.2.30/src/deepy/ui/shared/local_command/core.py +225 -0
  130. deepy_cli-0.2.30/src/deepy/ui/shared/local_command/windows.py +163 -0
  131. deepy_cli-0.2.30/src/deepy/ui/shared/render/__init__.py +3 -0
  132. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/render}/audit_approval_panel.py +2 -2
  133. deepy_cli-0.2.30/src/deepy/ui/shared/render/diff_highlight.py +146 -0
  134. deepy_cli-0.2.30/src/deepy/ui/shared/render/diff_preview.py +285 -0
  135. deepy_cli-0.2.30/src/deepy/ui/shared/render/diff_types.py +24 -0
  136. deepy_cli-0.2.30/src/deepy/ui/shared/render/message_render.py +157 -0
  137. deepy_cli-0.2.30/src/deepy/ui/shared/render/message_view.py +204 -0
  138. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/render}/styles.py +0 -4
  139. deepy_cli-0.2.30/src/deepy/ui/shared/render/tool_output.py +369 -0
  140. deepy_cli-0.2.30/src/deepy/ui/shared/render/tool_snippets.py +214 -0
  141. deepy_cli-0.2.30/src/deepy/ui/shared/render/tool_text.py +115 -0
  142. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/render}/welcome.py +2 -2
  143. deepy_cli-0.2.30/src/deepy/ui/shared/session/__init__.py +3 -0
  144. deepy_cli-0.2.30/src/deepy/ui/shared/session/session_list.py +43 -0
  145. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/session}/session_picker.py +1 -1
  146. deepy_cli-0.2.30/src/deepy/ui/shared/session/session_transcript.py +126 -0
  147. deepy_cli-0.2.30/src/deepy/utils/clock.py +7 -0
  148. deepy_cli-0.2.28/src/deepy/config/settings.py +0 -1070
  149. deepy_cli-0.2.28/src/deepy/tools/builtin.py +0 -3846
  150. deepy_cli-0.2.28/src/deepy/tui/app.py +0 -3534
  151. deepy_cli-0.2.28/src/deepy/tui/compat.py +0 -7
  152. deepy_cli-0.2.28/src/deepy/tui/screens.py +0 -715
  153. deepy_cli-0.2.28/src/deepy/tui/widgets.py +0 -1784
  154. deepy_cli-0.2.28/src/deepy/ui/__init__.py +0 -5
  155. deepy_cli-0.2.28/src/deepy/ui/app.py +0 -118
  156. deepy_cli-0.2.28/src/deepy/ui/loading_text.py +0 -87
  157. deepy_cli-0.2.28/src/deepy/ui/local_command.py +0 -600
  158. deepy_cli-0.2.28/src/deepy/ui/message_view.py +0 -1389
  159. deepy_cli-0.2.28/src/deepy/ui/prompt_buffer.py +0 -176
  160. deepy_cli-0.2.28/src/deepy/ui/session_list.py +0 -140
  161. deepy_cli-0.2.28/src/deepy/ui/terminal.py +0 -4416
  162. deepy_cli-0.2.28/src/deepy/ui/thinking_state.py +0 -29
  163. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/README.md +0 -0
  164. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/__main__.py +0 -0
  165. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/audit.py +0 -0
  166. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/background_tasks.py +0 -0
  167. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/config/__init__.py +0 -0
  168. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/__init__.py +0 -0
  169. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/skills/skill-creator/SKILL.md +0 -0
  170. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/skills/skill-installer/SKILL.md +0 -0
  171. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  172. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/Read.md +0 -0
  173. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/Search.md +0 -0
  174. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/Update.md +0 -0
  175. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/WebFetch.md +0 -0
  176. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/WebSearch.md +0 -0
  177. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/Write.md +0 -0
  178. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/__init__.py +0 -0
  179. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/shell.md +0 -0
  180. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/task_list.md +0 -0
  181. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/task_output.md +0 -0
  182. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/task_stop.md +0 -0
  183. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/test_shell.md +0 -0
  184. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/data/tools/todo_write.md +0 -0
  185. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/errors.py +0 -0
  186. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/input_suggestions.py +0 -0
  187. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/__init__.py +0 -0
  188. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/cache_context.py +0 -0
  189. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/compaction.py +0 -0
  190. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/context.py +0 -0
  191. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/events.py +0 -0
  192. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/model_capabilities.py +0 -0
  193. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/multimodal.py +0 -0
  194. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/provider.py +0 -0
  195. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/replay.py +0 -0
  196. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/llm/thinking.py +0 -0
  197. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/prompts/__init__.py +0 -0
  198. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/prompts/compact.py +0 -0
  199. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/prompts/init_agents.py +0 -0
  200. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/prompts/rules.py +0 -0
  201. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/prompts/runtime_context.py +0 -0
  202. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/prompts/system.py +0 -0
  203. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/prompts/tool_docs.py +0 -0
  204. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/session_cost.py +0 -0
  205. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/sessions/__init__.py +0 -0
  206. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/sessions/index.py +0 -0
  207. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/sessions/manager.py +0 -0
  208. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/sessions/store_helpers.py +0 -0
  209. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/skill_market.py +0 -0
  210. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/skills.py +0 -0
  211. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/subagents.py +0 -0
  212. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/todos.py +0 -0
  213. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/__init__.py +0 -0
  214. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/file_state.py +0 -0
  215. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/result.py +0 -0
  216. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/search.py +0 -0
  217. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/shell_output.py +0 -0
  218. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/tools/shell_utils.py +0 -0
  219. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/types/__init__.py +0 -0
  220. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/types/sdk.py +0 -0
  221. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/types/tool_payloads.py +0 -0
  222. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/classic/pickers}/audit_approval_picker.py +0 -0
  223. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/classic/pickers}/theme_picker.py +0 -0
  224. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern/render}/transcript.py +0 -0
  225. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern}/state.py +0 -0
  226. {deepy_cli-0.2.28/src/deepy/tui → deepy_cli-0.2.30/src/deepy/ui/modern}/theme.py +0 -0
  227. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/input}/ask_user_question.py +0 -0
  228. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/input}/file_mentions.py +0 -0
  229. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared}/model_picker.py +0 -0
  230. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/render}/exit_summary.py +0 -0
  231. {deepy_cli-0.2.28/src/deepy/ui → deepy_cli-0.2.30/src/deepy/ui/shared/render}/syntax.py +0 -0
  232. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/update_check.py +0 -0
  233. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/usage.py +0 -0
  234. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/utils/__init__.py +0 -0
  235. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/utils/debug_logger.py +0 -0
  236. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/utils/error_logger.py +0 -0
  237. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/src/deepy/utils/json.py +0 -0
  238. {deepy_cli-0.2.28 → deepy_cli-0.2.30}/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.28
3
+ Version: 0.2.30
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.2.28"
3
+ version = "0.2.30"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.2.28"
3
+ __version__ = "0.2.30"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -40,8 +40,8 @@ from .sessions import DeepySession, list_session_entries
40
40
  from .skills import discover_skills, find_skill, format_skills_for_terminal, read_skill_body
41
41
  from .status import build_status_report, format_status_report, status_report_to_dict
42
42
  from .usage import TokenUsage, format_usage_line, usage_from_run_result
43
- from .ui import run_interactive
44
- from .ui.styles import resolve_ui_palette
43
+ from .ui.classic import run_interactive
44
+ from .ui.shared.render.styles import resolve_ui_palette
45
45
  from .utils import json as json_utils
46
46
 
47
47
 
@@ -714,7 +714,7 @@ def _cmd_tui(args: argparse.Namespace) -> int:
714
714
  if not sys.stdin.isatty():
715
715
  print("Modern UI requires a TTY; use `deepy run` for non-interactive prompts.", file=sys.stderr)
716
716
  return 1
717
- from deepy.tui import run_tui
717
+ from deepy.ui.modern import run_tui
718
718
 
719
719
  return run_tui(_ensure_interactive_settings(args), project_root=Path.cwd())
720
720
 
@@ -724,7 +724,7 @@ def _cmd_interactive(args: argparse.Namespace) -> int:
724
724
  if settings.ui.interface == "modern":
725
725
  if not sys.stdin.isatty():
726
726
  raise RuntimeError("Modern UI requires a TTY.")
727
- from deepy.tui import run_tui
727
+ from deepy.ui.modern import run_tui
728
728
 
729
729
  return run_tui(settings, project_root=Path.cwd())
730
730
  return run_interactive(settings)
@@ -0,0 +1,463 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import tomllib
5
+ from dataclasses import asdict
6
+ from pathlib import Path
7
+ from typing import Any, Mapping
8
+
9
+ import tomli_w
10
+
11
+ from deepy.audit import AuditMode, DEFAULT_AUDIT_MODE, is_valid_audit_mode
12
+
13
+ from .providers import (
14
+ DEEPSEEK_REASONING_MODES,
15
+ DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES,
16
+ DEFAULT_COMPACT_TRIGGER_RATIO,
17
+ DEFAULT_CONTEXT_WINDOW_TOKENS,
18
+ DEFAULT_INPUT_SUGGESTIONS_ENABLED,
19
+ DEFAULT_MCP_CACHE_TOOLS_LIST,
20
+ DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS,
21
+ DEFAULT_MCP_CLIENT_SESSION_TIMEOUT_SECONDS,
22
+ DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS,
23
+ DEFAULT_MCP_ENABLED,
24
+ DEFAULT_PROVIDER,
25
+ DEFAULT_RESERVED_CONTEXT_TOKENS,
26
+ DEFAULT_UI_INTERFACE,
27
+ DEFAULT_UI_THEME,
28
+ DEFAULT_UI_VIEW_MODE,
29
+ DEFAULT_WEB_SEARCH_SEARXNG_URL,
30
+ REASONING_MODES,
31
+ SUPPORTED_DEEPSEEK_MODELS,
32
+ THINKING_MODES,
33
+ UI_INTERFACES,
34
+ UI_INTERFACE_OPTIONS,
35
+ UI_SETUP_OPTIONS,
36
+ UI_THEME_OPTIONS,
37
+ UI_THEMES,
38
+ UI_VIEW_MODES,
39
+ default_config_path,
40
+ is_supported_model_for_provider,
41
+ is_supported_provider,
42
+ is_valid_config_model_for_provider,
43
+ is_valid_thinking_mode_for_provider,
44
+ mask_secret,
45
+ provider_info_for,
46
+ reasoning_effort_for_mode,
47
+ thinking_enabled_for_mode,
48
+ )
49
+ from .schema import (
50
+ ModelConfig,
51
+ Settings,
52
+ )
53
+
54
+
55
+ def load_settings(
56
+ path: str | os.PathLike[str] | None = None,
57
+ *,
58
+ env: Mapping[str, str] | None = None,
59
+ ) -> Settings:
60
+ config_path = Path(path).expanduser() if path is not None else default_config_path()
61
+ if config_path.suffix == ".json":
62
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
63
+ env = env or os.environ
64
+ if not config_path.exists():
65
+ return Settings.from_mapping({}, path=config_path, env=env)
66
+
67
+ with config_path.open("rb") as fh:
68
+ raw = tomllib.load(fh)
69
+ return Settings.from_mapping(raw, path=config_path, env=env)
70
+
71
+
72
+ def settings_to_toml_dict(settings: Settings, *, reveal_secret: bool = False) -> dict[str, Any]:
73
+ data = _drop_empty(asdict(settings))
74
+ data.pop("path", None)
75
+ if "ui" in data:
76
+ data["ui"].pop("theme_configured", None)
77
+ if "audit" in data:
78
+ data["audit"].pop("invalid_mode", None)
79
+ if "mode" in data["audit"] and isinstance(settings.audit.mode, AuditMode):
80
+ data["audit"]["mode"] = settings.audit.mode.value
81
+ if "mcp_safe_tools" in data["audit"]:
82
+ data["audit"]["mcp_safe_tools"] = [
83
+ {"server": item.server, "tool": item.tool} for item in settings.audit.mcp_safe_tools
84
+ ]
85
+ api_key = settings.model.api_key
86
+ if api_key:
87
+ data["model"]["api_key"] = api_key if reveal_secret else mask_secret(api_key)
88
+ data["model"]["thinking"] = settings.model.thinking_enabled
89
+ return _drop_empty(data)
90
+
91
+
92
+ def is_valid_ui_theme(value: str) -> bool:
93
+ return value in UI_THEMES
94
+
95
+
96
+ def is_valid_ui_interface(value: str) -> bool:
97
+ return value in UI_INTERFACES
98
+
99
+
100
+ def is_valid_ui_view_mode(value: str) -> bool:
101
+ return value in UI_VIEW_MODES
102
+
103
+
104
+ def is_valid_config_audit_mode(value: str) -> bool:
105
+ return is_valid_audit_mode(value)
106
+
107
+
108
+ def is_supported_deepseek_model(value: str) -> bool:
109
+ return value in SUPPORTED_DEEPSEEK_MODELS
110
+
111
+
112
+ def is_supported_model(value: str, provider: str) -> bool:
113
+ return is_supported_model_for_provider(value, provider)
114
+
115
+
116
+ def is_valid_reasoning_mode(value: str) -> bool:
117
+ return value in REASONING_MODES
118
+
119
+
120
+ def is_valid_thinking_mode(value: str) -> bool:
121
+ return value in THINKING_MODES
122
+
123
+
124
+ def ui_theme_number(theme: str) -> str:
125
+ for number, value in UI_THEME_OPTIONS:
126
+ if value == theme:
127
+ return number
128
+ return "1"
129
+
130
+
131
+ def ui_theme_from_selection(value: str, *, default: str = DEFAULT_UI_THEME) -> str:
132
+ normalized = value.strip().lower()
133
+ if not normalized:
134
+ return default if is_valid_ui_theme(default) else DEFAULT_UI_THEME
135
+ if normalized in UI_THEMES:
136
+ return normalized
137
+ by_number = dict(UI_THEME_OPTIONS)
138
+ selected = by_number.get(normalized)
139
+ if selected is not None:
140
+ return selected
141
+ return default if is_valid_ui_theme(default) else DEFAULT_UI_THEME
142
+
143
+
144
+ def ui_interface_number(interface: str) -> str:
145
+ for number, value in UI_INTERFACE_OPTIONS:
146
+ if value == interface:
147
+ return number
148
+ return "1"
149
+
150
+
151
+ def ui_interface_from_selection(value: str, *, default: str = DEFAULT_UI_INTERFACE) -> str:
152
+ normalized = value.strip().lower()
153
+ if not normalized:
154
+ return default if is_valid_ui_interface(default) else DEFAULT_UI_INTERFACE
155
+ if normalized in UI_INTERFACES:
156
+ return normalized
157
+ by_number = dict(UI_INTERFACE_OPTIONS)
158
+ selected = by_number.get(normalized)
159
+ if selected is not None:
160
+ return selected
161
+ return default if is_valid_ui_interface(default) else DEFAULT_UI_INTERFACE
162
+
163
+
164
+ def ui_setup_number(interface: str, theme: str) -> str:
165
+ for number, option_interface, option_theme in UI_SETUP_OPTIONS:
166
+ if option_interface == interface and option_theme == theme:
167
+ return number
168
+ return "1"
169
+
170
+
171
+ def ui_setup_from_selection(
172
+ value: str,
173
+ *,
174
+ default_interface: str = DEFAULT_UI_INTERFACE,
175
+ default_theme: str = DEFAULT_UI_THEME,
176
+ ) -> tuple[str, str]:
177
+ normalized = value.strip().lower()
178
+ fallback = (
179
+ default_interface if is_valid_ui_interface(default_interface) else DEFAULT_UI_INTERFACE,
180
+ default_theme if is_valid_ui_theme(default_theme) else DEFAULT_UI_THEME,
181
+ )
182
+ if not normalized:
183
+ return fallback
184
+ for number, option_interface, option_theme in UI_SETUP_OPTIONS:
185
+ if normalized in {number, f"{option_interface}-{option_theme}", f"{option_interface} {option_theme}"}:
186
+ return option_interface, option_theme
187
+ return fallback
188
+
189
+
190
+ def write_config(
191
+ config_path: Path,
192
+ *,
193
+ api_key: str,
194
+ provider: str = DEFAULT_PROVIDER,
195
+ model: str,
196
+ base_url: str | None = None,
197
+ theme: str,
198
+ interface: str = DEFAULT_UI_INTERFACE,
199
+ thinking_mode: str | None = None,
200
+ ) -> None:
201
+ if not is_valid_ui_theme(theme):
202
+ raise ValueError("UI theme must be one of: dark, light.")
203
+ if not is_valid_ui_interface(interface):
204
+ raise ValueError("UI interface must be one of: classic, modern.")
205
+ if not is_supported_provider(provider):
206
+ raise ValueError("Provider must be one of: deepseek, openrouter, xiaomi.")
207
+ provider_info = provider_info_for(provider)
208
+ if not is_valid_config_model_for_provider(model, provider):
209
+ raise ValueError(
210
+ "Model must be one of: " + ", ".join(model_info.name for model_info in provider_info.models)
211
+ )
212
+ mode = thinking_mode or provider_info.default_thinking_mode
213
+ if not is_valid_thinking_mode_for_provider(mode, provider):
214
+ raise ValueError(
215
+ "Thinking mode must be one of: " + ", ".join(provider_info.thinking_modes)
216
+ )
217
+ path = config_path.expanduser()
218
+ if path.suffix == ".json":
219
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
220
+ resolved_base_url = base_url or provider_info.default_base_url
221
+ payload = {
222
+ "model": {
223
+ "provider": provider,
224
+ "name": model,
225
+ "base_url": resolved_base_url,
226
+ "api_key": api_key,
227
+ "thinking": thinking_enabled_for_mode(mode, provider),
228
+ "reasoning_effort": reasoning_effort_for_mode(mode, provider),
229
+ },
230
+ "audit": {
231
+ "mode": DEFAULT_AUDIT_MODE.value,
232
+ "mcp_safe_tools": [],
233
+ },
234
+ "context": {
235
+ "window_tokens": DEFAULT_CONTEXT_WINDOW_TOKENS,
236
+ "compact_trigger_ratio": DEFAULT_COMPACT_TRIGGER_RATIO,
237
+ "reserved_context_tokens": DEFAULT_RESERVED_CONTEXT_TOKENS,
238
+ "compact_preserve_recent_messages": DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES,
239
+ },
240
+ "logging": {
241
+ "debug": False,
242
+ },
243
+ "notify": {
244
+ "enabled": False,
245
+ "command": "",
246
+ },
247
+ "tools": {
248
+ "web_search": {
249
+ "searxng_url": DEFAULT_WEB_SEARCH_SEARXNG_URL,
250
+ },
251
+ },
252
+ "mcp": {
253
+ "enabled": DEFAULT_MCP_ENABLED,
254
+ "connect_timeout_seconds": DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS,
255
+ "cleanup_timeout_seconds": DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS,
256
+ "client_session_timeout_seconds": DEFAULT_MCP_CLIENT_SESSION_TIMEOUT_SECONDS,
257
+ "cache_tools_list": DEFAULT_MCP_CACHE_TOOLS_LIST,
258
+ "allow_project_config": False,
259
+ "prefer_mcp_web_search": True,
260
+ "web_search": {
261
+ "prefer_mcp": True,
262
+ "preferred_server": "",
263
+ "preferred_tools": [],
264
+ "fallback_to_builtin": True,
265
+ },
266
+ },
267
+ "ui": {
268
+ "interface": interface,
269
+ "theme": theme,
270
+ "input_suggestions_enabled": DEFAULT_INPUT_SUGGESTIONS_ENABLED,
271
+ "view_mode": DEFAULT_UI_VIEW_MODE,
272
+ },
273
+ }
274
+ path.parent.mkdir(parents=True, exist_ok=True)
275
+ path.write_text(tomli_w.dumps(payload), encoding="utf-8")
276
+ os.chmod(path, 0o600)
277
+
278
+
279
+ def update_config_model_settings(
280
+ config_path: Path,
281
+ *,
282
+ provider: str | None = None,
283
+ model: str | None = None,
284
+ base_url: str | None = None,
285
+ reasoning_mode: str | None = None,
286
+ ) -> None:
287
+ path = config_path.expanduser()
288
+ if path.suffix == ".json":
289
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
290
+ raw = _read_toml_mapping(path)
291
+ model_section = raw.get("model")
292
+ model_map = dict(model_section) if isinstance(model_section, Mapping) else {}
293
+ current = ModelConfig.from_mapping(model_map)
294
+ active_provider = provider or current.provider
295
+ if provider is not None and not is_supported_provider(provider):
296
+ raise ValueError("Provider must be one of: deepseek, openrouter, xiaomi.")
297
+ provider_info = provider_info_for(active_provider)
298
+ active_model = model or current.name
299
+ if model is None and provider is not None and not is_supported_model_for_provider(active_model, active_provider):
300
+ active_model = provider_info.default_model
301
+ # `/model` updates intentionally stay within Deepy's curated provider model catalog.
302
+ if not is_supported_model_for_provider(active_model, active_provider):
303
+ raise ValueError(
304
+ "Model must be one of: " + ", ".join(model_info.name for model_info in provider_info.models)
305
+ )
306
+ active_mode = reasoning_mode or (
307
+ current.reasoning_mode
308
+ if is_valid_thinking_mode_for_provider(current.reasoning_mode, active_provider)
309
+ else provider_info.default_thinking_mode
310
+ )
311
+ if not is_valid_thinking_mode_for_provider(active_mode, active_provider):
312
+ if provider_info.thinking_modes == DEEPSEEK_REASONING_MODES:
313
+ raise ValueError("Reasoning mode must be one of: none, high, max.")
314
+ raise ValueError(
315
+ "Thinking mode must be one of: " + ", ".join(provider_info.thinking_modes)
316
+ )
317
+ if provider is not None:
318
+ model_map["provider"] = active_provider
319
+ if base_url is None:
320
+ model_map["base_url"] = provider_info.default_base_url
321
+ if base_url is not None:
322
+ model_map["base_url"] = base_url
323
+ if model is not None:
324
+ model_map["name"] = active_model
325
+ elif provider is not None:
326
+ model_map["name"] = active_model
327
+ if reasoning_mode is not None or provider is not None:
328
+ model_map["thinking"] = thinking_enabled_for_mode(active_mode, active_provider)
329
+ model_map["reasoning_effort"] = reasoning_effort_for_mode(active_mode, active_provider)
330
+ raw["model"] = model_map
331
+ _write_private_toml(path, raw)
332
+
333
+
334
+ def update_config_theme(config_path: Path, theme: str) -> None:
335
+ if not is_valid_ui_theme(theme):
336
+ raise ValueError("UI theme must be one of: dark, light.")
337
+ path = config_path.expanduser()
338
+ if path.suffix == ".json":
339
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
340
+ raw = _read_toml_mapping(path)
341
+ ui = raw.get("ui")
342
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
343
+ ui_map["theme"] = theme
344
+ ui_map.pop("textual_theme", None)
345
+ raw["ui"] = ui_map
346
+ _write_private_toml(path, raw)
347
+
348
+
349
+ def update_config_ui_interface(config_path: Path, interface: str) -> None:
350
+ if not is_valid_ui_interface(interface):
351
+ raise ValueError("UI interface must be one of: classic, modern.")
352
+ path = config_path.expanduser()
353
+ if path.suffix == ".json":
354
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
355
+ raw = _read_toml_mapping(path)
356
+ ui = raw.get("ui")
357
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
358
+ ui_map["interface"] = interface
359
+ raw["ui"] = ui_map
360
+ _write_private_toml(path, raw)
361
+
362
+
363
+ def update_config_ui_choice(config_path: Path, *, interface: str, theme: str) -> None:
364
+ if not is_valid_ui_interface(interface):
365
+ raise ValueError("UI interface must be one of: classic, modern.")
366
+ if not is_valid_ui_theme(theme):
367
+ raise ValueError("UI theme must be one of: dark, light.")
368
+ path = config_path.expanduser()
369
+ if path.suffix == ".json":
370
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
371
+ raw = _read_toml_mapping(path)
372
+ ui = raw.get("ui")
373
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
374
+ ui_map["interface"] = interface
375
+ ui_map["theme"] = theme
376
+ ui_map.pop("textual_theme", None)
377
+ raw["ui"] = ui_map
378
+ _write_private_toml(path, raw)
379
+
380
+
381
+ def update_config_textual_theme(config_path: Path, textual_theme: str) -> None:
382
+ theme = textual_theme.strip()
383
+ if not theme:
384
+ raise ValueError("Textual theme must not be empty.")
385
+ path = config_path.expanduser()
386
+ if path.suffix == ".json":
387
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
388
+ raw = _read_toml_mapping(path)
389
+ ui = raw.get("ui")
390
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
391
+ ui_map["textual_theme"] = theme
392
+ raw["ui"] = ui_map
393
+ _write_private_toml(path, raw)
394
+
395
+
396
+ def update_config_input_suggestions_enabled(config_path: Path, enabled: bool) -> None:
397
+ path = config_path.expanduser()
398
+ if path.suffix == ".json":
399
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
400
+ raw = _read_toml_mapping(path)
401
+ ui = raw.get("ui")
402
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
403
+ ui_map["input_suggestions_enabled"] = bool(enabled)
404
+ raw["ui"] = ui_map
405
+ _write_private_toml(path, raw)
406
+
407
+
408
+ def update_config_view_mode(config_path: Path, view_mode: str) -> None:
409
+ if not is_valid_ui_view_mode(view_mode):
410
+ raise ValueError("View mode must be one of: concise, full.")
411
+ path = config_path.expanduser()
412
+ if path.suffix == ".json":
413
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
414
+ raw = _read_toml_mapping(path)
415
+ ui = raw.get("ui")
416
+ ui_map = dict(ui) if isinstance(ui, Mapping) else {}
417
+ ui_map["view_mode"] = view_mode
418
+ raw["ui"] = ui_map
419
+ _write_private_toml(path, raw)
420
+
421
+
422
+ def update_config_audit_mode(config_path: Path, audit_mode: str) -> None:
423
+ if not is_valid_config_audit_mode(audit_mode):
424
+ raise ValueError("Audit mode must be one of: normal, auto, yolo.")
425
+ path = config_path.expanduser()
426
+ if path.suffix == ".json":
427
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
428
+ raw = _read_toml_mapping(path)
429
+ audit = raw.get("audit")
430
+ audit_map = dict(audit) if isinstance(audit, Mapping) else {}
431
+ audit_map["mode"] = audit_mode
432
+ raw["audit"] = audit_map
433
+ _write_private_toml(path, raw)
434
+
435
+
436
+ def _read_toml_mapping(path: Path) -> dict[str, Any]:
437
+ if not path.exists():
438
+ return {}
439
+ with path.open("rb") as fh:
440
+ loaded = tomllib.load(fh)
441
+ return dict(loaded)
442
+
443
+
444
+ def _write_private_toml(path: Path, raw: Mapping[str, Any]) -> None:
445
+ path.parent.mkdir(parents=True, exist_ok=True)
446
+ path.write_text(tomli_w.dumps(raw), encoding="utf-8")
447
+ os.chmod(path, 0o600)
448
+
449
+
450
+ def _drop_empty(value: Any) -> Any:
451
+ if isinstance(value, dict):
452
+ result = {}
453
+ for key, item in value.items():
454
+ if item is None:
455
+ continue
456
+ cleaned = _drop_empty(item)
457
+ if cleaned == {}:
458
+ continue
459
+ result[key] = cleaned
460
+ return result
461
+ if isinstance(value, list):
462
+ return [_drop_empty(item) for item in value]
463
+ return value