lcode-cli 0.38.0__tar.gz → 0.40.0__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 (187) hide show
  1. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/CHANGELOG.md +52 -1
  2. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/PKG-INFO +4 -1
  3. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/README.md +3 -0
  4. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/configuration.md +4 -0
  5. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/mcp.md +5 -81
  6. lcode_cli-0.40.0/docs/modes.md +109 -0
  7. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/quickstart.md +1 -1
  8. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/usage.md +4 -3
  9. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/mkdocs.yml +1 -0
  10. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/__init__.py +1 -1
  11. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/agent.py +9 -0
  12. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/api.py +43 -11
  13. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/cli.py +85 -0
  14. lcode_cli-0.40.0/src/lcode/comfy.py +658 -0
  15. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/config.py +7 -0
  16. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/catalog.toml +0 -48
  17. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/commands.py +6 -0
  18. lcode_cli-0.40.0/src/lcode/media.py +262 -0
  19. lcode_cli-0.40.0/src/lcode/modes.py +124 -0
  20. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/repl.py +116 -19
  21. lcode_cli-0.40.0/src/lcode/workflows/__init__.py +0 -0
  22. lcode_cli-0.40.0/src/lcode/workflows/flux2-klein-4b-t2i.json +38 -0
  23. lcode_cli-0.40.0/tests/comfy_standin.py +231 -0
  24. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/conftest.py +12 -1
  25. lcode_cli-0.40.0/tests/test_comfy.py +499 -0
  26. lcode_cli-0.40.0/tests/test_modes.py +165 -0
  27. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_xai.py +0 -11
  28. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.editorconfig +0 -0
  29. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/CODEOWNERS +0 -0
  30. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  31. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  32. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  33. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
  34. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  35. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/dependabot.yml +0 -0
  36. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/workflows/ci.yml +0 -0
  37. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/workflows/docs.yml +0 -0
  38. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.github/workflows/release.yml +0 -0
  39. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.gitignore +0 -0
  40. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/.pre-commit-config.yaml +0 -0
  41. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/AGENTS.md +0 -0
  42. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/CODE_OF_CONDUCT.md +0 -0
  43. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/CONTRIBUTING.md +0 -0
  44. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/LICENSE +0 -0
  45. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/SECURITY.md +0 -0
  46. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/action.yml +0 -0
  47. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/agents.md +0 -0
  48. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/assets/demo.svg +0 -0
  49. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/assets/extra.css +0 -0
  50. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/automation.md +0 -0
  51. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/bench.md +0 -0
  52. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/changelog.md +0 -0
  53. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/commands.md +0 -0
  54. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/contributing.md +0 -0
  55. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/editors.md +0 -0
  56. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/git.md +0 -0
  57. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/hooks.md +0 -0
  58. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/how-it-works.md +0 -0
  59. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/index.md +0 -0
  60. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/installation.md +0 -0
  61. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/lsp.md +0 -0
  62. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/memory.md +0 -0
  63. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/models.md +0 -0
  64. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/sandbox.md +0 -0
  65. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/search.md +0 -0
  66. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/servers.md +0 -0
  67. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/docs/troubleshooting.md +0 -0
  68. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/examples/README.md +0 -0
  69. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/examples/api/fix_tests.py +0 -0
  70. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/examples/commands/review.md +0 -0
  71. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/examples/skills/commit-message/SKILL.md +0 -0
  72. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
  73. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/install.sh +0 -0
  74. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/overrides/main.html +0 -0
  75. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/pyproject.toml +0 -0
  76. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/scripts/make_logos.py +0 -0
  77. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/scripts/record_demo.py +0 -0
  78. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/scripts/social-preview.html +0 -0
  79. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/__main__.py +0 -0
  80. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/acp.py +0 -0
  81. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/action.py +0 -0
  82. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/backends.py +0 -0
  83. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/bench.py +0 -0
  84. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/bench_backend.py +0 -0
  85. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/bench_runtime.py +0 -0
  86. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/catalog.py +0 -0
  87. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/checkpoints.py +0 -0
  88. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/checks.py +0 -0
  89. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/clipboard.py +0 -0
  90. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/cloud.py +0 -0
  91. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/codesearch.py +0 -0
  92. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/context.py +0 -0
  93. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/escalate.py +0 -0
  94. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/extensions.py +0 -0
  95. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/frontmatter.py +0 -0
  96. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/gitflow.py +0 -0
  97. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/hardware.py +0 -0
  98. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/hooks.py +0 -0
  99. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/jobs.py +0 -0
  100. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/limits.py +0 -0
  101. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/lsp.py +0 -0
  102. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/matching.py +0 -0
  103. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/__init__.py +0 -0
  104. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/auth.py +0 -0
  105. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/catalog.py +0 -0
  106. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/client.py +0 -0
  107. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/config.py +0 -0
  108. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/http.py +0 -0
  109. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/manager.py +0 -0
  110. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/protocol.py +0 -0
  111. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/server.py +0 -0
  112. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/mcp/stdio.py +0 -0
  113. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/memory.py +0 -0
  114. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/models.toml +0 -0
  115. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/notify.py +0 -0
  116. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/ollama.py +0 -0
  117. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/permissions.py +0 -0
  118. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/planning.py +0 -0
  119. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/render.py +0 -0
  120. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/repeats.py +0 -0
  121. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/repomap.py +0 -0
  122. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/residency.py +0 -0
  123. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/review.py +0 -0
  124. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/sandbox.py +0 -0
  125. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/screenshots.py +0 -0
  126. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/secretscan.py +0 -0
  127. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/servers/__init__.py +0 -0
  128. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/servers/encord.py +0 -0
  129. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/servers/valohai.py +0 -0
  130. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/servers/xai.py +0 -0
  131. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/sessions.py +0 -0
  132. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/steer.py +0 -0
  133. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/subagents.py +0 -0
  134. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/tools.py +0 -0
  135. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/unreachable.py +0 -0
  136. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/update.py +0 -0
  137. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/vision.py +0 -0
  138. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/src/lcode/web.py +0 -0
  139. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/fake_lsp_server.py +0 -0
  140. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/fake_mcp_server.py +0 -0
  141. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_acp.py +0 -0
  142. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_action.py +0 -0
  143. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_agent.py +0 -0
  144. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_api.py +0 -0
  145. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_backends.py +0 -0
  146. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_bench.py +0 -0
  147. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_catalog.py +0 -0
  148. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_checkpoints.py +0 -0
  149. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_checks.py +0 -0
  150. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_cli.py +0 -0
  151. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_clipboard.py +0 -0
  152. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_cloud.py +0 -0
  153. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_codesearch.py +0 -0
  154. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_config.py +0 -0
  155. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_context.py +0 -0
  156. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_context_picker.py +0 -0
  157. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_escalate.py +0 -0
  158. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_extensions.py +0 -0
  159. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_gitflow.py +0 -0
  160. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_hooks.py +0 -0
  161. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_jobs.py +0 -0
  162. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_look.py +0 -0
  163. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_lsp.py +0 -0
  164. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_matching.py +0 -0
  165. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_mcp.py +0 -0
  166. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_memory.py +0 -0
  167. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_model_picker.py +0 -0
  168. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_permissions.py +0 -0
  169. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_plan_mode.py +0 -0
  170. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_repeats.py +0 -0
  171. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_repomap.py +0 -0
  172. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_residency.py +0 -0
  173. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_review.py +0 -0
  174. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_robustness.py +0 -0
  175. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_sandbox.py +0 -0
  176. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_screenshots.py +0 -0
  177. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_secretscan.py +0 -0
  178. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_servers.py +0 -0
  179. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_sessions.py +0 -0
  180. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_steer.py +0 -0
  181. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_stuck.py +0 -0
  182. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_subagents.py +0 -0
  183. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_tools.py +0 -0
  184. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_update.py +0 -0
  185. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_vision.py +0 -0
  186. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/tests/test_web.py +0 -0
  187. {lcode_cli-0.38.0 → lcode_cli-0.40.0}/uv.lock +0 -0
@@ -6,6 +6,55 @@ All notable changes to lcode are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.40.0] - 2026-10-23
10
+
11
+ ### Added
12
+
13
+ - **Image mode makes images** (#149). In image mode, what you type becomes a picture, made by
14
+ FLUX.2 klein 4B on your GPU in about 8 seconds, with no language model involved. lcode drives
15
+ ComfyUI itself. See [Modes](https://nasser1941.github.io/lcode/modes/#image-mode).
16
+ - **ComfyUI is found or started:** lcode uses a running ComfyUI as it is. Otherwise it starts
17
+ yours in the background when you enter image mode (`comfyui_path`, the `comfy` CLI's folder or
18
+ a usual one). It stops that ComfyUI when you quit, go back to code mode or close the terminal.
19
+ With none, it offers to install ComfyUI with comfy-cli.
20
+ - **Progress, cancel and type-ahead:** the status line shows the step, the time left and your
21
+ place in ComfyUI's queue. Esc or Ctrl+C cancels the job in ComfyUI too. What you type
22
+ meanwhile waits in the next prompt.
23
+ - **Files with their settings:** images go to `.lcode/media/` (the `media_dir` setting), each
24
+ with a JSON file of its prompt, model, seed and size, also stored in the PNG itself. Images
25
+ that finish after a crash are saved the next time.
26
+ - `lcode --mode image -p "…"` makes an image from a script, and `--output json` gives the paths.
27
+ `lcode doctor` reports ComfyUI and the image model.
28
+ - **No Ollama needed for images:** lcode starts in image mode without Ollama or the coding
29
+ model; `/mode code` says what's missing.
30
+
31
+ ### Fixed
32
+
33
+ - **lcode cleans up when its terminal closes.** A closed terminal or a `kill` used to end lcode
34
+ without stopping what it had started. Now it saves the session and stops its background jobs and
35
+ servers. On Linux, a ComfyUI lcode started also stops if lcode is killed outright.
36
+
37
+ ## [0.39.0] - 2026-10-23
38
+
39
+ ### Added
40
+
41
+ - **Modes: code, image and video** (#148). `/mode image`, `/mode video` and `/mode code` switch
42
+ what lcode makes. `lcode --mode` starts in one, and the `mode` setting is the default. The
43
+ banner, status bar and prompt show the mode, and sessions keep theirs.
44
+ - **The GPU goes to one model at a time:** switching to image or video mode unloads the coding
45
+ model, and switching back asks ComfyUI to free its memory.
46
+ - **Image and video mode don't generate yet:** they say what's coming (#149–#157) instead of
47
+ sending your words to the coding model. See [Modes](https://nasser1941.github.io/lcode/modes/).
48
+
49
+ ### Changed
50
+
51
+ - **Code mode makes code only.** The MCP servers that generated images and videos (`comfyui`,
52
+ `xai`) are out of the MCP catalog, and lcode doesn't load them in code mode even if they're
53
+ still in `mcp.json` (it says so once). Image and video generation are moving to their own modes.
54
+ - **`/mode` switches code, image and video.** The permission mode is set with `/permissions` now;
55
+ `/mode yolo` and the like still work, with a note. Shift+Tab cycles the permission mode as
56
+ before.
57
+
9
58
  ## [0.38.0] - 2026-10-21
10
59
 
11
60
  ### Added
@@ -855,7 +904,9 @@ First public release.
855
904
  - `AGENTS.md` project instructions and `/init` to generate them.
856
905
  - One-line installer for Linux and macOS.
857
906
 
858
- [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.38.0...HEAD
907
+ [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.40.0...HEAD
908
+ [0.40.0]: https://github.com/nasser1941/lcode/compare/v0.39.0...v0.40.0
909
+ [0.39.0]: https://github.com/nasser1941/lcode/compare/v0.38.0...v0.39.0
859
910
  [0.38.0]: https://github.com/nasser1941/lcode/compare/v0.37.0...v0.38.0
860
911
  [0.37.0]: https://github.com/nasser1941/lcode/compare/v0.36.0...v0.37.0
861
912
  [0.36.0]: https://github.com/nasser1941/lcode/compare/v0.35.1...v0.36.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lcode-cli
3
- Version: 0.38.0
3
+ Version: 0.40.0
4
4
  Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
5
5
  Project-URL: Homepage, https://nasser1941.github.io/lcode/
6
6
  Project-URL: Documentation, https://nasser1941.github.io/lcode/
@@ -107,6 +107,9 @@ through [Ollama](https://ollama.com), and when it needs current information it c
107
107
  `lcode --worktree` runs parallel sessions on one repository.
108
108
  - **In your editor.** Zed, JetBrains IDEs, Neovim and other editors that speak the Agent Client
109
109
  Protocol run lcode with `lcode acp`, with diffs and approvals in the editor.
110
+ - **Image mode.** `/mode image`, describe a picture, and FLUX.2 klein 4B makes it on your GPU in
111
+ seconds through ComfyUI, which lcode starts, follows and stops itself. Video mode is next
112
+ ([modes](https://nasser1941.github.io/lcode/modes/)).
110
113
  - **Automation.** `lcode -p --output json` for scripts and CI, a Python API (`from lcode import
111
114
  Session`), and a GitHub Action that answers `@lcode` in issues and pull requests on your own
112
115
  self-hosted runner.
@@ -71,6 +71,9 @@ through [Ollama](https://ollama.com), and when it needs current information it c
71
71
  `lcode --worktree` runs parallel sessions on one repository.
72
72
  - **In your editor.** Zed, JetBrains IDEs, Neovim and other editors that speak the Agent Client
73
73
  Protocol run lcode with `lcode acp`, with diffs and approvals in the editor.
74
+ - **Image mode.** `/mode image`, describe a picture, and FLUX.2 klein 4B makes it on your GPU in
75
+ seconds through ComfyUI, which lcode starts, follows and stops itself. Video mode is next
76
+ ([modes](https://nasser1941.github.io/lcode/modes/)).
74
77
  - **Automation.** `lcode -p --output json` for scripts and CI, a Python API (`from lcode import
75
78
  Session`), and a GitHub Action that answers `@lcode` in issues and pull requests on your own
76
79
  self-hosted runner.
@@ -23,6 +23,10 @@ lcode config path # print the file location
23
23
  | `backend` | `ollama` | The model server: `ollama`, `lmstudio`, `llama.cpp`, `vllm`, `mlx` or `openai` (any OpenAI-compatible API); see [Other model servers](servers.md) |
24
24
  | `base_url` | the backend's | The server's OpenAI-compatible address, e.g. `http://localhost:1234/v1` |
25
25
  | `permission_mode` | `ask` | `ask`, `plan`, `auto-edit` or `yolo` |
26
+ | `mode` | `code` | What lcode makes when it starts: `code`, `image` or `video` ([modes](modes.md)) |
27
+ | `comfyui_url` | `http://127.0.0.1:8188` | ComfyUI's address, for image and video mode |
28
+ | `comfyui_path` | (found) | Your ComfyUI folder, if lcode doesn't find it; lcode starts it when needed ([ComfyUI](modes.md#comfyui)) |
29
+ | `media_dir` | `.lcode/media` | Where images and videos go: a folder in the project, or an absolute path |
26
30
  | `think` | `true` | Let the model reason before answering |
27
31
  | `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
28
32
  | `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
@@ -28,8 +28,6 @@ and find the code for the top one."*
28
28
  | `gcp` | `gcloud` commands on your Google Cloud projects | `npx` and the `gcloud` CLI, signed in |
29
29
  | `github` | Repositories, issues, pull requests, Actions | a GitHub token (or the GitHub CLI) |
30
30
  | `playwright` | A real (headless) browser: open pages, click, fill forms | `npx` |
31
- | `comfyui` | Generate and edit images with local models (FLUX, SDXL, SD 1.5, Qwen-Image) | `uvx` and [ComfyUI](#images-with-comfyui) running locally |
32
- | `xai` | Generate and edit images, and generate videos and speech, with xAI's Grok Imagine (paid, in the cloud) | `uvx`, an xAI API key ([below](#images-videos-and-speech-with-xai)) |
33
31
  | `context7` | Up-to-date docs and examples for thousands of libraries | nothing (an API key is optional) |
34
32
  | `sentry` | Errors, issues, traces and releases | a Sentry account (browser sign-in) |
35
33
  | `postgres` | Schemas, read-only queries, query performance | `uvx`, a connection URL |
@@ -87,86 +85,12 @@ Google Cloud project:
87
85
  See [Google's guide](https://developers.google.com/workspace/guides/configure-mcp-servers) for
88
86
  details.
89
87
 
90
- ### Images with ComfyUI
88
+ ### Images and videos
91
89
 
92
- [ComfyUI](https://github.com/comfyanonymous/ComfyUI) runs image generation and editing models
93
- locally; its official MCP server lets lcode use it, for app icons, illustrations, mockups or
94
- placeholder art.
95
-
96
- ```bash
97
- uv tool install comfy-cli && comfy install # ComfyUI, once
98
- comfy launch --background -- --disable-smart-memory # start it (http://127.0.0.1:8188)
99
- lcode mcp add comfyui
100
- ```
101
-
102
- Then add models in ComfyUI (its model manager, or ask lcode: *"download FLUX.2 Klein 4B in
103
- ComfyUI"*), and ask for images: *"make a 512×512 app icon of a paper plane and save it in
104
- assets/"*. To edit an image, ask lcode to upload it and run an editing workflow (Kontext or
105
- Qwen-Image-Edit).
106
-
107
- | Model | Good for | On a 12 GB GPU |
108
- |---|---|---|
109
- | FLUX.2 Klein 4B | Generation and editing, fast | Fits (about 8 GB) |
110
- | SDXL and its community models | Generation, inpainting | Fits |
111
- | Stable Diffusion 1.5 and its community models | Light generation, inpainting | Fits easily |
112
- | FLUX.1 Dev and finetunes | High-quality generation | Needs an fp8 or GGUF version; slower |
113
- | FLUX.1 Kontext Dev | Editing an image from instructions | Needs an fp8 or GGUF version; slower |
114
- | Qwen-Image / Qwen-Image-Edit | Generation and editing, good text in images | Heavy: a GGUF version and RAM offloading |
115
-
116
- Only ComfyUI's local tools are turned on; Comfy Cloud's partner tools are left out, so prompts and
117
- images stay on your machine.
118
-
119
- **Sharing one GPU.** A GPU rarely holds a coding model and an image model at the same time, so the
120
- two take turns: before ComfyUI generates, lcode unloads its own model (the `free_gpu` setting), and
121
- ComfyUI started with `--disable-smart-memory` gives the GPU back after each image. lcode's model
122
- then reloads for the next step, which adds a few seconds to half a minute per image request. With
123
- enough GPU memory for both (or on a Mac with plenty of memory), remove `free_gpu` from the server's
124
- settings in `mcp.json`. lcode can also look at the results ([Images](usage.md#images)).
125
-
126
- Measured on an RTX 4080 Laptop GPU (12 GB) with qwen3.6-35b at 64K context and FLUX.2 Klein Base 4B
127
- (fp8, 512×512, 20 steps):
128
-
129
- | | |
130
- |---|---|
131
- | Generating while lcode's model is loaded | Fails: ComfyUI runs out of GPU memory |
132
- | Freeing the GPU (lcode unloads its model) | 0.2 s |
133
- | Generating one image | about 12 s |
134
- | Reloading lcode's model afterwards | about 20 s |
135
-
136
- The first time, the model has to find the right template and fill in its settings (model file names,
137
- size, prompt), which can take several minutes of trial and error. Once an image comes out right, ask
138
- lcode to save that workflow in your project (for example `assets/icon.workflow.json`) and reuse it:
139
- later images are a single `run_workflow` call.
140
-
141
- ### Images, videos and speech with xAI
142
-
143
- xAI's [Grok Imagine](https://docs.x.ai/developers/model-capabilities/imagine) and
144
- [text to speech](https://docs.x.ai/developers/models/text-to-speech) APIs make images, videos and
145
- speech in the cloud: no GPU needed, but each call is billed to your xAI account. lcode ships an MCP
146
- server for them, `lcode-mcp-xai`.
147
-
148
- ```bash
149
- lcode mcp add xai # asks for your xAI API key (create one at https://console.x.ai)
150
- ```
151
-
152
- The key is typed hidden and kept in `~/.config/lcode/mcp.json`, readable only by you. If
153
- `XAI_API_KEY` is already set in your environment, lcode uses that and doesn't store the key.
154
-
155
- | Tool | | Costs |
156
- |---|---|---|
157
- | `generate_image` | Images from a description: 1–10 at a time, aspect ratio, 1k or 2k | $0.02–0.05 an image |
158
- | `edit_image` | Change a local image as described | the same |
159
- | `generate_video` | A 1–15 second MP4 from a description, optionally starting from a local image, up to 1080p. Waits for it (a minute or more) | $0.02–0.08 a second |
160
- | `video_status` | Fetch a video that wasn't ready within 10 minutes | |
161
- | `text_to_speech` | Speech in one of 26 voices and 20 languages, as MP3 or WAV | per character |
162
-
163
- Files are saved in `generated/` in the project, or where you ask (*"generate an app icon and save
164
- it as assets/icon.png"*). xAI has no music generation API.
165
-
166
- Your prompts, and the images you edit or animate, go to xAI. lcode asks before each call, since each
167
- one costs money. To stop asking for some tools, list them under `"allow"` for the server in
168
- `mcp.json`, for example `"allow": ["generate_image", "text_to_speech"]`. xAI applies its own usage
169
- policies to what it generates; when it refuses a prompt, lcode passes its message to the model.
90
+ Code mode makes code only, so the servers that generated images and videos in earlier versions
91
+ (`comfyui`, `xai`) aren't in the catalog any more, and lcode doesn't load them in code mode if
92
+ they're still in your `mcp.json`. Image and video generation get their own [modes](modes.md),
93
+ with their own models and controls.
170
94
 
171
95
  ### Metabase
172
96
 
@@ -0,0 +1,109 @@
1
+ # Modes: code, image and video
2
+
3
+ lcode makes three kinds of things, each in its own mode with its own models:
4
+
5
+ | Mode | What it makes | The model doing the work |
6
+ |---|---|---|
7
+ | **code** (default) | Code: it reads, writes, runs and checks your project | A coding language model in Ollama (or another server) |
8
+ | **image** | Images from a description and reference images | An image model, run by ComfyUI on your GPU |
9
+ | **video** | Videos from a description, a start image or references | A video model, run by ComfyUI on your GPU |
10
+
11
+ ```text
12
+ ❯ /mode image
13
+ Mode: image (local image generation).
14
+ Unloaded qwen3.6-35b to free the GPU for images.
15
+ Starting ComfyUI (~/comfy/ComfyUI) in the background.
16
+ Describe an image and press Enter: FLUX.2 klein 4B makes it on your GPU, into .lcode/media.
17
+ image ❯ a red fox sitting in fresh snow at dusk
18
+ ◆ .lcode/media/20261023-184531-a-red-fox-sitting-in-fresh-snow-at-dusk-1.png 1024×1024 · FLUX.2 klein 4B · seed 3714779554 · 8.0 s
19
+ ```
20
+
21
+ - **Switching:** `/mode code`, `/mode image` and `/mode video` switch in a session. `lcode --mode image`
22
+ starts in one, and `lcode config set mode video` makes it the default. `/mode` alone shows
23
+ the current one.
24
+ - **Where it shows:** the banner, the status bar and the prompt (`image ❯`) show the mode.
25
+ - **Sessions:** a session keeps the mode it was made in, and resuming it brings that mode back.
26
+
27
+ ## Code mode makes code only
28
+
29
+ Code mode is the coding agent, as before, and it no longer generates images or videos. The MCP
30
+ servers that did (`comfyui` and `xai`) are gone from the MCP catalog, and lcode doesn't load them
31
+ in code mode even if they're still in your `mcp.json`. It says so once.
32
+
33
+ ## Image mode
34
+
35
+ In image mode, what you type is a description, and lcode makes the picture. No language model is
36
+ involved: lcode fills in its own ComfyUI workflow for the image model and follows the job itself.
37
+ For now that's one image per request, 1024×1024, with **FLUX.2 klein 4B** (4 steps, about 8 seconds
38
+ on a 12 GB laptop GPU; a 4B model that fits in 8 GB). Choosing models, sizes, seeds and variations
39
+ comes next (#150, #151).
40
+
41
+ - **Progress:** the status line says what ComfyUI is doing (loading the model, reading the
42
+ prompt, drawing, decoding), the step, the time left and the time so far. If other jobs are ahead
43
+ in ComfyUI's queue, it says how many.
44
+ - **Cancel** with Esc or Ctrl+C: lcode cancels the job in ComfyUI too, so it stops using the GPU.
45
+ - **Type ahead:** what you type while an image is being made waits in the next prompt.
46
+ - **Files:** each image goes to `.lcode/media/` in the project (the `media_dir` setting), named
47
+ after the time and the prompt, with a `.json` next to it holding the prompt, model, seed, size and
48
+ steps. PNGs carry the same in their own metadata (an `lcode` text chunk), and ComfyUI's workflow
49
+ as well. `.lcode/media/` keeps itself out of git; a folder of your own (`lcode config set
50
+ media_dir assets/generated`) doesn't.
51
+ - **Nothing is lost:** lcode writes each job down while it runs. If lcode crashes or the terminal
52
+ closes before an image arrives, the next request in image mode saves it.
53
+ - **From scripts:** `lcode --mode image -p "a red fox in the snow"` makes one image and exits;
54
+ with `--output json` it prints a result with the file paths, model, seed, size and time.
55
+ - **No Ollama needed:** `lcode --mode image` starts even when Ollama isn't running or the coding
56
+ model isn't installed; `/mode code` then says what's missing.
57
+
58
+ ## ComfyUI
59
+
60
+ ComfyUI runs the image and video models; lcode drives it through its API.
61
+
62
+ - **One that's running** at `comfyui_url` (by default `http://127.0.0.1:8188`) is used as it is.
63
+ - **Otherwise lcode starts one** in the background when you switch to image mode, so the first
64
+ image waits less, and stops it when you quit or go back to code mode, or when the terminal
65
+ closes. It looks in the `comfyui_path` setting, then the folder the `comfy` CLI uses, then
66
+ `~/comfy/ComfyUI`, `~/ComfyUI`, `~/comfyui` and `~/Documents/ComfyUI`. It uses that install's
67
+ own Python (its `.venv` or `venv`), and logs to `comfyui.log` in lcode's state folder.
68
+ - **With none,** your first image request offers to install ComfyUI into lcode's state folder with
69
+ [comfy-cli](https://github.com/Comfy-Org/comfy-cli) and the PyTorch build for your GPU (NVIDIA,
70
+ Apple Silicon or CPU). That downloads several GB. To install it yourself:
71
+
72
+ ```bash
73
+ uvx --from comfy-cli comfy install
74
+ ```
75
+
76
+ - **The image model** has to be in ComfyUI's model folders for now: for FLUX.2 klein 4B,
77
+ `diffusion_models/flux-2-klein-4b-fp8.safetensors`, `text_encoders/qwen_3_4b.safetensors` and
78
+ `vae/flux2-vae.safetensors` (from ComfyUI's FLUX.2 klein template). lcode names any that are
79
+ missing. Installing models from lcode comes with #150.
80
+ - **`lcode doctor`** shows whether ComfyUI is running or found, and whether the image model is
81
+ there.
82
+
83
+ ## One model on the GPU at a time
84
+
85
+ Image and video modes don't need the big coding model: the image or video model does the work. A
86
+ small optional model will help with prompts later. Image and video models need the GPU to
87
+ themselves:
88
+
89
+ - **Into image or video mode:** lcode unloads its coding model from Ollama, and checks again
90
+ before each image.
91
+ - **Back to code mode:** lcode stops the ComfyUI it started, or asks yours to free its memory,
92
+ then loads the coding model again in the background.
93
+
94
+ ## Coming next
95
+
96
+ Video mode doesn't generate yet: a request there says what's coming instead of going anywhere.
97
+
98
+ - **#150 Media model catalog:** image and video models that fit your machine, installed on
99
+ demand.
100
+ - **#151, #152 Image mode:** generating, variations, sizes and seeds, a gallery, editing,
101
+ references, inpainting, upscaling.
102
+ - **#153, #154 Video mode:** from text or a start image, first and last frames, several
103
+ references, camera moves, longer clips, sound.
104
+ - **#155 Movies:** storyboards with a consistent cast, shots stitched into one film.
105
+ - **#156 Prompt assistant:** a small local model for prompts and follow-ups.
106
+ - **#157 Cloud generation**, with costs shown (Grok Imagine first).
107
+
108
+ The permission mode (`ask`, `plan`, `auto-edit`, `yolo`) is a different setting: `/permissions`
109
+ sets it, and Shift+Tab cycles it as before.
@@ -12,7 +12,7 @@ lcode
12
12
 
13
13
  ```text
14
14
  ╭──────────────────────────────────────────────╮
15
- │ lcode v0.38.0 — local coding agent │
15
+ │ lcode v0.40.0 — local coding agent │
16
16
  │ │
17
17
  │ model lcode-qwen3.6-35b │
18
18
  │ context 256K tokens │
@@ -79,7 +79,8 @@ reasoning is on.
79
79
  | `/models`, `/model [number or name]` | List the models and how they fit this machine, numbered: type a number to switch, or `/model 2`, `/model qwen3.5-9b` |
80
80
  | `/think [on\|off]` | Toggle reasoning |
81
81
  | `/verbose` | Toggle showing the reasoning text |
82
- | `/mode [ask\|plan\|auto-edit\|yolo]` | Set the permission mode |
82
+ | `/mode [code\|image\|video]` | Switch what lcode makes: [code, images or videos](modes.md) |
83
+ | `/permissions [ask\|plan\|auto-edit\|yolo]` | Set the permission mode |
83
84
  | `/cd DIR` | Change the working directory |
84
85
  | `/todos` | Show the model's task list |
85
86
  | `/status` | The task sheet: your latest requests, the plan and the model's notes, files changed, the last check |
@@ -315,7 +316,7 @@ stays loaded:
315
316
  | The model was loaded | `◆ Loaded qwen3.6-35b in 15s: 22.3 GB, 45% on the GPU and the rest in system RAM.`, and a warning when less of it is on the GPU than usual, because another program or model is using GPU memory |
316
317
  | A dense model doesn't fit on the GPU (at startup and on `/model`) | `⚠ qwen3.8-27b is a dense model: all 27B parameters work on every token. It's capable, but slow here. Only about 61% of it fits in the 12 GB of GPU memory…`, with what to do instead |
317
318
  | The model that looks at images needs the memory | `Loading the vision model … to look at bug.png; qwen3.6-35b makes room and reloads afterwards`, then `◆ qwen3.6-35b was unloaded to make room…`, and the next step says `Reloading qwen3.6-35b into memory (it made room for the vision model …)` |
318
- | A tool needs the GPU to itself, such as image generation | `◆ Unloaded qwen3.6-35b so comfyui can use the GPU; it reloads afterwards.` |
319
+ | A tool needs the GPU to itself (an MCP server with `free_gpu`) | `◆ Unloaded qwen3.6-35b so the server can use the GPU; it reloads afterwards.` |
319
320
  | Ollama ran out of memory | `⚠` what ran out (the prompt batch, or the context next to the model), what lcode retries with, and what it remembers for next time |
320
321
 
321
322
  While the model loads in the background at startup, the status bar says
@@ -470,7 +471,7 @@ a screen and sees what you would, measured:
470
471
  | `auto-edit` | automatic | ask, except read-only commands |
471
472
  | `yolo` | automatic | automatic |
472
473
 
473
- ++shift+tab++ cycles through them in this order, `/mode` sets one, and `--plan`, `--auto-edit` and
474
+ ++shift+tab++ cycles through them in this order, `/permissions` sets one, and `--plan`, `--auto-edit` and
474
475
  `--yolo` start in one.
475
476
 
476
477
  Read-only commands run without asking: `ls`, `cat`, `head`, `tail`, `grep`, `rg`, `find`, `tree`,
@@ -92,6 +92,7 @@ nav:
92
92
  - Quickstart: quickstart.md
93
93
  - Guide:
94
94
  - Using lcode: usage.md
95
+ - Modes (code, image, video): modes.md
95
96
  - Memory: memory.md
96
97
  - Subagents: agents.md
97
98
  - Commands and skills: commands.md
@@ -3,7 +3,7 @@
3
3
  From a program: `from lcode import Session` (see lcode.api).
4
4
  """
5
5
 
6
- __version__ = "0.38.0"
6
+ __version__ = "0.40.0"
7
7
 
8
8
  API = ("Session", "Result", "Options", "SetupError")
9
9
 
@@ -304,6 +304,10 @@ class Progress:
304
304
  class Settings:
305
305
  model: str # Ollama model name to call
306
306
  context: int
307
+ mode: str = "code" # what lcode makes: code, image or video (lcode.modes)
308
+ comfyui_url: str = "http://127.0.0.1:8188"
309
+ comfyui_path: str | None = None # a ComfyUI install lcode doesn't find by itself (lcode.comfy)
310
+ media_dir: str = ".lcode/media" # where image and video mode save their files (lcode.media)
307
311
  num_batch: int | None = None
308
312
  keep_alive: str = "30m"
309
313
  think: bool = True
@@ -368,6 +372,8 @@ class Agent:
368
372
  self.session_name = ""
369
373
  self.session_title = ""
370
374
  self.interactive = True # False for `lcode -p`: no end-of-session questions
375
+ self.comfy_process = None # the ComfyUI lcode started for image and video mode, stopped when it exits
376
+ self.no_coding_model = "" # why there's no coding model, when lcode started in image or video mode without
371
377
  self.reflected = 0 # messages before this index were already checked for notes to remember
372
378
  self._memory: memory_notes.Memory | None = None
373
379
  self._agent_types: tuple[Path, dict[str, subagents.AgentType], list[str]] | None = None
@@ -680,6 +686,7 @@ class Agent:
680
686
  data = {
681
687
  "cwd": str(self.workspace), # where `lcode -c` there finds it, wherever the shell moved
682
688
  "model": self.settings.model,
689
+ "mode": self.settings.mode,
683
690
  "name": self.session_name,
684
691
  "title": self.session_title,
685
692
  "messages": self.messages,
@@ -726,6 +733,8 @@ class Agent:
726
733
  self.session_id = info.id
727
734
  self.checkpoints.load(info.id, data.get("checkpoints") or [])
728
735
  self.session_name = data.get("name", "")
736
+ if data.get("mode") in ("code", "image", "video"):
737
+ self.settings.mode = data["mode"] # a session belongs to the mode it was made in
729
738
  self.session_title = data.get("title") or sessions.title_from(self.messages)
730
739
  self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
731
740
  self.tools.todos = list(data.get("todos") or [])
@@ -15,6 +15,7 @@ A session is one conversation: each `run` continues it. `stream` yields the same
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
+ import contextlib
18
19
  import io
19
20
  import queue
20
21
  import threading
@@ -54,6 +55,7 @@ class Options:
54
55
  model: str | None = None
55
56
  context: int | str | None = None
56
57
  permission_mode: str | None = None # ask, plan, auto-edit or yolo
58
+ mode: str | None = None # code, image or video
57
59
  allowed_tools: list[str] | None = None # only these tools (default: all)
58
60
  max_steps: int | None = None # model steps per request
59
61
  think: bool | None = None
@@ -129,6 +131,24 @@ def switch(wanted: bool | None, configured: str, default_on: str) -> str:
129
131
  return configured if configured != "off" else default_on
130
132
 
131
133
 
134
+ def coding_model(ollama, options: Options, cfg: dict, requested: int | None, hw: Hardware, console: Console):
135
+ """The coding model to use, its catalog entry and the context window; SetupError when there's none."""
136
+ from lcode import cli
137
+
138
+ check_server(ollama, hw)
139
+ try:
140
+ model, spec = cli.resolve_model(ollama, options.model or cfg["model"])
141
+ except cli.NotInstalled as e:
142
+ first_run = not options.model and cfg["model"] == config.DEFAULT_MODEL and not config.CONFIG_PATH.exists()
143
+ if first_run and not backends.is_openai(ollama):
144
+ raise SetupError("lcode isn't set up yet. Run: lcode setup") from e
145
+ raise SetupError(str(e)) from e
146
+ context, note = cli.choose_context(ollama, model, spec, requested, hw)
147
+ if note:
148
+ console.print(f"[yellow]Context {config.format_tokens(context)}: {note}[/]")
149
+ return model, spec, context
150
+
151
+
132
152
  def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | None = None) -> Agent:
133
153
  """An agent set up the way `lcode` sets up a session: model, context, hooks, language servers, MCP."""
134
154
  from lcode import catalog, cli, extensions, hooks
@@ -152,17 +172,14 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
152
172
  except OllamaError as e:
153
173
  raise SetupError(str(e)) from e
154
174
  hw = hw or detect()
155
- check_server(ollama, hw)
175
+ no_coding_model = "" # why, when image or video mode starts without one
156
176
  try:
157
- model, spec = cli.resolve_model(ollama, options.model or cfg["model"])
158
- except cli.NotInstalled as e:
159
- first_run = not options.model and cfg["model"] == config.DEFAULT_MODEL and not config.CONFIG_PATH.exists()
160
- if first_run and not backends.is_openai(ollama):
161
- raise SetupError("lcode isn't set up yet. Run: lcode setup") from e
162
- raise SetupError(str(e)) from e
163
- context, note = cli.choose_context(ollama, model, spec, requested, hw)
164
- if note:
165
- console.print(f"[yellow]Context {config.format_tokens(context)}: {note}[/]")
177
+ model, spec, context = coding_model(ollama, options, cfg, requested, hw, console)
178
+ except SetupError as e:
179
+ if (options.mode or cfg["mode"]) == "code":
180
+ raise
181
+ # Image and video mode don't need the coding model: start without it, and say so on /mode code.
182
+ model, spec, context, no_coding_model = options.model or cfg["model"], None, requested or 32768, str(e)
166
183
  num_batch = cfg["num_batch"]
167
184
  if num_batch is None and spec and spec.num_batch:
168
185
  if model == spec.local_name:
@@ -179,6 +196,10 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
179
196
  think=cfg["think"] if options.think is None else options.think,
180
197
  show_thinking=options.show_thinking,
181
198
  permission_mode=options.permission_mode or cfg["permission_mode"],
199
+ mode=options.mode or cfg["mode"],
200
+ comfyui_url=cfg["comfyui_url"],
201
+ comfyui_path=cfg["comfyui_path"],
202
+ media_dir=cfg["media_dir"],
182
203
  web=switch(options.web, cfg["web"], "on"),
183
204
  search_backend=cfg["search_backend"],
184
205
  searxng_url=cfg["searxng_url"],
@@ -208,6 +229,7 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
208
229
  )
209
230
  agent = Agent(ollama, settings, cwd, console=console)
210
231
  agent.hardware = hw
232
+ agent.no_coding_model = no_coding_model
211
233
  agent.autosave = True
212
234
  agent.interactive = options.interactive
213
235
  if options.max_steps:
@@ -247,15 +269,25 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
247
269
 
248
270
 
249
271
  def close_agent(agent: Agent) -> None:
272
+ """Stop what lcode started (background jobs, servers, the sandbox, ComfyUI), then say so: the terminal may be
273
+ gone already (closed), and saying it then fails."""
274
+ from lcode import media
275
+
276
+ said = []
250
277
  stopped = agent.jobs.stop_all()
251
278
  if stopped:
252
- agent.console.print(f"[dim]Stopped {stopped} background job(s).[/]")
279
+ said.append(f"Stopped {stopped} background job(s).")
280
+ if media.stop_engine(agent):
281
+ said.append("Stopped ComfyUI.")
253
282
  if agent.lsp is not None:
254
283
  agent.lsp.close()
255
284
  if agent.mcp:
256
285
  agent.mcp.close()
257
286
  if agent.sandbox:
258
287
  agent.sandbox.stop()
288
+ for line in said:
289
+ with contextlib.suppress(OSError):
290
+ agent.console.print(f"[dim]{line}[/]")
259
291
 
260
292
 
261
293
  # ----------------------------------------------------------------------------- running