lcode-cli 0.37.0__tar.gz → 0.39.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 (181) hide show
  1. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/CHANGELOG.md +56 -1
  2. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/PKG-INFO +1 -1
  3. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/configuration.md +2 -0
  4. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/how-it-works.md +4 -2
  5. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/mcp.md +5 -81
  6. lcode_cli-0.39.0/docs/modes.md +58 -0
  7. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/quickstart.md +1 -1
  8. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/usage.md +28 -4
  9. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/mkdocs.yml +1 -0
  10. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/__init__.py +1 -1
  11. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/agent.py +37 -4
  12. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/api.py +3 -0
  13. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/checks.py +95 -17
  14. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/cli.py +12 -0
  15. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/config.py +5 -0
  16. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/catalog.toml +0 -48
  17. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/commands.py +6 -0
  18. lcode_cli-0.39.0/src/lcode/modes.py +102 -0
  19. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/repeats.py +11 -2
  20. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/repl.py +57 -10
  21. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/screenshots.py +237 -10
  22. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/tools.py +15 -1
  23. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_checks.py +30 -0
  24. lcode_cli-0.39.0/tests/test_look.py +227 -0
  25. lcode_cli-0.39.0/tests/test_modes.py +154 -0
  26. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_stuck.py +13 -0
  27. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_xai.py +0 -11
  28. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.editorconfig +0 -0
  29. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/CODEOWNERS +0 -0
  30. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  31. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  32. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  33. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
  34. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  35. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/dependabot.yml +0 -0
  36. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/workflows/ci.yml +0 -0
  37. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/workflows/docs.yml +0 -0
  38. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/workflows/release.yml +0 -0
  39. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.gitignore +0 -0
  40. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.pre-commit-config.yaml +0 -0
  41. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/AGENTS.md +0 -0
  42. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/CODE_OF_CONDUCT.md +0 -0
  43. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/CONTRIBUTING.md +0 -0
  44. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/LICENSE +0 -0
  45. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/README.md +0 -0
  46. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/SECURITY.md +0 -0
  47. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/action.yml +0 -0
  48. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/agents.md +0 -0
  49. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/assets/demo.svg +0 -0
  50. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/assets/extra.css +0 -0
  51. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/automation.md +0 -0
  52. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/bench.md +0 -0
  53. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/changelog.md +0 -0
  54. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/commands.md +0 -0
  55. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/contributing.md +0 -0
  56. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/editors.md +0 -0
  57. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/git.md +0 -0
  58. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/hooks.md +0 -0
  59. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/index.md +0 -0
  60. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/installation.md +0 -0
  61. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/lsp.md +0 -0
  62. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/memory.md +0 -0
  63. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/models.md +0 -0
  64. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/sandbox.md +0 -0
  65. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/search.md +0 -0
  66. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/servers.md +0 -0
  67. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/troubleshooting.md +0 -0
  68. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/README.md +0 -0
  69. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/api/fix_tests.py +0 -0
  70. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/commands/review.md +0 -0
  71. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/skills/commit-message/SKILL.md +0 -0
  72. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
  73. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/install.sh +0 -0
  74. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/overrides/main.html +0 -0
  75. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/pyproject.toml +0 -0
  76. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/scripts/make_logos.py +0 -0
  77. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/scripts/record_demo.py +0 -0
  78. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/scripts/social-preview.html +0 -0
  79. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/__main__.py +0 -0
  80. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/acp.py +0 -0
  81. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/action.py +0 -0
  82. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/backends.py +0 -0
  83. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/bench.py +0 -0
  84. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/bench_backend.py +0 -0
  85. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/bench_runtime.py +0 -0
  86. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/catalog.py +0 -0
  87. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/checkpoints.py +0 -0
  88. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/clipboard.py +0 -0
  89. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/cloud.py +0 -0
  90. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/codesearch.py +0 -0
  91. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/context.py +0 -0
  92. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/escalate.py +0 -0
  93. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/extensions.py +0 -0
  94. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/frontmatter.py +0 -0
  95. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/gitflow.py +0 -0
  96. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/hardware.py +0 -0
  97. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/hooks.py +0 -0
  98. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/jobs.py +0 -0
  99. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/limits.py +0 -0
  100. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/lsp.py +0 -0
  101. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/matching.py +0 -0
  102. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/__init__.py +0 -0
  103. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/auth.py +0 -0
  104. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/catalog.py +0 -0
  105. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/client.py +0 -0
  106. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/config.py +0 -0
  107. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/http.py +0 -0
  108. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/manager.py +0 -0
  109. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/protocol.py +0 -0
  110. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/server.py +0 -0
  111. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/stdio.py +0 -0
  112. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/memory.py +0 -0
  113. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/models.toml +0 -0
  114. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/notify.py +0 -0
  115. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/ollama.py +0 -0
  116. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/permissions.py +0 -0
  117. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/planning.py +0 -0
  118. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/render.py +0 -0
  119. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/repomap.py +0 -0
  120. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/residency.py +0 -0
  121. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/review.py +0 -0
  122. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/sandbox.py +0 -0
  123. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/secretscan.py +0 -0
  124. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/__init__.py +0 -0
  125. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/encord.py +0 -0
  126. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/valohai.py +0 -0
  127. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/xai.py +0 -0
  128. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/sessions.py +0 -0
  129. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/steer.py +0 -0
  130. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/subagents.py +0 -0
  131. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/unreachable.py +0 -0
  132. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/update.py +0 -0
  133. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/vision.py +0 -0
  134. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/web.py +0 -0
  135. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/conftest.py +0 -0
  136. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/fake_lsp_server.py +0 -0
  137. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/fake_mcp_server.py +0 -0
  138. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_acp.py +0 -0
  139. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_action.py +0 -0
  140. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_agent.py +0 -0
  141. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_api.py +0 -0
  142. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_backends.py +0 -0
  143. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_bench.py +0 -0
  144. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_catalog.py +0 -0
  145. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_checkpoints.py +0 -0
  146. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_cli.py +0 -0
  147. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_clipboard.py +0 -0
  148. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_cloud.py +0 -0
  149. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_codesearch.py +0 -0
  150. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_config.py +0 -0
  151. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_context.py +0 -0
  152. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_context_picker.py +0 -0
  153. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_escalate.py +0 -0
  154. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_extensions.py +0 -0
  155. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_gitflow.py +0 -0
  156. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_hooks.py +0 -0
  157. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_jobs.py +0 -0
  158. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_lsp.py +0 -0
  159. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_matching.py +0 -0
  160. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_mcp.py +0 -0
  161. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_memory.py +0 -0
  162. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_model_picker.py +0 -0
  163. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_permissions.py +0 -0
  164. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_plan_mode.py +0 -0
  165. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_repeats.py +0 -0
  166. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_repomap.py +0 -0
  167. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_residency.py +0 -0
  168. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_review.py +0 -0
  169. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_robustness.py +0 -0
  170. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_sandbox.py +0 -0
  171. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_screenshots.py +0 -0
  172. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_secretscan.py +0 -0
  173. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_servers.py +0 -0
  174. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_sessions.py +0 -0
  175. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_steer.py +0 -0
  176. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_subagents.py +0 -0
  177. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_tools.py +0 -0
  178. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_update.py +0 -0
  179. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_vision.py +0 -0
  180. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_web.py +0 -0
  181. {lcode_cli-0.37.0 → lcode_cli-0.39.0}/uv.lock +0 -0
@@ -6,6 +6,59 @@ All notable changes to lcode are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.39.0] - 2026-10-23
10
+
11
+ ### Added
12
+
13
+ - **Modes: code, image and video** (#148). `/mode image`, `/mode video` and `/mode code` switch
14
+ what lcode makes. `lcode --mode` starts in one, and the `mode` setting is the default. The
15
+ banner, status bar and prompt show the mode, and sessions keep theirs.
16
+ - **The GPU goes to one model at a time:** switching to image or video mode unloads the coding
17
+ model, and switching back asks ComfyUI to free its memory.
18
+ - **Image and video mode don't generate yet:** they say what's coming (#149–#157) instead of
19
+ sending your words to the coding model. See [Modes](https://nasser1941.github.io/lcode/modes/).
20
+
21
+ ### Changed
22
+
23
+ - **Code mode makes code only.** The MCP servers that generated images and videos (`comfyui`,
24
+ `xai`) are out of the MCP catalog, and lcode doesn't load them in code mode even if they're
25
+ still in `mcp.json` (it says so once). Image and video generation are moving to their own modes.
26
+ - **`/mode` switches code, image and video.** The permission mode is set with `/permissions` now;
27
+ `/mode yolo` and the like still work, with a note. Shift+Tab cycles the permission mode as
28
+ before.
29
+
30
+ ## [0.38.0] - 2026-10-21
31
+
32
+ ### Added
33
+
34
+ - The screenshot tool can click first. For a Python Qt program, `actions` run before the picture,
35
+ the way a user would: `double_click Camera 1` (a widget by its text) or `double_click 300,200`,
36
+ `click`, `right_click`, `key Escape`, `key ctrl+s`, `type …`, `wait 2`. The result says what each
37
+ step hit. Bugs that only show after a click were out of reach before.
38
+ - Qt's warnings are listed at the top of a screenshot's report, taken from everything the program
39
+ printed. In a real run the cause (`setLayout: … which already has a layout`) was there, among 30
40
+ lines of the program's logs, and the model looked past it.
41
+
42
+ ### Fixed
43
+
44
+ From a real session that "fixed" a double-click feature four times without once looking at the
45
+ window, while the user saw a blank screen each time:
46
+
47
+ - **Windows it never looked at** are sent back once before the answer counts. When a request
48
+ changed a Python file that draws a window (PyQt, PySide, tkinter, Kivy, wx, GTK) and the model
49
+ never took a screenshot or looked at an image, it's told to look (with clicks first, if that's
50
+ where the change shows) or to say plainly that it's unchecked. The real cause was in what Qt
51
+ printed: `setLayout: … which already has a layout`.
52
+ - **A rerun that only took a different time** counts as a repeat: the model ran the same tests four
53
+ times in a row (`75 passed in 0.14s`, then `0.12s`…) without the repeat check noticing.
54
+ - **A reply lcode drops** for going round in circles is saved, and the message says where, so you
55
+ can read what the model was thinking instead of taking the stop on trust.
56
+ - **The checks use the project's own Python.** They ran `python -m …` from PATH, which doesn't have
57
+ the project's dependencies, so a project with a `.venv` "failed" its checks on imports. Now
58
+ `.venv/bin/python` (or `venv`, `env`) runs them.
59
+ - **Tests written for pytest get pytest** when the project has no pytest configuration: the tests
60
+ import it, or a requirements file lists it. They used to get `unittest`, which can't run them.
61
+
9
62
  ## [0.37.0] - 2026-10-20
10
63
 
11
64
  ### Added
@@ -823,7 +876,9 @@ First public release.
823
876
  - `AGENTS.md` project instructions and `/init` to generate them.
824
877
  - One-line installer for Linux and macOS.
825
878
 
826
- [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.37.0...HEAD
879
+ [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.39.0...HEAD
880
+ [0.39.0]: https://github.com/nasser1941/lcode/compare/v0.38.0...v0.39.0
881
+ [0.38.0]: https://github.com/nasser1941/lcode/compare/v0.37.0...v0.38.0
827
882
  [0.37.0]: https://github.com/nasser1941/lcode/compare/v0.36.0...v0.37.0
828
883
  [0.36.0]: https://github.com/nasser1941/lcode/compare/v0.35.1...v0.36.0
829
884
  [0.35.1]: https://github.com/nasser1941/lcode/compare/v0.35.0...v0.35.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lcode-cli
3
- Version: 0.37.0
3
+ Version: 0.39.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/
@@ -23,6 +23,8 @@ 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 |
26
28
  | `think` | `true` | Let the model reason before answering |
27
29
  | `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
28
30
  | `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
@@ -56,7 +56,8 @@ same tool, the same arguments and the same result.
56
56
  `loop`.
57
57
 
58
58
  A result that changes isn't a repeat, so a test that's run again after a fix, or a background job
59
- that's checked while it runs, never adds up. A call that may have changed something, like an edit or
59
+ that's checked while it runs, never adds up. Timings don't count as a change: `75 passed in 0.12s`
60
+ and then `in 0.14s` is the same result. A call that may have changed something, like an edit or
60
61
  a shell command that isn't read-only, starts the count again for the other calls: checking the build
61
62
  after each edit is fine. Change the threshold with `lcode config set repeat_limit 5`, or turn it off
62
63
  with `0` (that also turns off the checks for unreachable sites and for thinking in circles, below).
@@ -85,7 +86,8 @@ aren't tracked.
85
86
  Small models sometimes think in circles: a few paragraphs, over and over, until the context is full.
86
87
  That takes minutes behind the spinner and ends without an answer. lcode watches the thinking as it
87
88
  streams, and when its sentences keep repeating, it stops the model, drops that reply and asks for a
88
- short one: the next tool call, or what's blocking it. An empty reply, or one that filled the context
89
+ short one: the next tool call, or what's blocking it. The dropped reply is saved, and the message
90
+ says where, so you can read what went round in circles. An empty reply, or one that filled the context
89
91
  before it said anything, gets the same second chance. The second time in a request, lcode stops it
90
92
  (status `loop`), offering the [stronger model](usage.md#a-stronger-model-when-stuck) first when one
91
93
  is set. An answer the context cut off midway is kept, with a warning to `/compact` or use a larger
@@ -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,58 @@
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
+ image ❯
16
+ ```
17
+
18
+ - **Switching:** `/mode code`, `/mode image` and `/mode video` switch in a session. `lcode --mode image`
19
+ starts in one, and `lcode config set mode video` makes it the default. `/mode` alone shows
20
+ the current one.
21
+ - **Where it shows:** the banner, the status bar and the prompt (`image ❯`) show the mode.
22
+ - **Sessions:** a session keeps the mode it was made in, and resuming it brings that mode back.
23
+
24
+ ## Code mode makes code only
25
+
26
+ Code mode is the coding agent, as before, and it no longer generates images or videos. The MCP
27
+ servers that did (`comfyui` and `xai`) are gone from the MCP catalog, and lcode doesn't load them
28
+ in code mode even if they're still in your `mcp.json`. It says so once.
29
+
30
+ ## One model on the GPU at a time
31
+
32
+ Image and video modes don't need the big coding model: the image or video model does the work. A
33
+ small optional model will help with prompts later. Image and video models need the GPU to
34
+ themselves:
35
+
36
+ - **Into image or video mode:** lcode unloads its coding model from Ollama.
37
+ - **Back to code mode:** lcode asks ComfyUI to free its memory (at `comfyui_url`, by default
38
+ `http://127.0.0.1:8188`), then loads the coding model again in the background.
39
+
40
+ ## Coming next
41
+
42
+ Image and video modes don't generate yet. Until the items below arrive, a request in those modes
43
+ says what's coming instead of going anywhere.
44
+
45
+ - **#149 Local generation engine:** lcode drives ComfyUI itself: finding or installing it, jobs,
46
+ progress, cancelling, and saving outputs with their settings.
47
+ - **#150 Media model catalog:** image and video models that fit your machine, installed on
48
+ demand.
49
+ - **#151, #152 Image mode:** generating, variations, sizes and seeds, a gallery, editing,
50
+ references, inpainting, upscaling.
51
+ - **#153, #154 Video mode:** from text or a start image, first and last frames, several
52
+ references, camera moves, longer clips, sound.
53
+ - **#155 Movies:** storyboards with a consistent cast, shots stitched into one film.
54
+ - **#156 Prompt assistant:** a small local model for prompts and follow-ups.
55
+ - **#157 Cloud generation**, with costs shown (Grok Imagine first).
56
+
57
+ The permission mode (`ask`, `plan`, `auto-edit`, `yolo`) is a different setting: `/permissions`
58
+ sets it, and Shift+Tab cycles it as before.
@@ -12,7 +12,7 @@ lcode
12
12
 
13
13
  ```text
14
14
  ╭──────────────────────────────────────────────╮
15
- │ lcode v0.37.0 — local coding agent │
15
+ │ lcode v0.39.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 |
@@ -170,7 +171,9 @@ runs the project's checks itself before the answer is final:
170
171
  - **Programs** using `lcode -p --output json` see the runs in the result's `checks`.
171
172
 
172
173
  **Which checks.** lcode looks at the project: pytest when the configuration mentions it (`uv run
173
- pytest -q` with a `uv.lock`), otherwise `python -m unittest` for `test_*.py` files. Then
174
+ pytest -q` with a `uv.lock`), when the tests import it, or when a requirements file lists it;
175
+ otherwise `python -m unittest` for `test_*.py` files. Python checks run with the project's own
176
+ environment when it has one (`.venv/bin/python`, `venv`, `env`), where its dependencies are. Then
174
177
  `npm test` (or pnpm, yarn, bun) when `package.json` has a test script, `cargo test`, `go test ./...`
175
178
  and `make test`. To choose the command yourself, set it for the repository in
176
179
  `.lcode/settings.toml`. That file is used once you trust the repository, like its hooks:
@@ -183,6 +186,13 @@ command = "uv run pytest -q && uv run ruff check ."
183
186
  Or set it for yourself with `lcode config set checks "make check"`; `lcode config set checks off`
184
187
  turns checks off.
185
188
 
189
+ **Windows it never looked at.** A model that changes a window checks it with an import and the
190
+ project's tests, which don't show what's on the screen, and calls it done: you find a blank
191
+ window. When a request changed a Python file that draws one (it imports PyQt, PySide, tkinter,
192
+ Kivy, wx or GTK) and the model never took a [screenshot](#screenshots) or looked at an image,
193
+ lcode sends it back once: look at it, with clicks first if that's where the change shows, or say
194
+ plainly that it's unchecked. `checks = "off"` turns this off.
195
+
186
196
  **Scripts it never ran.** A model that writes a build or install script tends to call it done
187
197
  without running it once, and the first thing you see is its first error. When a request wrote a
188
198
  shell script (a `.sh` file, or a file with a shell shebang) and no command in it ran the script,
@@ -306,7 +316,7 @@ stays loaded:
306
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 |
307
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 |
308
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 …)` |
309
- | 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.` |
310
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 |
311
321
 
312
322
  While the model loads in the background at startup, the status bar says
@@ -427,6 +437,20 @@ a screen and sees what you would, measured:
427
437
 
428
438
  - For a web app, the model gives the command that starts the server and the page's URL: lcode
429
439
  starts the server, waits for the page to answer, takes the shot and stops the server.
440
+ - **Clicks and keys first.** For something that only shows after a click, a Python Qt program can
441
+ take `actions` before the picture, the way you would: `double_click 300,200` (a point in the
442
+ window), `double_click Camera 1` (the widget showing that text), `click Save`, `right_click …`,
443
+ `key Escape`, `key ctrl+s`, `type some text`, `wait 2`. The result says what each step hit.
444
+ - **Qt's warnings come first.** Qt names many layout mistakes in what the program prints, often
445
+ among the program's own logs. lcode lists them at the top of the report:
446
+
447
+ ```text
448
+ ● screenshot(python camera_wall.py) actions: double_click Camera 1
449
+ Qt printed warnings (they often name the cause):
450
+ - QWidget::setLayout: Attempting to set QLayout "" on FocusedCamera "", which already has a layout
451
+ Steps, before the picture:
452
+ - double_click Camera 1: on QLabel "Camera 1 — Connected" at (207, 317)
453
+ ```
430
454
  - A model that can see describes the picture with the model's question in mind (see
431
455
  [Which model looks](#images)). Without one, the measurements still come back.
432
456
  - Shots are saved in `.lcode/screenshots/` in the project, which keeps itself out of git, so you
@@ -447,7 +471,7 @@ a screen and sees what you would, measured:
447
471
  | `auto-edit` | automatic | ask, except read-only commands |
448
472
  | `yolo` | automatic | automatic |
449
473
 
450
- ++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
451
475
  `--yolo` start in one.
452
476
 
453
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.37.0"
6
+ __version__ = "0.39.0"
7
7
 
8
8
  API = ("Session", "Result", "Options", "SetupError")
9
9
 
@@ -99,7 +99,9 @@ def stall_note(trouble: str) -> str:
99
99
  }[trouble]
100
100
  return (
101
101
  f"[lcode] {first} Don't deliberate at length now. Either make your next tool call, or, if you're stuck, "
102
- "tell the user in a few lines what you did, what fails, and what they could do."
102
+ "tell the user in a few lines what you did, what fails, and what they could do. If you were about to check "
103
+ "something by running it, run it now instead of reasoning about it: for a window, take a screenshot (with "
104
+ "actions for clicks or keys)."
103
105
  )
104
106
 
105
107
 
@@ -302,6 +304,8 @@ class Progress:
302
304
  class Settings:
303
305
  model: str # Ollama model name to call
304
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"
305
309
  num_batch: int | None = None
306
310
  keep_alive: str = "30m"
307
311
  think: bool = True
@@ -678,6 +682,7 @@ class Agent:
678
682
  data = {
679
683
  "cwd": str(self.workspace), # where `lcode -c` there finds it, wherever the shell moved
680
684
  "model": self.settings.model,
685
+ "mode": self.settings.mode,
681
686
  "name": self.session_name,
682
687
  "title": self.session_title,
683
688
  "messages": self.messages,
@@ -724,6 +729,8 @@ class Agent:
724
729
  self.session_id = info.id
725
730
  self.checkpoints.load(info.id, data.get("checkpoints") or [])
726
731
  self.session_name = data.get("name", "")
732
+ if data.get("mode") in ("code", "image", "video"):
733
+ self.settings.mode = data["mode"] # a session belongs to the mode it was made in
727
734
  self.session_title = data.get("title") or sessions.title_from(self.messages)
728
735
  self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
729
736
  self.tools.todos = list(data.get("todos") or [])
@@ -961,6 +968,12 @@ class Agent:
961
968
  return []
962
969
  return checks.unrun_scripts(project_root_or_cwd(self.cwd), list(self.tools.before), commands)
963
970
 
971
+ def unseen_windows(self, tools_used: set[str]) -> dict[str, str]:
972
+ """Window code the model changed in this request and never looked at (only where the checks would run)."""
973
+ if self.allowed_tools is not None or self.planning() or self.no_changes or self.settings.checks == "off":
974
+ return {}
975
+ return checks.unseen_windows(project_root_or_cwd(self.cwd), list(self.tools.before), tools_used)
976
+
964
977
  def project_check(self) -> checks.Check | None:
965
978
  """The project's check command, for the main agent outside plan mode and reviews (None otherwise)."""
966
979
  if self.allowed_tools is not None or self.planning() or self.no_changes or self.settings.checks == "off":
@@ -1164,6 +1177,8 @@ class Agent:
1164
1177
  stalls = 0 # replies that went round in circles or ended without a word
1165
1178
  commands: list[str] = [] # shell commands run in this request
1166
1179
  scripts_noted = False # the model was told about scripts it wrote and never ran
1180
+ tools_used: set[str] = set() # tools called in this request
1181
+ windows_noted = False # the model was told about windows it changed and never looked at
1167
1182
  for step in range(self.max_steps):
1168
1183
  if self.cancel is not None and self.cancel.is_set():
1169
1184
  raise KeyboardInterrupt
@@ -1215,10 +1230,11 @@ class Agent:
1215
1230
  continue
1216
1231
  if not calls and (self.reply_trouble == "circles" or not (reply.get("content") or "").strip()):
1217
1232
  stalls += 1
1218
- self.messages.pop() # nothing to keep: no answer, and circles would only fill the context
1233
+ dropped = self.messages.pop() # no answer, and circles would only fill the context
1234
+ kept = self.keep_dropped(dropped)
1219
1235
  if stalls < MAX_STALLS:
1220
1236
  self.console.print(
1221
- Text(f" ⎿ {STALLED[self.reply_trouble]}: asking for a short answer", style="yellow")
1237
+ Text(f" ⎿ {STALLED[self.reply_trouble]}: asking for a short answer{kept}", style="yellow")
1222
1238
  )
1223
1239
  self.messages.append({"role": "user", "content": stall_note(self.reply_trouble)})
1224
1240
  continue
@@ -1234,7 +1250,7 @@ class Agent:
1234
1250
  self.console.print(
1235
1251
  Text(
1236
1252
  "Stopped: the model's replies went round in circles or ended without an answer, twice in "
1237
- "this request. Try again with a smaller step, or tell it what to do differently.",
1253
+ f"this request{kept}. Try again with a smaller step, or tell it what to do differently.",
1238
1254
  style="yellow",
1239
1255
  )
1240
1256
  )
@@ -1253,6 +1269,14 @@ class Agent:
1253
1269
  self.console.print(Text(f" ⎿ never ran {', '.join(unrun)}: asking the model to", style="yellow"))
1254
1270
  self.messages.append({"role": "user", "content": checks.unrun_note(unrun)})
1255
1271
  continue
1272
+ unseen = {} if windows_noted else self.unseen_windows(tools_used)
1273
+ if unseen:
1274
+ windows_noted = True
1275
+ self.console.print(
1276
+ Text(f" ⎿ never looked at {', '.join(unseen)}: asking the model to", style="yellow")
1277
+ )
1278
+ self.messages.append({"role": "user", "content": checks.unseen_note(unseen)})
1279
+ continue
1256
1280
  check = self.project_check() if changed and not verified else None
1257
1281
  if check is not None and check_rounds < checks.MAX_ROUNDS:
1258
1282
  check_rounds += 1
@@ -1345,6 +1369,7 @@ class Agent:
1345
1369
  )
1346
1370
  if out_of_reach.stop:
1347
1371
  blocked = out_of_reach
1372
+ tools_used.add(name)
1348
1373
  if name == "bash":
1349
1374
  commands.append(str(args.get("command", "")))
1350
1375
  if name in ("edit_file", "write_file", "apply_patch") and not result.startswith(REFUSED):
@@ -1412,6 +1437,14 @@ class Agent:
1412
1437
  self.turn_status = "success"
1413
1438
  self.print_stats()
1414
1439
 
1440
+ def keep_dropped(self, reply: dict) -> str:
1441
+ """Save a reply lcode dropped (its thinking too), so the user can see what went round in circles."""
1442
+ text = "\n\n".join(part for part in (reply.get("thinking"), reply.get("content")) if part)
1443
+ if not text.strip():
1444
+ return ""
1445
+ path = context_tools.save_output(text, "dropped reply")
1446
+ return f" (the dropped reply is in {path})" if path else ""
1447
+
1415
1448
  def can_escalate(self) -> bool:
1416
1449
  """Whether a stronger model may be asked here: one is set, and this isn't a subagent or plan mode."""
1417
1450
  model = (self.settings.escalate_model or "").strip().lower()
@@ -54,6 +54,7 @@ class Options:
54
54
  model: str | None = None
55
55
  context: int | str | None = None
56
56
  permission_mode: str | None = None # ask, plan, auto-edit or yolo
57
+ mode: str | None = None # code, image or video
57
58
  allowed_tools: list[str] | None = None # only these tools (default: all)
58
59
  max_steps: int | None = None # model steps per request
59
60
  think: bool | None = None
@@ -179,6 +180,8 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
179
180
  think=cfg["think"] if options.think is None else options.think,
180
181
  show_thinking=options.show_thinking,
181
182
  permission_mode=options.permission_mode or cfg["permission_mode"],
183
+ mode=options.mode or cfg["mode"],
184
+ comfyui_url=cfg["comfyui_url"],
182
185
  web=switch(options.web, cfg["web"], "on"),
183
186
  search_backend=cfg["search_backend"],
184
187
  searxng_url=cfg["searxng_url"],