lcode-cli 0.41.0__tar.gz → 0.43.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 (199) hide show
  1. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/CHANGELOG.md +34 -1
  2. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/PKG-INFO +1 -1
  3. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/configuration.md +1 -0
  4. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/modes.md +97 -13
  5. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/quickstart.md +1 -1
  6. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/usage.md +2 -0
  7. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/__init__.py +1 -1
  8. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/agent.py +15 -1
  9. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/api.py +1 -0
  10. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/cli.py +14 -9
  11. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/comfy.py +8 -5
  12. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/config.py +6 -0
  13. lcode_cli-0.43.0/src/lcode/edits.py +283 -0
  14. lcode_cli-0.43.0/src/lcode/imageopts.py +207 -0
  15. lcode_cli-0.43.0/src/lcode/media.py +683 -0
  16. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/media.toml +43 -2
  17. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/media_catalog.py +21 -0
  18. lcode_cli-0.43.0/src/lcode/preview.py +152 -0
  19. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/repl.py +177 -3
  20. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/sessions.py +2 -2
  21. lcode_cli-0.43.0/src/lcode/workflows/remove-background.json +13 -0
  22. lcode_cli-0.43.0/src/lcode/workflows/upscale-esrgan.json +12 -0
  23. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/comfy_standin.py +19 -1
  24. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_comfy.py +7 -7
  25. lcode_cli-0.43.0/tests/test_image_edit.py +214 -0
  26. lcode_cli-0.43.0/tests/test_image_generate.py +253 -0
  27. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_media_catalog.py +12 -3
  28. lcode_cli-0.41.0/src/lcode/media.py +0 -293
  29. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.editorconfig +0 -0
  30. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/CODEOWNERS +0 -0
  31. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  32. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  33. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  34. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
  35. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  36. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/dependabot.yml +0 -0
  37. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/workflows/ci.yml +0 -0
  38. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/workflows/docs.yml +0 -0
  39. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.github/workflows/release.yml +0 -0
  40. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.gitignore +0 -0
  41. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/.pre-commit-config.yaml +0 -0
  42. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/AGENTS.md +0 -0
  43. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/CODE_OF_CONDUCT.md +0 -0
  44. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/CONTRIBUTING.md +0 -0
  45. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/LICENSE +0 -0
  46. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/README.md +0 -0
  47. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/SECURITY.md +0 -0
  48. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/action.yml +0 -0
  49. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/agents.md +0 -0
  50. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/assets/demo.svg +0 -0
  51. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/assets/extra.css +0 -0
  52. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/automation.md +0 -0
  53. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/bench.md +0 -0
  54. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/changelog.md +0 -0
  55. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/commands.md +0 -0
  56. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/contributing.md +0 -0
  57. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/editors.md +0 -0
  58. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/git.md +0 -0
  59. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/hooks.md +0 -0
  60. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/how-it-works.md +0 -0
  61. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/index.md +0 -0
  62. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/installation.md +0 -0
  63. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/lsp.md +0 -0
  64. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/mcp.md +0 -0
  65. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/memory.md +0 -0
  66. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/models.md +0 -0
  67. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/sandbox.md +0 -0
  68. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/search.md +0 -0
  69. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/servers.md +0 -0
  70. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/docs/troubleshooting.md +0 -0
  71. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/examples/README.md +0 -0
  72. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/examples/api/fix_tests.py +0 -0
  73. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/examples/commands/review.md +0 -0
  74. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/examples/skills/commit-message/SKILL.md +0 -0
  75. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
  76. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/install.sh +0 -0
  77. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/mkdocs.yml +0 -0
  78. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/overrides/main.html +0 -0
  79. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/pyproject.toml +0 -0
  80. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/scripts/make_logos.py +0 -0
  81. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/scripts/record_demo.py +0 -0
  82. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/scripts/social-preview.html +0 -0
  83. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/__main__.py +0 -0
  84. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/acp.py +0 -0
  85. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/action.py +0 -0
  86. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/backends.py +0 -0
  87. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/bench.py +0 -0
  88. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/bench_backend.py +0 -0
  89. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/bench_runtime.py +0 -0
  90. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/catalog.py +0 -0
  91. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/checkpoints.py +0 -0
  92. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/checks.py +0 -0
  93. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/clipboard.py +0 -0
  94. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/cloud.py +0 -0
  95. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/codesearch.py +0 -0
  96. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/context.py +0 -0
  97. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/escalate.py +0 -0
  98. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/extensions.py +0 -0
  99. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/frontmatter.py +0 -0
  100. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/gitflow.py +0 -0
  101. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/hardware.py +0 -0
  102. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/hooks.py +0 -0
  103. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/jobs.py +0 -0
  104. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/limits.py +0 -0
  105. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/lsp.py +0 -0
  106. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/matching.py +0 -0
  107. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/__init__.py +0 -0
  108. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/auth.py +0 -0
  109. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/catalog.py +0 -0
  110. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/catalog.toml +0 -0
  111. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/client.py +0 -0
  112. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/commands.py +0 -0
  113. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/config.py +0 -0
  114. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/http.py +0 -0
  115. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/manager.py +0 -0
  116. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/protocol.py +0 -0
  117. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/server.py +0 -0
  118. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/mcp/stdio.py +0 -0
  119. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/memory.py +0 -0
  120. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/models.toml +0 -0
  121. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/modes.py +0 -0
  122. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/notify.py +0 -0
  123. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/ollama.py +0 -0
  124. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/permissions.py +0 -0
  125. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/planning.py +0 -0
  126. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/render.py +0 -0
  127. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/repeats.py +0 -0
  128. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/repomap.py +0 -0
  129. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/residency.py +0 -0
  130. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/review.py +0 -0
  131. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/sandbox.py +0 -0
  132. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/screenshots.py +0 -0
  133. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/secretscan.py +0 -0
  134. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/servers/__init__.py +0 -0
  135. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/servers/encord.py +0 -0
  136. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/servers/valohai.py +0 -0
  137. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/servers/xai.py +0 -0
  138. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/steer.py +0 -0
  139. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/subagents.py +0 -0
  140. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/tools.py +0 -0
  141. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/unreachable.py +0 -0
  142. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/update.py +0 -0
  143. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/vision.py +0 -0
  144. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/web.py +0 -0
  145. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/workflows/__init__.py +0 -0
  146. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/workflows/flux2-klein-4b-t2i.json +0 -0
  147. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/src/lcode/workflows/z-image-turbo-t2i.json +0 -0
  148. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/conftest.py +0 -0
  149. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/fake_lsp_server.py +0 -0
  150. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/fake_mcp_server.py +0 -0
  151. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_acp.py +0 -0
  152. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_action.py +0 -0
  153. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_agent.py +0 -0
  154. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_api.py +0 -0
  155. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_backends.py +0 -0
  156. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_bench.py +0 -0
  157. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_catalog.py +0 -0
  158. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_checkpoints.py +0 -0
  159. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_checks.py +0 -0
  160. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_cli.py +0 -0
  161. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_clipboard.py +0 -0
  162. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_cloud.py +0 -0
  163. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_codesearch.py +0 -0
  164. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_config.py +0 -0
  165. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_context.py +0 -0
  166. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_context_picker.py +0 -0
  167. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_escalate.py +0 -0
  168. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_extensions.py +0 -0
  169. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_gitflow.py +0 -0
  170. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_hooks.py +0 -0
  171. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_jobs.py +0 -0
  172. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_look.py +0 -0
  173. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_lsp.py +0 -0
  174. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_matching.py +0 -0
  175. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_mcp.py +0 -0
  176. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_memory.py +0 -0
  177. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_model_picker.py +0 -0
  178. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_modes.py +0 -0
  179. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_permissions.py +0 -0
  180. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_plan_mode.py +0 -0
  181. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_repeats.py +0 -0
  182. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_repomap.py +0 -0
  183. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_residency.py +0 -0
  184. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_review.py +0 -0
  185. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_robustness.py +0 -0
  186. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_sandbox.py +0 -0
  187. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_screenshots.py +0 -0
  188. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_secretscan.py +0 -0
  189. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_servers.py +0 -0
  190. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_sessions.py +0 -0
  191. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_steer.py +0 -0
  192. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_stuck.py +0 -0
  193. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_subagents.py +0 -0
  194. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_tools.py +0 -0
  195. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_update.py +0 -0
  196. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_vision.py +0 -0
  197. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_web.py +0 -0
  198. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/tests/test_xai.py +0 -0
  199. {lcode_cli-0.41.0 → lcode_cli-0.43.0}/uv.lock +0 -0
@@ -6,6 +6,37 @@ All notable changes to lcode are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.43.0] - 2026-10-24
10
+
11
+ ### Added
12
+
13
+ - **Image mode edits pictures** (#152). See [Editing](https://nasser1941.github.io/lcode/modes/#editing).
14
+ - **Edit by instruction** (`/edit 3 make the sky purple`, or `@photo.png make the sky purple`)
15
+ and **references** (`@photo.png @#3 put the mug from image 1 on the table in image 2`), with
16
+ FLUX.2 klein 4B. `/use 3` makes every next request follow an image until `/use off`.
17
+ - **Inpainting** (`--mask mask.png`: only the white part changes) and **outpainting** (`/extend 3
18
+ --ar 16:9`, or by sides: only the new border is painted, the original stays as it was).
19
+ - **`/upscale 3`** (Real-ESRGAN, 2× or 4×) and **`/nobg 3`** (BiRefNet, a transparent PNG), installed
20
+ on first use. `/vary` on an edit makes new edits of the same images.
21
+ - ComfyUI's reasons for refusing a workflow now include its explanation, not just the input's name.
22
+
23
+ ## [0.42.0] - 2026-10-24
24
+
25
+ ### Added
26
+
27
+ - **Image mode's options, results and follow-ups** (#151). See
28
+ [Image mode](https://nasser1941.github.io/lcode/modes/#image-mode).
29
+ - **Options in a request** (`a red fox --n 4 --ar 16:9 --seed 42 --quality best --style photo`)
30
+ or for the session (`/options n=4 ar=16:9`): how many images, the shape, the seed (counting up
31
+ over several images), a quality preset (0.5, 1 or 2 megapixels), ten style presets, and a
32
+ negative prompt for models that use one.
33
+ - **Numbered results, shown in the terminal:** the picture itself in kitty, Ghostty, iTerm2 and
34
+ WezTerm, and a half-block colour preview elsewhere (the `preview` setting).
35
+ - **Follow-ups:** `/again` (new seeds), `/vary 3` (four variations of image #3), `/gallery` and
36
+ `/open 3`. The gallery and options are saved with the session.
37
+ - `lcode --mode image -p "… --n 2"` reports every image's file and seed.
38
+ - One image is now named `<time>-<prompt>.png` (no `-1`).
39
+
9
40
  ## [0.41.0] - 2026-10-23
10
41
 
11
42
  ### Added
@@ -923,7 +954,9 @@ First public release.
923
954
  - `AGENTS.md` project instructions and `/init` to generate them.
924
955
  - One-line installer for Linux and macOS.
925
956
 
926
- [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.41.0...HEAD
957
+ [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.43.0...HEAD
958
+ [0.43.0]: https://github.com/nasser1941/lcode/compare/v0.42.0...v0.43.0
959
+ [0.42.0]: https://github.com/nasser1941/lcode/compare/v0.41.0...v0.42.0
927
960
  [0.41.0]: https://github.com/nasser1941/lcode/compare/v0.40.0...v0.41.0
928
961
  [0.40.0]: https://github.com/nasser1941/lcode/compare/v0.39.0...v0.40.0
929
962
  [0.39.0]: https://github.com/nasser1941/lcode/compare/v0.38.0...v0.39.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lcode-cli
3
- Version: 0.41.0
3
+ Version: 0.43.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/
@@ -27,6 +27,7 @@ lcode config path # print the file location
27
27
  | `comfyui_url` | `http://127.0.0.1:8188` | ComfyUI's address, for image and video mode |
28
28
  | `comfyui_path` | (found) | Your ComfyUI folder, if lcode doesn't find it; lcode starts it when needed ([ComfyUI](modes.md#comfyui)) |
29
29
  | `media_dir` | `.lcode/media` | Where images and videos go: a folder in the project, or an absolute path |
30
+ | `preview` | `auto` | Images in the terminal: `auto`, `kitty`, `iterm`, `blocks` (colour text) or `off` ([results](modes.md#results)) |
30
31
  | `image_model` | `flux2-klein-4b` | The model image mode uses: a key from `lcode media` ([image models](modes.md#image-models)) |
31
32
  | `think` | `true` | Let the model reason before answering |
32
33
  | `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
@@ -5,7 +5,7 @@ lcode makes three kinds of things, each in its own mode with its own models:
5
5
  | Mode | What it makes | The model doing the work |
6
6
  |---|---|---|
7
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 |
8
+ | **image** | Images from a description, edits of your images (references, inpainting, outpainting), upscales and cut-outs | An image model, run by ComfyUI on your GPU |
9
9
  | **video** | Videos from a description, a start image or references | A video model, run by ComfyUI on your GPU |
10
10
 
11
11
  ```text
@@ -15,7 +15,7 @@ Unloaded qwen3.6-35b to free the GPU for images.
15
15
  Starting ComfyUI (~/comfy/ComfyUI) in the background.
16
16
  Describe an image and press Enter: FLUX.2 klein 4B makes it on your GPU, into .lcode/media.
17
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
18
+ ◆ #1 .lcode/media/20261023-184531-a-red-fox-sitting-in-fresh-snow-at-dusk.png 1024×1024 · FLUX.2 klein 4B · seed 3714779554 · 8.0 s
19
19
  ```
20
20
 
21
21
  - **Switching:** `/mode code`, `/mode image` and `/mode video` switch in a session. `lcode --mode image`
@@ -33,9 +33,55 @@ in code mode even if they're still in your `mcp.json`. It says so once.
33
33
  ## Image mode
34
34
 
35
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 the image model you choose (see
38
- [Image models](#image-models)). Sizes, seeds and variations come next (#151).
36
+ involved: lcode fills in its own ComfyUI workflow for the image model and follows the job itself,
37
+ with the image model you choose (see [Image models](#image-models)).
38
+
39
+ ```text
40
+ image ❯ a red fox in fresh snow --n 2 --ar 16:9 --seed 7
41
+ ◆ #1 .lcode/media/20261023-194019-a-red-fox-in-fresh-snow.png 1360×768 · FLUX.2 klein 4B · seed 7 · 4.9 s
42
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ (the picture, in colour)
43
+ ◆ #2 .lcode/media/20261023-194024-a-red-fox-in-fresh-snow.png 1360×768 · FLUX.2 klein 4B · seed 8 · 2.7 s
44
+ image ❯ /vary 1 --n 2
45
+ Variations of #1: a red fox in fresh snow
46
+ ◆ #3 …
47
+ ```
48
+
49
+ ### Options
50
+
51
+ Add them to a request, or set them for the session with `/options` (`/options n=4 ar=16:9
52
+ quality=best style=photo`). A flag in a request wins over `/options` for that request. `/options`
53
+ alone shows them.
54
+
55
+ | Flag | `/options` | What it does |
56
+ |---|---|---|
57
+ | `--n 4` | `n=4` | How many images: 1 to 8. Each gets its own seed. |
58
+ | `--ar 16:9` | `ar=16:9` | The shape: a ratio (`3:2`, `9:16`, up to 4:1), a name (`square`, `portrait`, `landscape`, `wide`, `tall`, `photo`, `cinema`), or pixels (`--size 1344x768`). |
59
+ | `--seed 42` | `seed=42` | The same seed and settings make the same image. With `--n`, the seeds count up (42, 43, …). `seed=random` goes back to a new one each time. |
60
+ | `--quality best` | `quality=best` | `fast` (about 0.5 megapixels, fewer steps for models that allow it), `balanced` (1 megapixel, the default), `best` (2 megapixels, more steps where they help). |
61
+ | `--style photo` | `style=photo` | Adds a style to the description: `photo`, `cinematic`, `anime`, `watercolor`, `oil`, `pixel`, `3d`, `sketch`, `comic`, `flat`. `style=none` takes it off. |
62
+ | `--no "text, people"` | `no="text"` | A negative prompt, for models that use one. FLUX.2 klein and Z-Image Turbo don't (they're distilled), and lcode says so: describe what you want instead. |
63
+
64
+ ### Results
65
+
66
+ - **Numbered:** every image in the session gets a number (`#3`), shown with its file, size, model,
67
+ seed and time.
68
+ - **Shown in the terminal:** in kitty, Ghostty, iTerm2 and WezTerm the picture itself appears
69
+ under its line. Other terminals get a small colour preview drawn with half blocks. The
70
+ `preview` setting chooses: `auto`, `kitty`, `iterm`, `blocks` or `off`. Inside tmux or screen,
71
+ lcode draws the half-block preview.
72
+ - **Follow-ups:**
73
+
74
+ | Command | What it does |
75
+ |---|---|
76
+ | `/again` | The last request again with new seeds; flags change it (`/again --n 4 --ar 9:16`) |
77
+ | `/vary 3` | Four variations of image #3: its prompt and options, new seeds (`/vary 3 --n 2`; the last image without a number) |
78
+ | `/gallery` | The session's images with their prompt, size, model and seed; `/gallery all` lists the latest 50 in the media folder |
79
+ | `/open 3` | Opens image #3 in your image viewer (the last one without a number) |
80
+
81
+ - **Sessions:** the gallery and `/options` are saved with the session, so `lcode -c` or `/resume`
82
+ brings them back. An image session is listed under its first prompt.
83
+
84
+ ### While it works
39
85
 
40
86
  - **Progress:** the status line says what ComfyUI is doing (loading the model, reading the
41
87
  prompt, drawing, decoding), the step, the time left and the time so far. If other jobs are ahead
@@ -43,17 +89,54 @@ For now that's one image per request, 1024×1024, with the image model you choos
43
89
  - **Cancel** with Esc or Ctrl+C: lcode cancels the job in ComfyUI too, so it stops using the GPU.
44
90
  - **Type ahead:** what you type while an image is being made waits in the next prompt.
45
91
  - **Files:** each image goes to `.lcode/media/` in the project (the `media_dir` setting), named
46
- after the time and the prompt, with a `.json` next to it holding the prompt, model, seed, size and
47
- steps. PNGs carry the same in their own metadata (an `lcode` text chunk), and ComfyUI's workflow
92
+ after the time and the prompt, with a `.json` next to it holding the prompt, model, seed, size,
93
+ steps, quality and style. PNGs carry the same in their own metadata (an `lcode` text chunk), and ComfyUI's workflow
48
94
  as well. `.lcode/media/` keeps itself out of git; a folder of your own (`lcode config set
49
95
  media_dir assets/generated`) doesn't.
50
96
  - **Nothing is lost:** lcode writes each job down while it runs. If lcode crashes or the terminal
51
97
  closes before an image arrives, the next request in image mode saves it.
52
- - **From scripts:** `lcode --mode image -p "a red fox in the snow"` makes one image and exits;
53
- with `--output json` it prints a result with the file paths, model, seed, size and time.
98
+ - **From scripts:** `lcode --mode image -p "a red fox in the snow --n 2"` makes the images and
99
+ exits; with `--output json` it prints a result with the file paths, the model, the seeds, the size
100
+ and the time.
54
101
  - **No Ollama needed:** `lcode --mode image` starts even when Ollama isn't running or the coding
55
102
  model isn't installed; `/mode code` then says what's missing.
56
103
 
104
+ ### Editing
105
+
106
+ FLUX.2 klein 4B edits pictures as well as making them: it takes images as references and an
107
+ instruction as the prompt. When the chosen model can't edit (Z-Image Turbo), lcode hands edits to
108
+ FLUX.2 klein and says so.
109
+
110
+ ```text
111
+ image ❯ /edit 1 make the mug matte black with a small gold star
112
+ ◆ #2 .lcode/media/20261024-100504-make-the-mug-matte-black-with-a-small-go.png 1024×1024 · FLUX.2 klein 4B · seed 1798225303 · 6.1 s
113
+ image ❯ /extend 1 --ar 16:9
114
+ Extending #1 from 1024×1024 to 1824×1024 before scaling.
115
+ ◆ #4 .lcode/media/…-extended.png 1360×768 · FLUX.2 klein 4B · seed 2994584653 · 6.4 s
116
+ image ❯ /nobg 2
117
+ ◆ #6 .lcode/media/…-nobg.png 1024×1024 · BiRefNet · 0.7 s
118
+ ```
119
+
120
+ | What | How | What happens |
121
+ |---|---|---|
122
+ | **Edit by instruction** | `/edit 3 make the sky purple` (the last image without a number), or `@photo.png make the sky purple` | The image goes in as a reference, the instruction is the prompt. The result keeps the image's shape unless you give one (`--ar`). |
123
+ | **References** | `@photo.png @#3 put the mug from image 1 on the table in image 2`, or `/edit 3 … @style.png` | Several images, in order ("image 1", "image 2" in the instruction): a subject, a style, a place. `@"a file with spaces.png"` works too. |
124
+ | **Keep following an image** | `/use 3` (or `/use photo.png`), then plain requests; `/use off` | Every request is an edit of those images until you stop it: handy for one subject in many scenes. |
125
+ | **Inpaint** | `@photo.png --mask mask.png make the sky purple` | Only where the mask image is white changes (with a soft edge); the rest stays exactly as it was. |
126
+ | **Outpaint** | `/extend 3 --ar 16:9`, or `/extend 3 --left 256 --top 128`, plus words about the new part | The canvas grows, the model paints only the new border, and the original is pasted back as it was. |
127
+ | **Upscale** | `/upscale 3` (2×) or `/upscale 3 4` | Real-ESRGAN, a few seconds: a 1024×1024 image becomes 2048×2048. |
128
+ | **Remove the background** | `/nobg 3` | BiRefNet cuts out the subject: a PNG with a transparent background. |
129
+
130
+ - **Follow-ups work on edits too:** `/vary` on an edit makes new edits of the same images with the
131
+ same instruction, and `/again` repeats the last request, edit or not.
132
+ - **The tools install on first use,** after you agree: Real-ESRGAN (0.07 GB) and BiRefNet (0.44 GB).
133
+ `lcode media` lists them under "Tools for images".
134
+ - **Text in images:** FLUX.2 klein and Z-Image Turbo write short text you put in quotes
135
+ (`a café sign that says "OPEN"`).
136
+ - **Measured** on the RTX 4080 Laptop GPU (12 GB): an edit takes 5 to 7 s, two references 10 s,
137
+ inpainting and outpainting 6 to 12 s, a 2× upscale 6 s, and a background cut-out under a second
138
+ once the tools are loaded.
139
+
57
140
  ## ComfyUI
58
141
 
59
142
  ComfyUI runs the image and video models; lcode drives it through its API.
@@ -123,6 +206,10 @@ lcode media install z-image-turbo # download what's missing (asks first; --yes
123
206
 
124
207
  ### Measured
125
208
 
209
+ On an RTX 4080 Laptop GPU (12 GB), FLUX.2 klein 4B makes a 1360×768 image in 2.6 to 2.8 s and a
210
+ `--quality fast` image (720×720) in 1.3 to 2.0 s once it's loaded.
211
+
212
+
126
213
  On an RTX 4080 Laptop GPU (12 GB) with 32 GB of RAM, ComfyUI 0.38, 1024×1024, four different
127
214
  prompts each:
128
215
 
@@ -149,10 +236,7 @@ themselves:
149
236
 
150
237
  Video mode doesn't generate yet: a request there says what's coming instead of going anywhere.
151
238
 
152
- - **#150 Media model catalog** (image models done; video models come with video generation).
153
- - **#151, #152 Image mode:** generating, variations, sizes and seeds, a gallery, editing,
154
- references, inpainting, upscaling.
155
- - **#153, #154 Video mode:** from text or a start image, first and last frames, several
239
+ - **#153, #154 Video mode:** with video models in the catalog, from text or a start image, first and last frames, several
156
240
  references, camera moves, longer clips, sound.
157
241
  - **#155 Movies:** storyboards with a consistent cast, shots stitched into one film.
158
242
  - **#156 Prompt assistant:** a small local model for prompts and follow-ups.
@@ -12,7 +12,7 @@ lcode
12
12
 
13
13
  ```text
14
14
  ╭──────────────────────────────────────────────╮
15
- │ lcode v0.41.0 — local coding agent │
15
+ │ lcode v0.43.0 — local coding agent │
16
16
  │ │
17
17
  │ model lcode-qwen3.6-35b │
18
18
  │ context 256K tokens │
@@ -81,6 +81,8 @@ reasoning is on.
81
81
  | `/think [on\|off]` | Toggle reasoning |
82
82
  | `/verbose` | Toggle showing the reasoning text |
83
83
  | `/mode [code\|image\|video]` | Switch what lcode makes: [code, images or videos](modes.md) |
84
+ | `/options`, `/again`, `/vary N`, `/gallery`, `/open N` | Image mode: [options for the session, follow-ups on results](modes.md#results) |
85
+ | `/edit N`, `/use N`, `/extend N`, `/upscale N`, `/nobg N` | Image mode: [edit, follow, outpaint, upscale, cut out](modes.md#editing) |
84
86
  | `/permissions [ask\|plan\|auto-edit\|yolo]` | Set the permission mode |
85
87
  | `/cd DIR` | Change the working directory |
86
88
  | `/todos` | Show the model's task list |
@@ -3,7 +3,7 @@
3
3
  From a program: `from lcode import Session` (see lcode.api).
4
4
  """
5
5
 
6
- __version__ = "0.41.0"
6
+ __version__ = "0.43.0"
7
7
 
8
8
  API = ("Session", "Result", "Options", "SetupError")
9
9
 
@@ -309,6 +309,7 @@ class Settings:
309
309
  comfyui_path: str | None = None # a ComfyUI install lcode doesn't find by itself (lcode.comfy)
310
310
  media_dir: str = ".lcode/media" # where image and video mode save their files (lcode.media)
311
311
  image_model: str = "flux2-klein-4b" # the media catalog's model image mode uses (lcode.media_catalog)
312
+ preview: str = "auto" # images in the terminal: auto, kitty, iterm, blocks or off (lcode.preview)
312
313
  num_batch: int | None = None
313
314
  keep_alive: str = "30m"
314
315
  think: bool = True
@@ -375,6 +376,12 @@ class Agent:
375
376
  self.interactive = True # False for `lcode -p`: no end-of-session questions
376
377
  self.comfy_process = None # the ComfyUI lcode started for image and video mode, stopped when it exits
377
378
  self.no_coding_model = "" # why there's no coding model, when lcode started in image or video mode without
379
+ from lcode.imageopts import Options as ImageOptions
380
+
381
+ self.image_options = ImageOptions() # image mode's defaults for this session (/options)
382
+ self.media_results: list[dict] = [] # the session's images, numbered (/gallery, /vary, /open)
383
+ self.last_request: dict | None = None # what /again repeats (lcode.media.redo)
384
+ self.image_refs: list[str] = [] # images every next request follows (/use)
378
385
  self.reflected = 0 # messages before this index were already checked for notes to remember
379
386
  self._memory: memory_notes.Memory | None = None
380
387
  self._agent_types: tuple[Path, dict[str, subagents.AgentType], list[str]] | None = None
@@ -679,8 +686,10 @@ class Agent:
679
686
  return any(m.get("role") == "user" for m in self.messages)
680
687
 
681
688
  def save(self) -> None:
682
- if not (self.has_conversation() or self.session_name):
689
+ if not (self.has_conversation() or self.session_name or self.media_results):
683
690
  return # nothing worth resuming
691
+ if not self.session_title and not self.has_conversation() and self.media_results:
692
+ self.session_title = " ".join(self.media_results[0]["prompt"].split())[:60] # an image session
684
693
  self.session_title = self.session_title or sessions.title_from(self.messages)
685
694
  f = self.session_file()
686
695
  f.parent.mkdir(parents=True, exist_ok=True)
@@ -689,6 +698,7 @@ class Agent:
689
698
  "model": self.settings.model,
690
699
  "mode": self.settings.mode,
691
700
  "image_model": self.settings.image_model,
701
+ "media": {"results": self.media_results, "options": self.image_options.to_json(), "refs": self.image_refs},
692
702
  "name": self.session_name,
693
703
  "title": self.session_title,
694
704
  "messages": self.messages,
@@ -739,6 +749,10 @@ class Agent:
739
749
  self.settings.mode = data["mode"] # a session belongs to the mode it was made in
740
750
  if isinstance(data.get("image_model"), str):
741
751
  self.settings.image_model = data["image_model"]
752
+ media = data.get("media") or {}
753
+ self.media_results = [r for r in media.get("results") or [] if isinstance(r, dict) and "path" in r]
754
+ self.image_options = type(self.image_options).from_json(media.get("options") or {})
755
+ self.image_refs = [r for r in media.get("refs") or [] if isinstance(r, str)]
742
756
  self.session_title = data.get("title") or sessions.title_from(self.messages)
743
757
  self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
744
758
  self.tools.todos = list(data.get("todos") or [])
@@ -201,6 +201,7 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
201
201
  comfyui_path=cfg["comfyui_path"],
202
202
  media_dir=cfg["media_dir"],
203
203
  image_model=cfg["image_model"],
204
+ preview=cfg["preview"],
204
205
  web=switch(options.web, cfg["web"], "on"),
205
206
  search_backend=cfg["search_backend"],
206
207
  searxng_url=cfg["searxng_url"],
@@ -812,7 +812,7 @@ def media_request(agent, prompt: str, out: Console, quiet: bool) -> None:
812
812
  api.close_agent(agent)
813
813
  sys.exit(2)
814
814
  try:
815
- made = media.generate_image(agent, prompt)
815
+ made = media.request(agent, prompt)
816
816
  except (media.MediaError, KeyboardInterrupt) as e:
817
817
  message = str(e) or "Cancelled."
818
818
  if quiet:
@@ -826,12 +826,13 @@ def media_request(agent, prompt: str, out: Console, quiet: bool) -> None:
826
826
  "type": "result",
827
827
  "status": "success",
828
828
  "mode": "image",
829
- "files": [str(path) for path in made.files],
830
- "model": made.model,
831
- "seed": made.seed,
832
- "width": made.width,
833
- "height": made.height,
834
- "seconds": round(made.seconds, 1),
829
+ "files": [str(path) for one in made for path in one.files],
830
+ "model": made[0].model,
831
+ "seed": made[0].seed,
832
+ "seeds": [one.seed for one in made],
833
+ "width": made[0].width,
834
+ "height": made[0].height,
835
+ "seconds": round(sum(one.seconds for one in made), 1),
835
836
  }
836
837
  print(json.dumps(result, ensure_ascii=False), flush=True)
837
838
  api.close_agent(agent)
@@ -1106,9 +1107,13 @@ def cmd_media(args) -> None:
1106
1107
  f"ComfyUI at {server.url}" if running else (f"{install.path} (not running)" if install else "no ComfyUI found")
1107
1108
  )
1108
1109
  console.print(f"[dim]{hw.describe()} · {where} · image model: {cfg['image_model']}[/]\n")
1109
- for mode in ("image", "video"):
1110
+ for mode in ("image", "video", "tool"):
1110
1111
  models = cat.for_mode(mode)
1111
- console.print(f"[bold]{mode.capitalize()} models[/]")
1112
+ console.print(
1113
+ "[bold]Tools for images[/] [dim](/upscale, /nobg)[/]"
1114
+ if mode == "tool"
1115
+ else f"[bold]{mode.capitalize()} models[/]"
1116
+ )
1112
1117
  if not models:
1113
1118
  console.print(f" [dim]{media.SOON[mode]}[/]\n")
1114
1119
  continue
@@ -336,8 +336,9 @@ def describe_rejection(r: requests.Response) -> str:
336
336
  lines = [str((data.get("error") or {}).get("message") or "ComfyUI refused the workflow")]
337
337
  for node, info in (data.get("node_errors") or {}).items():
338
338
  for err in info.get("errors", [])[:3]:
339
- detail = err.get("details") or err.get("message") or ""
340
- lines.append(f"{info.get('class_type', 'node')} ({node}): {detail}")
339
+ message, detail = err.get("message") or "", err.get("details") or ""
340
+ why = f"{message}: {detail}" if message and detail and detail not in message else (detail or message)
341
+ lines.append(f"{info.get('class_type', 'node')} ({node}): {why}")
341
342
  return "; ".join(lines)
342
343
 
343
344
 
@@ -560,13 +561,15 @@ def describe_history_error(entry: dict) -> str:
560
561
 
561
562
 
562
563
  def save(server: Server, outputs: dict, folder: Path, stem: str, meta: dict) -> list[Path]:
563
- """Fetch the job's files into `folder`: stem-1.png, … with stem-1.json next to each."""
564
+ """Fetch the job's files into `folder`: stem.png (stem-2.png if that's taken), or stem-1.png, … for several,
565
+ each with its settings in a .json next to it."""
564
566
  folder.mkdir(parents=True, exist_ok=True)
565
567
  items = [item for node in outputs.values() for kind in OUTPUT_KINDS for item in node.get(kind, [])]
568
+ items = [it for it in items if it.get("type", "output") == "output"]
566
569
  saved = []
567
- for i, item in enumerate((it for it in items if it.get("type", "output") == "output"), 1):
570
+ for i, item in enumerate(items, 1):
568
571
  ext = Path(item["filename"]).suffix or ".png"
569
- path = unique(folder / f"{stem}-{i}{ext}")
572
+ path = unique(folder / (f"{stem}{ext}" if len(items) == 1 else f"{stem}-{i}{ext}"))
570
573
  data = server.fetch(item)
571
574
  if ext.lower() == ".png":
572
575
  data = with_text(data, "lcode", json.dumps(meta, ensure_ascii=False))
@@ -46,6 +46,7 @@ SETTINGS: dict[str, tuple[object, type, str]] = {
46
46
  "comfyui_path": (None, str, "your ComfyUI folder, if lcode doesn't find it (lcode starts it when needed)"),
47
47
  "media_dir": (".lcode/media", str, "where images and videos go: a folder in the project, or an absolute path"),
48
48
  "image_model": ("flux2-klein-4b", str, "the model image mode uses: a key from `lcode media`"),
49
+ "preview": ("auto", str, "images in the terminal: auto | kitty | iterm | blocks (colour text) | off"),
49
50
  "think": (True, bool, "let the model reason before answering (slower, better)"),
50
51
  "web": ("on", str, "web search and page fetching for the model: on | ask | off"),
51
52
  "search_backend": ("auto", str, "auto | ollama | brave | tavily | searxng (keys come from environment variables)"),
@@ -211,6 +212,11 @@ def coerce(key: str, value: object) -> object:
211
212
  raise ConfigError(f"permission_mode must be one of {', '.join(PERMISSION_MODES)}")
212
213
  if key == "mode" and value not in WORK_MODES:
213
214
  raise ConfigError(f"mode must be one of {', '.join(WORK_MODES)}")
215
+ if key == "preview":
216
+ from lcode.preview import PROTOCOLS
217
+
218
+ if value not in PROTOCOLS:
219
+ raise ConfigError(f"preview must be one of {', '.join(PROTOCOLS)}")
214
220
  if key == "image_model":
215
221
  from lcode import media_catalog
216
222