cecli-dev 0.93.1__py3-none-any.whl

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 (366) hide show
  1. cecli/__init__.py +20 -0
  2. cecli/__main__.py +4 -0
  3. cecli/_version.py +34 -0
  4. cecli/args.py +1092 -0
  5. cecli/args_formatter.py +228 -0
  6. cecli/change_tracker.py +133 -0
  7. cecli/coders/__init__.py +38 -0
  8. cecli/coders/agent_coder.py +1872 -0
  9. cecli/coders/architect_coder.py +63 -0
  10. cecli/coders/ask_coder.py +8 -0
  11. cecli/coders/base_coder.py +3993 -0
  12. cecli/coders/chat_chunks.py +116 -0
  13. cecli/coders/context_coder.py +52 -0
  14. cecli/coders/copypaste_coder.py +269 -0
  15. cecli/coders/editblock_coder.py +656 -0
  16. cecli/coders/editblock_fenced_coder.py +9 -0
  17. cecli/coders/editblock_func_coder.py +140 -0
  18. cecli/coders/editor_diff_fenced_coder.py +8 -0
  19. cecli/coders/editor_editblock_coder.py +8 -0
  20. cecli/coders/editor_whole_coder.py +8 -0
  21. cecli/coders/help_coder.py +15 -0
  22. cecli/coders/patch_coder.py +705 -0
  23. cecli/coders/search_replace.py +757 -0
  24. cecli/coders/shell.py +37 -0
  25. cecli/coders/single_wholefile_func_coder.py +101 -0
  26. cecli/coders/udiff_coder.py +428 -0
  27. cecli/coders/udiff_simple.py +12 -0
  28. cecli/coders/wholefile_coder.py +143 -0
  29. cecli/coders/wholefile_func_coder.py +133 -0
  30. cecli/commands/__init__.py +192 -0
  31. cecli/commands/add.py +226 -0
  32. cecli/commands/agent.py +51 -0
  33. cecli/commands/architect.py +46 -0
  34. cecli/commands/ask.py +44 -0
  35. cecli/commands/chat_mode.py +0 -0
  36. cecli/commands/clear.py +37 -0
  37. cecli/commands/code.py +46 -0
  38. cecli/commands/command_prefix.py +44 -0
  39. cecli/commands/commit.py +52 -0
  40. cecli/commands/context.py +47 -0
  41. cecli/commands/context_blocks.py +124 -0
  42. cecli/commands/context_management.py +51 -0
  43. cecli/commands/copy.py +62 -0
  44. cecli/commands/copy_context.py +81 -0
  45. cecli/commands/core.py +287 -0
  46. cecli/commands/diff.py +68 -0
  47. cecli/commands/drop.py +217 -0
  48. cecli/commands/editor.py +78 -0
  49. cecli/commands/exit.py +55 -0
  50. cecli/commands/git.py +57 -0
  51. cecli/commands/help.py +140 -0
  52. cecli/commands/history_search.py +40 -0
  53. cecli/commands/lint.py +109 -0
  54. cecli/commands/list_sessions.py +56 -0
  55. cecli/commands/load.py +85 -0
  56. cecli/commands/load_session.py +48 -0
  57. cecli/commands/load_skill.py +68 -0
  58. cecli/commands/ls.py +75 -0
  59. cecli/commands/map.py +37 -0
  60. cecli/commands/map_refresh.py +35 -0
  61. cecli/commands/model.py +118 -0
  62. cecli/commands/models.py +41 -0
  63. cecli/commands/multiline_mode.py +38 -0
  64. cecli/commands/paste.py +91 -0
  65. cecli/commands/quit.py +32 -0
  66. cecli/commands/read_only.py +267 -0
  67. cecli/commands/read_only_stub.py +270 -0
  68. cecli/commands/reasoning_effort.py +70 -0
  69. cecli/commands/remove_skill.py +68 -0
  70. cecli/commands/report.py +40 -0
  71. cecli/commands/reset.py +88 -0
  72. cecli/commands/run.py +99 -0
  73. cecli/commands/save.py +49 -0
  74. cecli/commands/save_session.py +43 -0
  75. cecli/commands/settings.py +69 -0
  76. cecli/commands/test.py +58 -0
  77. cecli/commands/think_tokens.py +74 -0
  78. cecli/commands/tokens.py +207 -0
  79. cecli/commands/undo.py +145 -0
  80. cecli/commands/utils/__init__.py +0 -0
  81. cecli/commands/utils/base_command.py +131 -0
  82. cecli/commands/utils/helpers.py +142 -0
  83. cecli/commands/utils/registry.py +53 -0
  84. cecli/commands/utils/save_load_manager.py +98 -0
  85. cecli/commands/voice.py +78 -0
  86. cecli/commands/weak_model.py +123 -0
  87. cecli/commands/web.py +87 -0
  88. cecli/deprecated_args.py +185 -0
  89. cecli/diffs.py +129 -0
  90. cecli/dump.py +29 -0
  91. cecli/editor.py +147 -0
  92. cecli/exceptions.py +115 -0
  93. cecli/format_settings.py +26 -0
  94. cecli/help.py +119 -0
  95. cecli/help_pats.py +19 -0
  96. cecli/helpers/__init__.py +9 -0
  97. cecli/helpers/copypaste.py +123 -0
  98. cecli/helpers/coroutines.py +8 -0
  99. cecli/helpers/file_searcher.py +142 -0
  100. cecli/helpers/model_providers.py +552 -0
  101. cecli/helpers/plugin_manager.py +81 -0
  102. cecli/helpers/profiler.py +162 -0
  103. cecli/helpers/requests.py +77 -0
  104. cecli/helpers/similarity.py +98 -0
  105. cecli/helpers/skills.py +577 -0
  106. cecli/history.py +186 -0
  107. cecli/io.py +1782 -0
  108. cecli/linter.py +304 -0
  109. cecli/llm.py +101 -0
  110. cecli/main.py +1280 -0
  111. cecli/mcp/__init__.py +154 -0
  112. cecli/mcp/oauth.py +250 -0
  113. cecli/mcp/server.py +278 -0
  114. cecli/mdstream.py +243 -0
  115. cecli/models.py +1255 -0
  116. cecli/onboarding.py +301 -0
  117. cecli/prompts/__init__.py +0 -0
  118. cecli/prompts/agent.yml +71 -0
  119. cecli/prompts/architect.yml +35 -0
  120. cecli/prompts/ask.yml +31 -0
  121. cecli/prompts/base.yml +99 -0
  122. cecli/prompts/context.yml +60 -0
  123. cecli/prompts/copypaste.yml +5 -0
  124. cecli/prompts/editblock.yml +143 -0
  125. cecli/prompts/editblock_fenced.yml +106 -0
  126. cecli/prompts/editblock_func.yml +25 -0
  127. cecli/prompts/editor_diff_fenced.yml +115 -0
  128. cecli/prompts/editor_editblock.yml +121 -0
  129. cecli/prompts/editor_whole.yml +46 -0
  130. cecli/prompts/help.yml +37 -0
  131. cecli/prompts/patch.yml +110 -0
  132. cecli/prompts/single_wholefile_func.yml +24 -0
  133. cecli/prompts/udiff.yml +106 -0
  134. cecli/prompts/udiff_simple.yml +13 -0
  135. cecli/prompts/utils/__init__.py +0 -0
  136. cecli/prompts/utils/prompt_registry.py +167 -0
  137. cecli/prompts/utils/system.py +56 -0
  138. cecli/prompts/wholefile.yml +50 -0
  139. cecli/prompts/wholefile_func.yml +24 -0
  140. cecli/queries/tree-sitter-language-pack/README.md +7 -0
  141. cecli/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  142. cecli/queries/tree-sitter-language-pack/c-tags.scm +12 -0
  143. cecli/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  144. cecli/queries/tree-sitter-language-pack/clojure-tags.scm +12 -0
  145. cecli/queries/tree-sitter-language-pack/commonlisp-tags.scm +127 -0
  146. cecli/queries/tree-sitter-language-pack/cpp-tags.scm +18 -0
  147. cecli/queries/tree-sitter-language-pack/csharp-tags.scm +32 -0
  148. cecli/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  149. cecli/queries/tree-sitter-language-pack/dart-tags.scm +97 -0
  150. cecli/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  151. cecli/queries/tree-sitter-language-pack/elixir-tags.scm +59 -0
  152. cecli/queries/tree-sitter-language-pack/elm-tags.scm +22 -0
  153. cecli/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  154. cecli/queries/tree-sitter-language-pack/go-tags.scm +49 -0
  155. cecli/queries/tree-sitter-language-pack/java-tags.scm +26 -0
  156. cecli/queries/tree-sitter-language-pack/javascript-tags.scm +96 -0
  157. cecli/queries/tree-sitter-language-pack/lua-tags.scm +39 -0
  158. cecli/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  159. cecli/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  160. cecli/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +101 -0
  161. cecli/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  162. cecli/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  163. cecli/queries/tree-sitter-language-pack/python-tags.scm +24 -0
  164. cecli/queries/tree-sitter-language-pack/r-tags.scm +27 -0
  165. cecli/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  166. cecli/queries/tree-sitter-language-pack/ruby-tags.scm +69 -0
  167. cecli/queries/tree-sitter-language-pack/rust-tags.scm +63 -0
  168. cecli/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  169. cecli/queries/tree-sitter-language-pack/swift-tags.scm +54 -0
  170. cecli/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  171. cecli/queries/tree-sitter-languages/README.md +24 -0
  172. cecli/queries/tree-sitter-languages/c-tags.scm +12 -0
  173. cecli/queries/tree-sitter-languages/c_sharp-tags.scm +52 -0
  174. cecli/queries/tree-sitter-languages/cpp-tags.scm +18 -0
  175. cecli/queries/tree-sitter-languages/dart-tags.scm +92 -0
  176. cecli/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  177. cecli/queries/tree-sitter-languages/elixir-tags.scm +59 -0
  178. cecli/queries/tree-sitter-languages/elm-tags.scm +22 -0
  179. cecli/queries/tree-sitter-languages/fortran-tags.scm +18 -0
  180. cecli/queries/tree-sitter-languages/go-tags.scm +36 -0
  181. cecli/queries/tree-sitter-languages/haskell-tags.scm +5 -0
  182. cecli/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  183. cecli/queries/tree-sitter-languages/java-tags.scm +26 -0
  184. cecli/queries/tree-sitter-languages/javascript-tags.scm +96 -0
  185. cecli/queries/tree-sitter-languages/julia-tags.scm +60 -0
  186. cecli/queries/tree-sitter-languages/kotlin-tags.scm +30 -0
  187. cecli/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  188. cecli/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  189. cecli/queries/tree-sitter-languages/ocaml_interface-tags.scm +104 -0
  190. cecli/queries/tree-sitter-languages/php-tags.scm +32 -0
  191. cecli/queries/tree-sitter-languages/python-tags.scm +22 -0
  192. cecli/queries/tree-sitter-languages/ql-tags.scm +26 -0
  193. cecli/queries/tree-sitter-languages/ruby-tags.scm +69 -0
  194. cecli/queries/tree-sitter-languages/rust-tags.scm +63 -0
  195. cecli/queries/tree-sitter-languages/scala-tags.scm +64 -0
  196. cecli/queries/tree-sitter-languages/typescript-tags.scm +44 -0
  197. cecli/queries/tree-sitter-languages/zig-tags.scm +20 -0
  198. cecli/reasoning_tags.py +82 -0
  199. cecli/repo.py +626 -0
  200. cecli/repomap.py +1368 -0
  201. cecli/report.py +260 -0
  202. cecli/resources/__init__.py +3 -0
  203. cecli/resources/model-metadata.json +25751 -0
  204. cecli/resources/model-settings.yml +2394 -0
  205. cecli/resources/providers.json +67 -0
  206. cecli/run_cmd.py +143 -0
  207. cecli/scrape.py +295 -0
  208. cecli/sendchat.py +250 -0
  209. cecli/sessions.py +281 -0
  210. cecli/special.py +203 -0
  211. cecli/tools/__init__.py +72 -0
  212. cecli/tools/command.py +103 -0
  213. cecli/tools/command_interactive.py +113 -0
  214. cecli/tools/context_manager.py +175 -0
  215. cecli/tools/delete_block.py +154 -0
  216. cecli/tools/delete_line.py +120 -0
  217. cecli/tools/delete_lines.py +144 -0
  218. cecli/tools/extract_lines.py +281 -0
  219. cecli/tools/finished.py +35 -0
  220. cecli/tools/git_branch.py +132 -0
  221. cecli/tools/git_diff.py +49 -0
  222. cecli/tools/git_log.py +43 -0
  223. cecli/tools/git_remote.py +39 -0
  224. cecli/tools/git_show.py +37 -0
  225. cecli/tools/git_status.py +32 -0
  226. cecli/tools/grep.py +242 -0
  227. cecli/tools/indent_lines.py +195 -0
  228. cecli/tools/insert_block.py +263 -0
  229. cecli/tools/list_changes.py +71 -0
  230. cecli/tools/load_skill.py +51 -0
  231. cecli/tools/ls.py +77 -0
  232. cecli/tools/remove_skill.py +51 -0
  233. cecli/tools/replace_all.py +113 -0
  234. cecli/tools/replace_line.py +135 -0
  235. cecli/tools/replace_lines.py +180 -0
  236. cecli/tools/replace_text.py +186 -0
  237. cecli/tools/show_numbered_context.py +137 -0
  238. cecli/tools/thinking.py +52 -0
  239. cecli/tools/undo_change.py +82 -0
  240. cecli/tools/update_todo_list.py +148 -0
  241. cecli/tools/utils/base_tool.py +64 -0
  242. cecli/tools/utils/helpers.py +359 -0
  243. cecli/tools/utils/output.py +119 -0
  244. cecli/tools/utils/registry.py +145 -0
  245. cecli/tools/view_files_matching.py +138 -0
  246. cecli/tools/view_files_with_symbol.py +117 -0
  247. cecli/tui/__init__.py +83 -0
  248. cecli/tui/app.py +971 -0
  249. cecli/tui/io.py +566 -0
  250. cecli/tui/styles.tcss +117 -0
  251. cecli/tui/widgets/__init__.py +19 -0
  252. cecli/tui/widgets/completion_bar.py +331 -0
  253. cecli/tui/widgets/file_list.py +76 -0
  254. cecli/tui/widgets/footer.py +165 -0
  255. cecli/tui/widgets/input_area.py +320 -0
  256. cecli/tui/widgets/key_hints.py +16 -0
  257. cecli/tui/widgets/output.py +354 -0
  258. cecli/tui/widgets/status_bar.py +279 -0
  259. cecli/tui/worker.py +160 -0
  260. cecli/urls.py +16 -0
  261. cecli/utils.py +499 -0
  262. cecli/versioncheck.py +90 -0
  263. cecli/voice.py +90 -0
  264. cecli/waiting.py +38 -0
  265. cecli/watch.py +316 -0
  266. cecli/watch_prompts.py +12 -0
  267. cecli/website/Gemfile +8 -0
  268. cecli/website/_includes/blame.md +162 -0
  269. cecli/website/_includes/get-started.md +22 -0
  270. cecli/website/_includes/help-tip.md +5 -0
  271. cecli/website/_includes/help.md +24 -0
  272. cecli/website/_includes/install.md +5 -0
  273. cecli/website/_includes/keys.md +4 -0
  274. cecli/website/_includes/model-warnings.md +67 -0
  275. cecli/website/_includes/multi-line.md +22 -0
  276. cecli/website/_includes/python-m-aider.md +5 -0
  277. cecli/website/_includes/recording.css +228 -0
  278. cecli/website/_includes/recording.md +34 -0
  279. cecli/website/_includes/replit-pipx.md +9 -0
  280. cecli/website/_includes/works-best.md +1 -0
  281. cecli/website/_sass/custom/custom.scss +103 -0
  282. cecli/website/docs/config/adv-model-settings.md +2498 -0
  283. cecli/website/docs/config/agent-mode.md +320 -0
  284. cecli/website/docs/config/aider_conf.md +548 -0
  285. cecli/website/docs/config/api-keys.md +90 -0
  286. cecli/website/docs/config/custom-commands.md +187 -0
  287. cecli/website/docs/config/dotenv.md +493 -0
  288. cecli/website/docs/config/editor.md +127 -0
  289. cecli/website/docs/config/mcp.md +210 -0
  290. cecli/website/docs/config/model-aliases.md +173 -0
  291. cecli/website/docs/config/options.md +890 -0
  292. cecli/website/docs/config/reasoning.md +210 -0
  293. cecli/website/docs/config/skills.md +172 -0
  294. cecli/website/docs/config/tui.md +126 -0
  295. cecli/website/docs/config.md +44 -0
  296. cecli/website/docs/faq.md +379 -0
  297. cecli/website/docs/git.md +76 -0
  298. cecli/website/docs/index.md +47 -0
  299. cecli/website/docs/install/codespaces.md +39 -0
  300. cecli/website/docs/install/docker.md +48 -0
  301. cecli/website/docs/install/optional.md +100 -0
  302. cecli/website/docs/install/replit.md +8 -0
  303. cecli/website/docs/install.md +115 -0
  304. cecli/website/docs/languages.md +264 -0
  305. cecli/website/docs/legal/contributor-agreement.md +111 -0
  306. cecli/website/docs/legal/privacy.md +104 -0
  307. cecli/website/docs/llms/anthropic.md +77 -0
  308. cecli/website/docs/llms/azure.md +48 -0
  309. cecli/website/docs/llms/bedrock.md +132 -0
  310. cecli/website/docs/llms/cohere.md +34 -0
  311. cecli/website/docs/llms/deepseek.md +32 -0
  312. cecli/website/docs/llms/gemini.md +49 -0
  313. cecli/website/docs/llms/github.md +111 -0
  314. cecli/website/docs/llms/groq.md +36 -0
  315. cecli/website/docs/llms/lm-studio.md +39 -0
  316. cecli/website/docs/llms/ollama.md +75 -0
  317. cecli/website/docs/llms/openai-compat.md +39 -0
  318. cecli/website/docs/llms/openai.md +58 -0
  319. cecli/website/docs/llms/openrouter.md +78 -0
  320. cecli/website/docs/llms/other.md +117 -0
  321. cecli/website/docs/llms/vertex.md +50 -0
  322. cecli/website/docs/llms/warnings.md +10 -0
  323. cecli/website/docs/llms/xai.md +53 -0
  324. cecli/website/docs/llms.md +54 -0
  325. cecli/website/docs/more/analytics.md +127 -0
  326. cecli/website/docs/more/edit-formats.md +116 -0
  327. cecli/website/docs/more/infinite-output.md +192 -0
  328. cecli/website/docs/more-info.md +8 -0
  329. cecli/website/docs/recordings/auto-accept-architect.md +31 -0
  330. cecli/website/docs/recordings/dont-drop-original-read-files.md +35 -0
  331. cecli/website/docs/recordings/index.md +21 -0
  332. cecli/website/docs/recordings/model-accepts-settings.md +69 -0
  333. cecli/website/docs/recordings/tree-sitter-language-pack.md +80 -0
  334. cecli/website/docs/repomap.md +112 -0
  335. cecli/website/docs/scripting.md +100 -0
  336. cecli/website/docs/sessions.md +213 -0
  337. cecli/website/docs/troubleshooting/aider-not-found.md +24 -0
  338. cecli/website/docs/troubleshooting/edit-errors.md +76 -0
  339. cecli/website/docs/troubleshooting/imports.md +62 -0
  340. cecli/website/docs/troubleshooting/models-and-keys.md +54 -0
  341. cecli/website/docs/troubleshooting/support.md +79 -0
  342. cecli/website/docs/troubleshooting/token-limits.md +96 -0
  343. cecli/website/docs/troubleshooting/warnings.md +12 -0
  344. cecli/website/docs/troubleshooting.md +11 -0
  345. cecli/website/docs/usage/browser.md +57 -0
  346. cecli/website/docs/usage/caching.md +49 -0
  347. cecli/website/docs/usage/commands.md +133 -0
  348. cecli/website/docs/usage/conventions.md +119 -0
  349. cecli/website/docs/usage/copypaste.md +136 -0
  350. cecli/website/docs/usage/images-urls.md +48 -0
  351. cecli/website/docs/usage/lint-test.md +118 -0
  352. cecli/website/docs/usage/modes.md +211 -0
  353. cecli/website/docs/usage/not-code.md +179 -0
  354. cecli/website/docs/usage/notifications.md +87 -0
  355. cecli/website/docs/usage/tips.md +79 -0
  356. cecli/website/docs/usage/tutorials.md +30 -0
  357. cecli/website/docs/usage/voice.md +121 -0
  358. cecli/website/docs/usage/watch.md +294 -0
  359. cecli/website/docs/usage.md +102 -0
  360. cecli/website/share/index.md +101 -0
  361. cecli_dev-0.93.1.dist-info/METADATA +549 -0
  362. cecli_dev-0.93.1.dist-info/RECORD +366 -0
  363. cecli_dev-0.93.1.dist-info/WHEEL +5 -0
  364. cecli_dev-0.93.1.dist-info/entry_points.txt +4 -0
  365. cecli_dev-0.93.1.dist-info/licenses/LICENSE.txt +202 -0
  366. cecli_dev-0.93.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,143 @@
1
+ from pathlib import Path
2
+
3
+ from cecli import diffs
4
+
5
+ from ..dump import dump # noqa: F401
6
+ from .base_coder import Coder
7
+
8
+
9
+ class WholeFileCoder(Coder):
10
+ """A coder that operates on entire files for code modifications."""
11
+
12
+ edit_format = "whole"
13
+ prompt_format = "wholefile"
14
+
15
+ def render_incremental_response(self, final):
16
+ try:
17
+ return self.get_edits(mode="diff")
18
+ except ValueError:
19
+ return self.get_multi_response_content_in_progress()
20
+
21
+ def get_edits(self, mode="update"):
22
+ content = self.get_multi_response_content_in_progress()
23
+
24
+ chat_files = self.get_inchat_relative_files()
25
+
26
+ output = []
27
+ lines = content.splitlines(keepends=True)
28
+
29
+ edits = []
30
+
31
+ saw_fname = None
32
+ fname = None
33
+ fname_source = None
34
+ new_lines = []
35
+ for i, line in enumerate(lines):
36
+ if line.startswith(self.fence[0]) or line.startswith(self.fence[1]):
37
+ if fname is not None:
38
+ # ending an existing block
39
+ saw_fname = None
40
+
41
+ full_path = self.abs_root_path(fname)
42
+
43
+ if mode == "diff":
44
+ output += self.do_live_diff(full_path, new_lines, True)
45
+ else:
46
+ edits.append((fname, fname_source, new_lines))
47
+
48
+ fname = None
49
+ fname_source = None
50
+ new_lines = []
51
+ continue
52
+
53
+ # fname==None ... starting a new block
54
+ if i > 0:
55
+ fname_source = "block"
56
+ fname = lines[i - 1].strip()
57
+ fname = fname.strip("*") # handle **filename.py**
58
+ fname = fname.rstrip(":")
59
+ fname = fname.strip("`")
60
+ fname = fname.lstrip("#")
61
+ fname = fname.strip()
62
+
63
+ # Issue #1232
64
+ if len(fname) > 250:
65
+ fname = ""
66
+
67
+ # Did gpt prepend a bogus dir? It especially likes to
68
+ # include the path/to prefix from the one-shot example in
69
+ # the prompt.
70
+ if fname and fname not in chat_files and Path(fname).name in chat_files:
71
+ fname = Path(fname).name
72
+ if not fname: # blank line? or ``` was on first line i==0
73
+ if saw_fname:
74
+ fname = saw_fname
75
+ fname_source = "saw"
76
+ elif len(chat_files) == 1:
77
+ fname = chat_files[0]
78
+ fname_source = "chat"
79
+ else:
80
+ # TODO: sense which file it is by diff size
81
+ raise ValueError(
82
+ f"No filename provided before {self.fence[0]} in file listing"
83
+ )
84
+
85
+ elif fname is not None:
86
+ new_lines.append(line)
87
+ else:
88
+ for word in line.strip().split():
89
+ word = word.rstrip(".:,;!")
90
+ for chat_file in chat_files:
91
+ quoted_chat_file = f"`{chat_file}`"
92
+ if word == quoted_chat_file:
93
+ saw_fname = chat_file
94
+
95
+ output.append(line)
96
+
97
+ if mode == "diff":
98
+ if fname is not None:
99
+ # ending an existing block
100
+ full_path = (Path(self.root) / fname).absolute()
101
+ output += self.do_live_diff(full_path, new_lines, False)
102
+ return "\n".join(output)
103
+
104
+ if fname:
105
+ edits.append((fname, fname_source, new_lines))
106
+
107
+ seen = set()
108
+ refined_edits = []
109
+ # process from most reliable filename, to least reliable
110
+ for source in ("block", "saw", "chat"):
111
+ for fname, fname_source, new_lines in edits:
112
+ if fname_source != source:
113
+ continue
114
+ # if a higher priority source already edited the file, skip
115
+ if fname in seen:
116
+ continue
117
+
118
+ seen.add(fname)
119
+ refined_edits.append((fname, fname_source, new_lines))
120
+
121
+ return refined_edits
122
+
123
+ def apply_edits(self, edits):
124
+ for path, fname_source, new_lines in edits:
125
+ full_path = self.abs_root_path(path)
126
+ new_lines = "".join(new_lines)
127
+ self.io.write_text(full_path, new_lines)
128
+
129
+ def do_live_diff(self, full_path, new_lines, final):
130
+ if Path(full_path).exists():
131
+ orig_lines = self.io.read_text(full_path)
132
+ if orig_lines is not None:
133
+ orig_lines = orig_lines.splitlines(keepends=True)
134
+
135
+ show_diff = diffs.diff_partial_update(
136
+ orig_lines,
137
+ new_lines,
138
+ final=final,
139
+ ).splitlines()
140
+ return show_diff
141
+
142
+ output = ["```"] + new_lines + ["```"]
143
+ return output
@@ -0,0 +1,133 @@
1
+ from cecli import diffs
2
+
3
+ from ..dump import dump # noqa: F401
4
+ from .base_coder import Coder
5
+
6
+
7
+ class WholeFileFunctionCoder(Coder):
8
+ prompt_format = "wholefile_func"
9
+ functions = [
10
+ dict(
11
+ name="write_file",
12
+ description="create or update one or more files",
13
+ parameters=dict(
14
+ type="object",
15
+ required=["explanation", "files"],
16
+ properties=dict(
17
+ explanation=dict(
18
+ type="string",
19
+ description=(
20
+ "Step by step plan for the changes to be made to the code (future"
21
+ " tense, markdown format)"
22
+ ),
23
+ ),
24
+ files=dict(
25
+ type="array",
26
+ items=dict(
27
+ type="object",
28
+ required=["path", "content"],
29
+ properties=dict(
30
+ path=dict(
31
+ type="string",
32
+ description="Path of file to write",
33
+ ),
34
+ content=dict(
35
+ type="string",
36
+ description="Content to write to the file",
37
+ ),
38
+ ),
39
+ ),
40
+ ),
41
+ ),
42
+ ),
43
+ ),
44
+ ]
45
+
46
+ def __init__(self, *args, **kwargs):
47
+ raise RuntimeError("Deprecated, needs to be refactored to support get_edits/apply_edits")
48
+
49
+ super().__init__(*args, **kwargs)
50
+
51
+ def add_assistant_reply_to_cur_messages(self, edited):
52
+ if edited:
53
+ self.cur_messages += [
54
+ dict(role="assistant", content=self.gpt_prompts.redacted_edit_message)
55
+ ]
56
+ else:
57
+ self.cur_messages += [dict(role="assistant", content=self.partial_response_content)]
58
+
59
+ def render_incremental_response(self, final=False):
60
+ if self.partial_response_content:
61
+ return self.partial_response_content
62
+
63
+ args = self.parse_partial_args()
64
+
65
+ if not args:
66
+ return
67
+
68
+ explanation = args.get("explanation")
69
+ files = args.get("files", [])
70
+
71
+ res = ""
72
+ if explanation:
73
+ res += f"{explanation}\n\n"
74
+
75
+ for i, file_upd in enumerate(files):
76
+ path = file_upd.get("path")
77
+ if not path:
78
+ continue
79
+ content = file_upd.get("content")
80
+ if not content:
81
+ continue
82
+
83
+ this_final = (i < len(files) - 1) or final
84
+ res += self.live_diffs(path, content, this_final)
85
+
86
+ return res
87
+
88
+ def live_diffs(self, fname, content, final):
89
+ lines = content.splitlines(keepends=True)
90
+
91
+ # ending an existing block
92
+ full_path = self.abs_root_path(fname)
93
+
94
+ content = self.io.read_text(full_path)
95
+ if content is None:
96
+ orig_lines = []
97
+ else:
98
+ orig_lines = content.splitlines()
99
+
100
+ show_diff = diffs.diff_partial_update(
101
+ orig_lines,
102
+ lines,
103
+ final,
104
+ fname=fname,
105
+ ).splitlines()
106
+
107
+ return "\n".join(show_diff)
108
+
109
+ async def _update_files(self):
110
+ name = self.partial_response_function_call.get("name")
111
+ if name and name != "write_file":
112
+ raise ValueError(f'Unknown function_call name="{name}", use name="write_file"')
113
+
114
+ args = self.parse_partial_args()
115
+ if not args:
116
+ return
117
+
118
+ files = args.get("files", [])
119
+
120
+ edited = set()
121
+ for file_upd in files:
122
+ path = file_upd.get("path")
123
+ if not path:
124
+ raise ValueError(f"Missing path parameter: {file_upd}")
125
+
126
+ content = file_upd.get("content")
127
+ if not content:
128
+ raise ValueError(f"Missing content parameter: {file_upd}")
129
+
130
+ if await self.allowed_to_edit(path, content):
131
+ edited.add(path)
132
+
133
+ return edited
@@ -0,0 +1,192 @@
1
+ """
2
+ Command system for cecli.
3
+
4
+ This package contains individual command implementations that follow the
5
+ BaseCommand pattern for modular, testable command execution.
6
+ """
7
+
8
+ from .add import AddCommand
9
+ from .agent import AgentCommand
10
+ from .architect import ArchitectCommand
11
+ from .ask import AskCommand
12
+ from .clear import ClearCommand
13
+ from .code import CodeCommand
14
+ from .command_prefix import CommandPrefixCommand
15
+ from .commit import CommitCommand
16
+ from .context import ContextCommand
17
+ from .context_blocks import ContextBlocksCommand
18
+ from .context_management import ContextManagementCommand
19
+ from .copy import CopyCommand
20
+ from .copy_context import CopyContextCommand
21
+ from .core import Commands, SwitchCoderSignal
22
+ from .diff import DiffCommand
23
+
24
+ # Import and register commands
25
+ from .drop import DropCommand
26
+ from .editor import EditCommand, EditorCommand
27
+ from .exit import ExitCommand
28
+ from .git import GitCommand
29
+ from .help import HelpCommand
30
+ from .history_search import HistorySearchCommand
31
+ from .lint import LintCommand
32
+ from .list_sessions import ListSessionsCommand
33
+ from .load import LoadCommand
34
+ from .load_session import LoadSessionCommand
35
+ from .load_skill import LoadSkillCommand
36
+ from .ls import LsCommand
37
+ from .map import MapCommand
38
+ from .map_refresh import MapRefreshCommand
39
+ from .model import ModelCommand
40
+ from .models import ModelsCommand
41
+ from .multiline_mode import MultilineModeCommand
42
+ from .paste import PasteCommand
43
+ from .quit import QuitCommand
44
+ from .read_only import ReadOnlyCommand
45
+ from .read_only_stub import ReadOnlyStubCommand
46
+ from .reasoning_effort import ReasoningEffortCommand
47
+ from .remove_skill import RemoveSkillCommand
48
+ from .report import ReportCommand
49
+ from .reset import ResetCommand
50
+ from .run import RunCommand
51
+ from .save import SaveCommand
52
+ from .save_session import SaveSessionCommand
53
+ from .settings import SettingsCommand
54
+ from .test import TestCommand
55
+ from .think_tokens import ThinkTokensCommand
56
+ from .tokens import TokensCommand
57
+ from .undo import UndoCommand
58
+ from .utils.base_command import BaseCommand
59
+ from .utils.helpers import (
60
+ CommandError,
61
+ expand_subdir,
62
+ format_command_result,
63
+ get_available_files,
64
+ glob_filtered_to_repo,
65
+ parse_quoted_filenames,
66
+ quote_filename,
67
+ validate_file_access,
68
+ )
69
+ from .utils.registry import CommandRegistry
70
+ from .voice import VoiceCommand
71
+ from .weak_model import WeakModelCommand
72
+ from .web import WebCommand
73
+
74
+ # Register commands
75
+ CommandRegistry.register(DropCommand)
76
+ CommandRegistry.register(ClearCommand)
77
+ CommandRegistry.register(LsCommand)
78
+ CommandRegistry.register(DiffCommand)
79
+ CommandRegistry.register(ResetCommand)
80
+ CommandRegistry.register(CopyCommand)
81
+ CommandRegistry.register(PasteCommand)
82
+ CommandRegistry.register(SettingsCommand)
83
+ CommandRegistry.register(ReportCommand)
84
+ CommandRegistry.register(TokensCommand)
85
+ CommandRegistry.register(UndoCommand)
86
+ CommandRegistry.register(GitCommand)
87
+ CommandRegistry.register(RunCommand)
88
+ CommandRegistry.register(HelpCommand)
89
+ CommandRegistry.register(CommitCommand)
90
+ CommandRegistry.register(ModelsCommand)
91
+ CommandRegistry.register(ExitCommand)
92
+ CommandRegistry.register(QuitCommand)
93
+ CommandRegistry.register(VoiceCommand)
94
+ CommandRegistry.register(MapCommand)
95
+ CommandRegistry.register(MapRefreshCommand)
96
+ CommandRegistry.register(MultilineModeCommand)
97
+ CommandRegistry.register(EditorCommand)
98
+ CommandRegistry.register(EditCommand)
99
+ CommandRegistry.register(HistorySearchCommand)
100
+ CommandRegistry.register(ThinkTokensCommand)
101
+ CommandRegistry.register(LoadCommand)
102
+ CommandRegistry.register(SaveCommand)
103
+ CommandRegistry.register(ReasoningEffortCommand)
104
+ CommandRegistry.register(SaveSessionCommand)
105
+ CommandRegistry.register(ListSessionsCommand)
106
+ CommandRegistry.register(LoadSessionCommand)
107
+ CommandRegistry.register(ReadOnlyCommand)
108
+ CommandRegistry.register(ReadOnlyStubCommand)
109
+ CommandRegistry.register(AddCommand)
110
+ CommandRegistry.register(ModelCommand)
111
+ CommandRegistry.register(WeakModelCommand)
112
+ CommandRegistry.register(WebCommand)
113
+ CommandRegistry.register(LintCommand)
114
+ CommandRegistry.register(TestCommand)
115
+ CommandRegistry.register(ContextManagementCommand)
116
+ CommandRegistry.register(ContextBlocksCommand)
117
+ CommandRegistry.register(AskCommand)
118
+ CommandRegistry.register(CodeCommand)
119
+ CommandRegistry.register(ArchitectCommand)
120
+ CommandRegistry.register(ContextCommand)
121
+ CommandRegistry.register(AgentCommand)
122
+ CommandRegistry.register(CopyContextCommand)
123
+ CommandRegistry.register(CommandPrefixCommand)
124
+ CommandRegistry.register(LoadSkillCommand)
125
+ CommandRegistry.register(RemoveSkillCommand)
126
+
127
+
128
+ __all__ = [
129
+ "BaseCommand",
130
+ "CommandRegistry",
131
+ "CommandError",
132
+ "quote_filename",
133
+ "parse_quoted_filenames",
134
+ "glob_filtered_to_repo",
135
+ "validate_file_access",
136
+ "format_command_result",
137
+ "get_available_files",
138
+ "expand_subdir",
139
+ "DropCommand",
140
+ "ClearCommand",
141
+ "LsCommand",
142
+ "DiffCommand",
143
+ "ResetCommand",
144
+ "CopyCommand",
145
+ "PasteCommand",
146
+ "SettingsCommand",
147
+ "ReportCommand",
148
+ "TokensCommand",
149
+ "UndoCommand",
150
+ "GitCommand",
151
+ "RunCommand",
152
+ "HelpCommand",
153
+ "CommitCommand",
154
+ "ModelsCommand",
155
+ "ExitCommand",
156
+ "QuitCommand",
157
+ "VoiceCommand",
158
+ "MapCommand",
159
+ "MapRefreshCommand",
160
+ "MultilineModeCommand",
161
+ "EditorCommand",
162
+ "EditCommand",
163
+ "HistorySearchCommand",
164
+ "ThinkTokensCommand",
165
+ "LoadCommand",
166
+ "SaveCommand",
167
+ "ReasoningEffortCommand",
168
+ "SaveSessionCommand",
169
+ "ListSessionsCommand",
170
+ "LoadSessionCommand",
171
+ "ReadOnlyCommand",
172
+ "ReadOnlyStubCommand",
173
+ "AddCommand",
174
+ "ModelCommand",
175
+ "WeakModelCommand",
176
+ "WebCommand",
177
+ "LintCommand",
178
+ "TestCommand",
179
+ "ContextManagementCommand",
180
+ "ContextBlocksCommand",
181
+ "AskCommand",
182
+ "CodeCommand",
183
+ "ArchitectCommand",
184
+ "ContextCommand",
185
+ "AgentCommand",
186
+ "CopyContextCommand",
187
+ "CommandPrefixCommand",
188
+ "LoadSkillCommand",
189
+ "RemoveSkillCommand",
190
+ "SwitchCoderSignal",
191
+ "Commands",
192
+ ]
cecli/commands/add.py ADDED
@@ -0,0 +1,226 @@
1
+ import os
2
+ import re
3
+ from pathlib import Path
4
+ from typing import List
5
+
6
+ from cecli.commands.utils.base_command import BaseCommand
7
+ from cecli.commands.utils.helpers import (
8
+ format_command_result,
9
+ parse_quoted_filenames,
10
+ quote_filename,
11
+ )
12
+ from cecli.utils import is_image_file, run_fzf
13
+
14
+
15
+ class AddCommand(BaseCommand):
16
+ NORM_NAME = "add"
17
+ DESCRIPTION = "Add files to the chat so cecli can edit them or review them in detail"
18
+
19
+ @classmethod
20
+ async def execute(cls, io, coder, args, **kwargs):
21
+ """Execute the add command with given parameters."""
22
+ if not args.strip():
23
+ all_files = coder.get_all_relative_files()
24
+ files_in_chat = coder.get_inchat_relative_files()
25
+ addable_files = sorted(set(all_files) - set(files_in_chat))
26
+ if not addable_files:
27
+ io.tool_output("No files available to add.")
28
+ return format_command_result(io, "add", "No files available to add")
29
+ selected_files = run_fzf(addable_files, multi=True, coder=coder)
30
+ if not selected_files:
31
+ return format_command_result(io, "add", "No files selected")
32
+ args = " ".join([quote_filename(f) for f in selected_files])
33
+
34
+ all_matched_files = set()
35
+
36
+ filenames = parse_quoted_filenames(args)
37
+ for word in filenames:
38
+ if Path(word).is_absolute():
39
+ fname = Path(word)
40
+ else:
41
+ fname = Path(coder.root) / word
42
+
43
+ if coder.repo and coder.repo.ignored_file(fname):
44
+ io.tool_warning(f"Skipping {fname} due to cecli.ignore or --subtree-only.")
45
+ continue
46
+
47
+ if fname.exists():
48
+ if fname.is_file():
49
+ all_matched_files.add(str(fname))
50
+ continue
51
+ # an existing dir, escape any special chars so they won't be globs
52
+ word = re.sub(r"([\*\?\[\]])", r"[\1]", word)
53
+
54
+ matched_files = cls.glob_filtered_to_repo(coder, word)
55
+ if matched_files:
56
+ all_matched_files.update(matched_files)
57
+ continue
58
+
59
+ if "*" in str(fname) or "?" in str(fname):
60
+ io.tool_error(f"No match, and cannot create file with wildcard characters: {fname}")
61
+ continue
62
+
63
+ if fname.exists() and fname.is_dir() and coder.repo:
64
+ io.tool_error(f"Directory {fname} is not in git.")
65
+ io.tool_output(f"You can add to git with: /git add {fname}")
66
+ continue
67
+
68
+ if await io.confirm_ask(f"No files matched '{word}'. Do you want to create {fname}?"):
69
+ try:
70
+ fname.parent.mkdir(parents=True, exist_ok=True)
71
+ fname.touch()
72
+ all_matched_files.add(str(fname))
73
+ except OSError as e:
74
+ io.tool_error(f"Error creating file {fname}: {e}")
75
+
76
+ for matched_file in sorted(all_matched_files):
77
+ abs_file_path = coder.abs_root_path(matched_file)
78
+
79
+ if not abs_file_path.startswith(coder.root) and not is_image_file(matched_file):
80
+ io.tool_error(f"Can not add {abs_file_path}, which is not within {coder.root}")
81
+ continue
82
+
83
+ if (
84
+ coder.repo
85
+ and coder.repo.git_ignored_file(matched_file)
86
+ and not coder.add_gitignore_files
87
+ ):
88
+ io.tool_error(f"Can't add {matched_file} which is in gitignore")
89
+ continue
90
+
91
+ if abs_file_path in coder.abs_fnames:
92
+ io.tool_error(f"{matched_file} is already in the chat as an editable file")
93
+ continue
94
+ elif abs_file_path in coder.abs_read_only_stubs_fnames:
95
+ if coder.repo and coder.repo.path_in_repo(matched_file):
96
+ coder.abs_read_only_stubs_fnames.remove(abs_file_path)
97
+ coder.abs_fnames.add(abs_file_path)
98
+ io.tool_output(
99
+ f"Moved {matched_file} from read-only (stub) to editable files in the chat"
100
+ )
101
+ else:
102
+ io.tool_error(f"Cannot add {matched_file} as it's not part of the repository")
103
+ elif abs_file_path in coder.abs_read_only_fnames:
104
+ if coder.repo and coder.repo.path_in_repo(matched_file):
105
+ coder.abs_read_only_fnames.remove(abs_file_path)
106
+ coder.abs_fnames.add(abs_file_path)
107
+ io.tool_output(
108
+ f"Moved {matched_file} from read-only to editable files in the chat"
109
+ )
110
+ else:
111
+ io.tool_error(f"Cannot add {matched_file} as it's not part of the repository")
112
+ else:
113
+ if is_image_file(matched_file) and not coder.main_model.info.get("supports_vision"):
114
+ io.tool_error(
115
+ f"Cannot add image file {matched_file} as the"
116
+ f" {coder.main_model.name} does not support images."
117
+ )
118
+ continue
119
+ content = io.read_text(abs_file_path)
120
+ if content is None:
121
+ io.tool_error(f"Unable to read {matched_file}")
122
+ else:
123
+ coder.abs_fnames.add(abs_file_path)
124
+ fname = coder.get_rel_fname(abs_file_path)
125
+ io.tool_output(f"Added {fname} to the chat")
126
+ coder.check_added_files()
127
+
128
+ # Recalculate context block tokens if using agent mode
129
+ if hasattr(coder, "use_enhanced_context") and coder.use_enhanced_context:
130
+ if hasattr(coder, "_calculate_context_block_tokens"):
131
+ coder._calculate_context_block_tokens()
132
+
133
+ if coder.repo_map:
134
+ map_tokens = coder.repo_map.max_map_tokens
135
+ map_mul_no_files = coder.repo_map.map_mul_no_files
136
+ else:
137
+ map_tokens = 0
138
+ map_mul_no_files = 1
139
+
140
+ from cecli.commands import SwitchCoderSignal
141
+
142
+ raise SwitchCoderSignal(
143
+ edit_format=coder.edit_format,
144
+ summarize_from_coder=False,
145
+ from_coder=coder,
146
+ map_tokens=map_tokens,
147
+ map_mul_no_files=map_mul_no_files,
148
+ show_announcements=False,
149
+ )
150
+
151
+ @classmethod
152
+ def glob_filtered_to_repo(cls, coder, pattern: str) -> List[str]:
153
+ """Glob pattern and filter results to repository files."""
154
+ if not pattern.strip():
155
+ return []
156
+ try:
157
+ if os.path.isabs(pattern):
158
+ # Handle absolute paths
159
+ raw_matched_files = [Path(pattern)]
160
+ else:
161
+ try:
162
+ raw_matched_files = list(Path(coder.root).glob(pattern))
163
+ except (IndexError, AttributeError):
164
+ raw_matched_files = []
165
+ except ValueError:
166
+ # This error will be handled by the caller
167
+ raw_matched_files = []
168
+
169
+ matched_files = []
170
+ for fn in raw_matched_files:
171
+ matched_files += cls.expand_subdir(fn)
172
+
173
+ matched_files = [
174
+ fn.relative_to(coder.root) for fn in matched_files if fn.is_relative_to(coder.root)
175
+ ]
176
+
177
+ # if repo, filter against it
178
+ if coder.repo:
179
+ git_files = coder.repo.get_tracked_files()
180
+ matched_files = [fn for fn in matched_files if str(fn) in git_files]
181
+
182
+ return list(map(str, matched_files))
183
+
184
+ @staticmethod
185
+ def expand_subdir(file_path: Path) -> List[Path]:
186
+ """Expand a directory path to all files within it."""
187
+ if file_path.is_file():
188
+ return [file_path]
189
+
190
+ if file_path.is_dir():
191
+ files = []
192
+ for file in file_path.rglob("*"):
193
+ if file.is_file():
194
+ files.append(file)
195
+ return files
196
+
197
+ return []
198
+
199
+ @classmethod
200
+ def get_completions(cls, io, coder, args) -> List[str]:
201
+ """Get completion options for add command."""
202
+ files = set(coder.get_all_relative_files())
203
+ files = files - set(coder.get_inchat_relative_files())
204
+ files = [quote_filename(fn) for fn in files]
205
+ return files
206
+
207
+ @classmethod
208
+ def get_help(cls) -> str:
209
+ """Get help text for the add command."""
210
+ help_text = super().get_help()
211
+ help_text += "\nUsage:\n"
212
+ help_text += " /add # Interactive file selection using fuzzy finder\n"
213
+ help_text += " /add <files> # Add specific files or glob patterns\n"
214
+ help_text += "\nExamples:\n"
215
+ help_text += " /add # Use fuzzy finder to select files\n"
216
+ help_text += " /add *.py # Add all Python files\n"
217
+ help_text += " /add main.py # Add main.py\n"
218
+ help_text += ' /add "file with spaces.py" # Add file with spaces\n'
219
+ help_text += (
220
+ "\nThis command adds files to the chat so cecli can edit them or review them in"
221
+ " detail.\n"
222
+ )
223
+ help_text += "If a file doesn't exist, you'll be asked if you want to create it.\n"
224
+ help_text += "Files can be moved from read-only to editable status.\n"
225
+ help_text += "Image files can be added if the model supports vision.\n"
226
+ return help_text