lcode-cli 0.39.0__tar.gz → 0.41.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.
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/CHANGELOG.md +50 -1
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/PKG-INFO +4 -1
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/README.md +3 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/configuration.md +3 -0
- lcode_cli-0.41.0/docs/modes.md +162 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/quickstart.md +1 -1
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/usage.md +1 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/agent.py +8 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/api.py +41 -11
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/cli.py +139 -9
- lcode_cli-0.41.0/src/lcode/comfy.py +652 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/config.py +9 -0
- lcode_cli-0.41.0/src/lcode/media.py +293 -0
- lcode_cli-0.41.0/src/lcode/media.toml +74 -0
- lcode_cli-0.41.0/src/lcode/media_catalog.py +331 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/modes.py +35 -13
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/repl.py +99 -15
- lcode_cli-0.41.0/src/lcode/workflows/__init__.py +0 -0
- lcode_cli-0.41.0/src/lcode/workflows/flux2-klein-4b-t2i.json +31 -0
- lcode_cli-0.41.0/src/lcode/workflows/z-image-turbo-t2i.json +31 -0
- lcode_cli-0.41.0/tests/comfy_standin.py +241 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/conftest.py +12 -1
- lcode_cli-0.41.0/tests/test_comfy.py +498 -0
- lcode_cli-0.41.0/tests/test_media_catalog.py +312 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_modes.py +21 -9
- lcode_cli-0.39.0/docs/modes.md +0 -58
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.editorconfig +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.gitignore +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/AGENTS.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/LICENSE +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/SECURITY.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/action.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/agents.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/automation.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/bench.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/changelog.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/commands.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/contributing.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/editors.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/git.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/hooks.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/how-it-works.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/index.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/installation.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/lsp.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/mcp.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/memory.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/models.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/sandbox.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/search.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/servers.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/examples/README.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/examples/api/fix_tests.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/examples/commands/review.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/examples/skills/commit-message/SKILL.md +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/install.sh +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/mkdocs.yml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/overrides/main.html +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/pyproject.toml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/scripts/make_logos.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/acp.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/action.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/backends.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/bench.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/bench_backend.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/bench_runtime.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/checks.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/clipboard.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/cloud.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/codesearch.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/context.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/escalate.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/extensions.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/frontmatter.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/gitflow.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/hooks.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/jobs.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/lsp.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/matching.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/catalog.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/catalog.toml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/commands.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/server.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/memory.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/notify.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/permissions.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/planning.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/repeats.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/repomap.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/residency.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/review.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/screenshots.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/secretscan.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/servers/__init__.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/servers/encord.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/servers/valohai.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/servers/xai.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/steer.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/subagents.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/tools.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/unreachable.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/update.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/vision.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/fake_lsp_server.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_acp.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_action.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_api.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_backends.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_bench.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_checks.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_cli.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_clipboard.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_cloud.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_codesearch.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_config.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_context.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_escalate.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_extensions.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_gitflow.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_hooks.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_jobs.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_look.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_lsp.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_matching.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_mcp.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_memory.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_model_picker.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_plan_mode.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_repeats.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_repomap.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_residency.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_review.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_robustness.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_screenshots.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_secretscan.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_servers.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_steer.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_stuck.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_subagents.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_tools.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_update.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_vision.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_web.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/tests/test_xai.py +0 -0
- {lcode_cli-0.39.0 → lcode_cli-0.41.0}/uv.lock +0 -0
|
@@ -6,6 +6,53 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.41.0] - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Image models to choose from, installed by lcode** (#150). lcode's media catalog lists the image
|
|
14
|
+
models it can run, with their size, license, measured speed and whether they fit your GPU.
|
|
15
|
+
**Z-Image Turbo** (photorealistic) joins FLUX.2 klein 4B (fast): 17 s and 3.5 s per 1024×1024
|
|
16
|
+
image on a 12 GB laptop GPU. See
|
|
17
|
+
[Image models](https://nasser1941.github.io/lcode/modes/#image-models).
|
|
18
|
+
- **Choose one:** `/models` in image mode (numbered) or `/model z-image-turbo` switches; the
|
|
19
|
+
`image_model` setting is the default, and sessions keep theirs.
|
|
20
|
+
- **Installed on demand:** a model that isn't installed downloads with your next image, after you
|
|
21
|
+
say yes, or ahead with `lcode media install <model>`. lcode downloads only the missing files
|
|
22
|
+
(models share text encoders and VAEs) into ComfyUI's model folders, checks the disk first,
|
|
23
|
+
verifies each file's SHA-256, and resumes interrupted downloads. `HF_TOKEN` works for gated
|
|
24
|
+
models.
|
|
25
|
+
- **`lcode media`** lists image and video models with what fits and what's installed, and
|
|
26
|
+
`lcode doctor` says how to install the image model when it's missing.
|
|
27
|
+
|
|
28
|
+
## [0.40.0] - 2026-10-23
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **Image mode makes images** (#149). In image mode, what you type becomes a picture, made by
|
|
33
|
+
FLUX.2 klein 4B on your GPU in about 8 seconds, with no language model involved. lcode drives
|
|
34
|
+
ComfyUI itself. See [Modes](https://nasser1941.github.io/lcode/modes/#image-mode).
|
|
35
|
+
- **ComfyUI is found or started:** lcode uses a running ComfyUI as it is. Otherwise it starts
|
|
36
|
+
yours in the background when you enter image mode (`comfyui_path`, the `comfy` CLI's folder or
|
|
37
|
+
a usual one). It stops that ComfyUI when you quit, go back to code mode or close the terminal.
|
|
38
|
+
With none, it offers to install ComfyUI with comfy-cli.
|
|
39
|
+
- **Progress, cancel and type-ahead:** the status line shows the step, the time left and your
|
|
40
|
+
place in ComfyUI's queue. Esc or Ctrl+C cancels the job in ComfyUI too. What you type
|
|
41
|
+
meanwhile waits in the next prompt.
|
|
42
|
+
- **Files with their settings:** images go to `.lcode/media/` (the `media_dir` setting), each
|
|
43
|
+
with a JSON file of its prompt, model, seed and size, also stored in the PNG itself. Images
|
|
44
|
+
that finish after a crash are saved the next time.
|
|
45
|
+
- `lcode --mode image -p "…"` makes an image from a script, and `--output json` gives the paths.
|
|
46
|
+
`lcode doctor` reports ComfyUI and the image model.
|
|
47
|
+
- **No Ollama needed for images:** lcode starts in image mode without Ollama or the coding
|
|
48
|
+
model; `/mode code` says what's missing.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **lcode cleans up when its terminal closes.** A closed terminal or a `kill` used to end lcode
|
|
53
|
+
without stopping what it had started. Now it saves the session and stops its background jobs and
|
|
54
|
+
servers. On Linux, a ComfyUI lcode started also stops if lcode is killed outright.
|
|
55
|
+
|
|
9
56
|
## [0.39.0] - 2026-10-23
|
|
10
57
|
|
|
11
58
|
### Added
|
|
@@ -876,7 +923,9 @@ First public release.
|
|
|
876
923
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
877
924
|
- One-line installer for Linux and macOS.
|
|
878
925
|
|
|
879
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
926
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.41.0...HEAD
|
|
927
|
+
[0.41.0]: https://github.com/nasser1941/lcode/compare/v0.40.0...v0.41.0
|
|
928
|
+
[0.40.0]: https://github.com/nasser1941/lcode/compare/v0.39.0...v0.40.0
|
|
880
929
|
[0.39.0]: https://github.com/nasser1941/lcode/compare/v0.38.0...v0.39.0
|
|
881
930
|
[0.38.0]: https://github.com/nasser1941/lcode/compare/v0.37.0...v0.38.0
|
|
882
931
|
[0.37.0]: https://github.com/nasser1941/lcode/compare/v0.36.0...v0.37.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.41.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.
|
|
@@ -25,6 +25,9 @@ lcode config path # print the file location
|
|
|
25
25
|
| `permission_mode` | `ask` | `ask`, `plan`, `auto-edit` or `yolo` |
|
|
26
26
|
| `mode` | `code` | What lcode makes when it starts: `code`, `image` or `video` ([modes](modes.md)) |
|
|
27
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 |
|
|
30
|
+
| `image_model` | `flux2-klein-4b` | The model image mode uses: a key from `lcode media` ([image models](modes.md#image-models)) |
|
|
28
31
|
| `think` | `true` | Let the model reason before answering |
|
|
29
32
|
| `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
|
|
30
33
|
| `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
|
|
@@ -0,0 +1,162 @@
|
|
|
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 the image model you choose (see
|
|
38
|
+
[Image models](#image-models)). Sizes, seeds and variations come next (#151).
|
|
39
|
+
|
|
40
|
+
- **Progress:** the status line says what ComfyUI is doing (loading the model, reading the
|
|
41
|
+
prompt, drawing, decoding), the step, the time left and the time so far. If other jobs are ahead
|
|
42
|
+
in ComfyUI's queue, it says how many.
|
|
43
|
+
- **Cancel** with Esc or Ctrl+C: lcode cancels the job in ComfyUI too, so it stops using the GPU.
|
|
44
|
+
- **Type ahead:** what you type while an image is being made waits in the next prompt.
|
|
45
|
+
- **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
|
|
48
|
+
as well. `.lcode/media/` keeps itself out of git; a folder of your own (`lcode config set
|
|
49
|
+
media_dir assets/generated`) doesn't.
|
|
50
|
+
- **Nothing is lost:** lcode writes each job down while it runs. If lcode crashes or the terminal
|
|
51
|
+
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.
|
|
54
|
+
- **No Ollama needed:** `lcode --mode image` starts even when Ollama isn't running or the coding
|
|
55
|
+
model isn't installed; `/mode code` then says what's missing.
|
|
56
|
+
|
|
57
|
+
## ComfyUI
|
|
58
|
+
|
|
59
|
+
ComfyUI runs the image and video models; lcode drives it through its API.
|
|
60
|
+
|
|
61
|
+
- **One that's running** at `comfyui_url` (by default `http://127.0.0.1:8188`) is used as it is.
|
|
62
|
+
- **Otherwise lcode starts one** in the background when you switch to image mode, so the first
|
|
63
|
+
image waits less, and stops it when you quit or go back to code mode, or when the terminal
|
|
64
|
+
closes. It looks in the `comfyui_path` setting, then the folder the `comfy` CLI uses, then
|
|
65
|
+
`~/comfy/ComfyUI`, `~/ComfyUI`, `~/comfyui` and `~/Documents/ComfyUI`. It uses that install's
|
|
66
|
+
own Python (its `.venv` or `venv`), and logs to `comfyui.log` in lcode's state folder.
|
|
67
|
+
- **With none,** your first image request offers to install ComfyUI into lcode's state folder with
|
|
68
|
+
[comfy-cli](https://github.com/Comfy-Org/comfy-cli) and the PyTorch build for your GPU (NVIDIA,
|
|
69
|
+
Apple Silicon or CPU). That downloads several GB. To install it yourself:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uvx --from comfy-cli comfy install
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- **`lcode doctor`** shows whether ComfyUI is running or found, and whether the image model is
|
|
76
|
+
installed.
|
|
77
|
+
|
|
78
|
+
## Image models
|
|
79
|
+
|
|
80
|
+
lcode's media catalog lists the image models it can run, with what each needs and whether it fits
|
|
81
|
+
your machine. `/models` in image mode shows them, numbered; pick one with its number, or `/model
|
|
82
|
+
z-image-turbo`. `lcode config set image_model z-image-turbo` makes one the default.
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
image ❯ /models
|
|
86
|
+
1. ● FLUX.2 klein 4B (flux2-klein-4b) text-to-image · 12.4 GB · installed · fits (~9 GB of 12 GB GPU memory)
|
|
87
|
+
1024×1024: 3.5 s per image, 14 s for the first with ComfyUI starting (RTX 4080 Laptop, 12 GB)
|
|
88
|
+
2. Z-Image Turbo (z-image-turbo) text-to-image · 14.6 GB · 6.5 GB to download · fits (~9 GB of 12 GB GPU memory)
|
|
89
|
+
1024×1024: 17 s per image (1.9 s per step), 23 s for the first (RTX 4080 Laptop, 12 GB)
|
|
90
|
+
Switch to which model? (number or name, Enter to keep FLUX.2 klein 4B): 2
|
|
91
|
+
Image model: Z-Image Turbo.
|
|
92
|
+
Not installed yet: 6.5 GB download with your next image (you're asked first).
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Model | Made by | License | Steps | Good at |
|
|
96
|
+
|---|---|---|---|---|
|
|
97
|
+
| **FLUX.2 klein 4B** (default) | Black Forest Labs | Apache 2.0 | 4 | Speed: a few seconds per image |
|
|
98
|
+
| **Z-Image Turbo** | Tongyi-MAI (Alibaba) | Apache 2.0 | 8 | Photorealism, text in images; slower |
|
|
99
|
+
|
|
100
|
+
Measured speeds are in the list (`/models`, `lcode media`) and in [Measured](#measured).
|
|
101
|
+
|
|
102
|
+
- **Installing:** a model that isn't installed downloads with your next image, after you say yes.
|
|
103
|
+
You can also install it ahead with `lcode media install z-image-turbo`. lcode downloads only
|
|
104
|
+
the files ComfyUI doesn't have: models share text encoders and VAEs, so Z-Image Turbo needs
|
|
105
|
+
6.5 GB once FLUX.2 klein is there. Files go into ComfyUI's model folders, and the folders of
|
|
106
|
+
`extra_model_paths.yaml` count when ComfyUI is running.
|
|
107
|
+
- **Safe downloads:** lcode checks the disk first and keeps 2 GB free. The progress shows the
|
|
108
|
+
speed and the time left. Each file is checked against Hugging Face's SHA-256 before it's used.
|
|
109
|
+
- **Interrupted?** Ctrl+C pauses a download, and so does a dropped connection. Run the same install
|
|
110
|
+
again and it continues where it stopped (a `.part` file holds what arrived).
|
|
111
|
+
- **Gated models:** for a model whose license you must accept on Hugging Face, accept it there
|
|
112
|
+
and set `HF_TOKEN`. lcode sends the token to huggingface.co only.
|
|
113
|
+
- **Will it fit?** On NVIDIA GPUs the list compares an estimate of the GPU memory the model needs
|
|
114
|
+
with your GPU: the largest of its files plus 1.5 GB. ComfyUI loads the text encoder, the model and
|
|
115
|
+
the VAE one after another, so the largest file sets the need. A model too big for the GPU still
|
|
116
|
+
runs, slower, because ComfyUI keeps part of it in RAM. On Apple Silicon, the list compares it
|
|
117
|
+
with unified memory.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
lcode media # image and video models, what fits, what's installed
|
|
121
|
+
lcode media install z-image-turbo # download what's missing (asks first; --yes doesn't)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Measured
|
|
125
|
+
|
|
126
|
+
On an RTX 4080 Laptop GPU (12 GB) with 32 GB of RAM, ComfyUI 0.38, 1024×1024, four different
|
|
127
|
+
prompts each:
|
|
128
|
+
|
|
129
|
+
| Model | First image (ComfyUI starting, model loading) | Each next image | Per step |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| FLUX.2 klein 4B (fp8) | 14.3 s | 3.5 s | 0.6 s (4 steps) |
|
|
132
|
+
| Z-Image Turbo (int8) | 22.9 s | 17.4 s | 1.9 s (8 steps) |
|
|
133
|
+
|
|
134
|
+
ComfyUI uses whatever GPU memory is free to keep models loaded (11.3 to 11.5 GB here), so the
|
|
135
|
+
memory a model *needs* is lower than what `nvidia-smi` shows while it runs.
|
|
136
|
+
|
|
137
|
+
## One model on the GPU at a time
|
|
138
|
+
|
|
139
|
+
Image and video modes don't need the big coding model: the image or video model does the work. A
|
|
140
|
+
small optional model will help with prompts later. Image and video models need the GPU to
|
|
141
|
+
themselves:
|
|
142
|
+
|
|
143
|
+
- **Into image or video mode:** lcode unloads its coding model from Ollama, and checks again
|
|
144
|
+
before each image.
|
|
145
|
+
- **Back to code mode:** lcode stops the ComfyUI it started, or asks yours to free its memory,
|
|
146
|
+
then loads the coding model again in the background.
|
|
147
|
+
|
|
148
|
+
## Coming next
|
|
149
|
+
|
|
150
|
+
Video mode doesn't generate yet: a request there says what's coming instead of going anywhere.
|
|
151
|
+
|
|
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
|
|
156
|
+
references, camera moves, longer clips, sound.
|
|
157
|
+
- **#155 Movies:** storyboards with a consistent cast, shots stitched into one film.
|
|
158
|
+
- **#156 Prompt assistant:** a small local model for prompts and follow-ups.
|
|
159
|
+
- **#157 Cloud generation**, with costs shown (Grok Imagine first).
|
|
160
|
+
|
|
161
|
+
The permission mode (`ask`, `plan`, `auto-edit`, `yolo`) is a different setting: `/permissions`
|
|
162
|
+
sets it, and Shift+Tab cycles it as before.
|
|
@@ -7,6 +7,7 @@ lcode [options] # interactive session in the current directory
|
|
|
7
7
|
lcode setup [model] # pick, download and configure a model
|
|
8
8
|
lcode models # the model catalog and what fits this machine
|
|
9
9
|
lcode doctor # check the installation (include this in bug reports)
|
|
10
|
+
lcode media [install MODEL] # image and video models: what fits here, what's installed; install one
|
|
10
11
|
lcode config [set|unset] # show or change settings
|
|
11
12
|
lcode bench [models] # score models on small coding tasks on this machine
|
|
12
13
|
lcode mcp [catalog|add|list] # connect MCP servers: Jira, GitHub, AWS, databases… (see MCP servers)
|
|
@@ -306,6 +306,9 @@ class Settings:
|
|
|
306
306
|
context: int
|
|
307
307
|
mode: str = "code" # what lcode makes: code, image or video (lcode.modes)
|
|
308
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)
|
|
311
|
+
image_model: str = "flux2-klein-4b" # the media catalog's model image mode uses (lcode.media_catalog)
|
|
309
312
|
num_batch: int | None = None
|
|
310
313
|
keep_alive: str = "30m"
|
|
311
314
|
think: bool = True
|
|
@@ -370,6 +373,8 @@ class Agent:
|
|
|
370
373
|
self.session_name = ""
|
|
371
374
|
self.session_title = ""
|
|
372
375
|
self.interactive = True # False for `lcode -p`: no end-of-session questions
|
|
376
|
+
self.comfy_process = None # the ComfyUI lcode started for image and video mode, stopped when it exits
|
|
377
|
+
self.no_coding_model = "" # why there's no coding model, when lcode started in image or video mode without
|
|
373
378
|
self.reflected = 0 # messages before this index were already checked for notes to remember
|
|
374
379
|
self._memory: memory_notes.Memory | None = None
|
|
375
380
|
self._agent_types: tuple[Path, dict[str, subagents.AgentType], list[str]] | None = None
|
|
@@ -683,6 +688,7 @@ class Agent:
|
|
|
683
688
|
"cwd": str(self.workspace), # where `lcode -c` there finds it, wherever the shell moved
|
|
684
689
|
"model": self.settings.model,
|
|
685
690
|
"mode": self.settings.mode,
|
|
691
|
+
"image_model": self.settings.image_model,
|
|
686
692
|
"name": self.session_name,
|
|
687
693
|
"title": self.session_title,
|
|
688
694
|
"messages": self.messages,
|
|
@@ -731,6 +737,8 @@ class Agent:
|
|
|
731
737
|
self.session_name = data.get("name", "")
|
|
732
738
|
if data.get("mode") in ("code", "image", "video"):
|
|
733
739
|
self.settings.mode = data["mode"] # a session belongs to the mode it was made in
|
|
740
|
+
if isinstance(data.get("image_model"), str):
|
|
741
|
+
self.settings.image_model = data["image_model"]
|
|
734
742
|
self.session_title = data.get("title") or sessions.title_from(self.messages)
|
|
735
743
|
self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
|
|
736
744
|
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
|
|
@@ -130,6 +131,24 @@ def switch(wanted: bool | None, configured: str, default_on: str) -> str:
|
|
|
130
131
|
return configured if configured != "off" else default_on
|
|
131
132
|
|
|
132
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
|
+
|
|
133
152
|
def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | None = None) -> Agent:
|
|
134
153
|
"""An agent set up the way `lcode` sets up a session: model, context, hooks, language servers, MCP."""
|
|
135
154
|
from lcode import catalog, cli, extensions, hooks
|
|
@@ -153,17 +172,14 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
153
172
|
except OllamaError as e:
|
|
154
173
|
raise SetupError(str(e)) from e
|
|
155
174
|
hw = hw or detect()
|
|
156
|
-
|
|
175
|
+
no_coding_model = "" # why, when image or video mode starts without one
|
|
157
176
|
try:
|
|
158
|
-
model, spec =
|
|
159
|
-
except
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
context, note = cli.choose_context(ollama, model, spec, requested, hw)
|
|
165
|
-
if note:
|
|
166
|
-
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)
|
|
167
183
|
num_batch = cfg["num_batch"]
|
|
168
184
|
if num_batch is None and spec and spec.num_batch:
|
|
169
185
|
if model == spec.local_name:
|
|
@@ -182,6 +198,9 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
182
198
|
permission_mode=options.permission_mode or cfg["permission_mode"],
|
|
183
199
|
mode=options.mode or cfg["mode"],
|
|
184
200
|
comfyui_url=cfg["comfyui_url"],
|
|
201
|
+
comfyui_path=cfg["comfyui_path"],
|
|
202
|
+
media_dir=cfg["media_dir"],
|
|
203
|
+
image_model=cfg["image_model"],
|
|
185
204
|
web=switch(options.web, cfg["web"], "on"),
|
|
186
205
|
search_backend=cfg["search_backend"],
|
|
187
206
|
searxng_url=cfg["searxng_url"],
|
|
@@ -211,6 +230,7 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
211
230
|
)
|
|
212
231
|
agent = Agent(ollama, settings, cwd, console=console)
|
|
213
232
|
agent.hardware = hw
|
|
233
|
+
agent.no_coding_model = no_coding_model
|
|
214
234
|
agent.autosave = True
|
|
215
235
|
agent.interactive = options.interactive
|
|
216
236
|
if options.max_steps:
|
|
@@ -250,15 +270,25 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
250
270
|
|
|
251
271
|
|
|
252
272
|
def close_agent(agent: Agent) -> None:
|
|
273
|
+
"""Stop what lcode started (background jobs, servers, the sandbox, ComfyUI), then say so: the terminal may be
|
|
274
|
+
gone already (closed), and saying it then fails."""
|
|
275
|
+
from lcode import media
|
|
276
|
+
|
|
277
|
+
said = []
|
|
253
278
|
stopped = agent.jobs.stop_all()
|
|
254
279
|
if stopped:
|
|
255
|
-
|
|
280
|
+
said.append(f"Stopped {stopped} background job(s).")
|
|
281
|
+
if media.stop_engine(agent):
|
|
282
|
+
said.append("Stopped ComfyUI.")
|
|
256
283
|
if agent.lsp is not None:
|
|
257
284
|
agent.lsp.close()
|
|
258
285
|
if agent.mcp:
|
|
259
286
|
agent.mcp.close()
|
|
260
287
|
if agent.sandbox:
|
|
261
288
|
agent.sandbox.stop()
|
|
289
|
+
for line in said:
|
|
290
|
+
with contextlib.suppress(OSError):
|
|
291
|
+
agent.console.print(f"[dim]{line}[/]")
|
|
262
292
|
|
|
263
293
|
|
|
264
294
|
# ----------------------------------------------------------------------------- running
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import contextlib
|
|
6
7
|
import platform
|
|
7
8
|
import re
|
|
8
9
|
import shutil
|
|
10
|
+
import signal
|
|
9
11
|
import sys
|
|
10
12
|
import tempfile
|
|
11
13
|
import time
|
|
@@ -466,6 +468,7 @@ def cmd_doctor(args) -> None:
|
|
|
466
468
|
except ValueError as e:
|
|
467
469
|
line("MCP", str(e), False)
|
|
468
470
|
ok = False
|
|
471
|
+
line("ComfyUI", *comfyui_status(cfg))
|
|
469
472
|
if cfg["sandbox"] == "off":
|
|
470
473
|
line("Sandbox", "off (lcode config set sandbox docker)", True)
|
|
471
474
|
else:
|
|
@@ -733,17 +736,10 @@ def cmd_chat(args) -> None:
|
|
|
733
736
|
print(json.dumps({"type": "result", "status": "error", "error": str(e)}), flush=True)
|
|
734
737
|
sys.exit(1)
|
|
735
738
|
fail(str(e))
|
|
739
|
+
exit_on_hangup()
|
|
736
740
|
exit_code = 0
|
|
737
741
|
if args.prompt and agent.settings.mode != "code":
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
message = modes.COMING[agent.settings.mode]
|
|
741
|
-
if quiet:
|
|
742
|
-
print(json.dumps({"type": "result", "status": "error", "error": message}), flush=True)
|
|
743
|
-
else:
|
|
744
|
-
out.print(f"[yellow]{escape(message)}[/]")
|
|
745
|
-
api.close_agent(agent)
|
|
746
|
-
sys.exit(2)
|
|
742
|
+
media_request(agent, args.prompt, out, quiet) # makes the image or video, then exits
|
|
747
743
|
if args.escalate and args.prompt:
|
|
748
744
|
agent.escalate_approved = True # the flag is the user's OK for this run
|
|
749
745
|
try:
|
|
@@ -765,6 +761,83 @@ def cmd_chat(args) -> None:
|
|
|
765
761
|
sys.exit(exit_code)
|
|
766
762
|
|
|
767
763
|
|
|
764
|
+
def exit_on_hangup() -> None:
|
|
765
|
+
"""A closed terminal (SIGHUP) or a plain kill (SIGTERM) ends lcode the usual way: it saves the session and stops
|
|
766
|
+
what it started (ComfyUI, MCP servers, background jobs) instead of leaving them running."""
|
|
767
|
+
|
|
768
|
+
def leave(signum, frame):
|
|
769
|
+
raise SystemExit(128 + signum)
|
|
770
|
+
|
|
771
|
+
for name in ("SIGHUP", "SIGTERM"):
|
|
772
|
+
sig = getattr(signal, name, None)
|
|
773
|
+
if sig is not None:
|
|
774
|
+
with contextlib.suppress(ValueError, OSError): # only the main thread can
|
|
775
|
+
signal.signal(sig, leave)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def comfyui_status(cfg: dict) -> tuple[str, bool | None]:
|
|
779
|
+
"""lcode doctor's ComfyUI line: running or found, and whether the image model is there."""
|
|
780
|
+
from lcode import comfy
|
|
781
|
+
from lcode import media_catalog as catalog
|
|
782
|
+
|
|
783
|
+
model = catalog.load().find(cfg.get("image_model") or "") or catalog.load().models[catalog.DEFAULT_IMAGE]
|
|
784
|
+
server = comfy.Server(cfg["comfyui_url"])
|
|
785
|
+
stats = server.stats()
|
|
786
|
+
install = None if stats else comfy.find_install(cfg["comfyui_path"])
|
|
787
|
+
if not stats and install is None:
|
|
788
|
+
return "not found: image mode offers to install it (or lcode config set comfyui_path <folder>)", None
|
|
789
|
+
if stats:
|
|
790
|
+
where = f"{(stats.get('system') or {}).get('comfyui_version', '?')} running at {server.url}"
|
|
791
|
+
else:
|
|
792
|
+
where = f"{install.path} (lcode starts it in image mode)"
|
|
793
|
+
_, missing = catalog.chosen_files(model, catalog.lister(server if stats else None, install))
|
|
794
|
+
if missing:
|
|
795
|
+
size = sum(f.gb for f in missing)
|
|
796
|
+
return f"{where} · {model.name} not installed: lcode media install {model.key} ({size:.1f} GB)", None
|
|
797
|
+
return f"{where} · image model: {model.name}", True
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def media_request(agent, prompt: str, out: Console, quiet: bool) -> None:
|
|
801
|
+
"""`lcode --mode image -p "…"`: make the image, say where it went (or, with --output json, a result object)."""
|
|
802
|
+
import json
|
|
803
|
+
|
|
804
|
+
from lcode import api, media, modes
|
|
805
|
+
|
|
806
|
+
if agent.settings.mode == "video":
|
|
807
|
+
message = modes.COMING["video"]
|
|
808
|
+
if quiet:
|
|
809
|
+
print(json.dumps({"type": "result", "status": "error", "error": message}), flush=True)
|
|
810
|
+
else:
|
|
811
|
+
out.print(f"[yellow]{escape(message)}[/]")
|
|
812
|
+
api.close_agent(agent)
|
|
813
|
+
sys.exit(2)
|
|
814
|
+
try:
|
|
815
|
+
made = media.generate_image(agent, prompt)
|
|
816
|
+
except (media.MediaError, KeyboardInterrupt) as e:
|
|
817
|
+
message = str(e) or "Cancelled."
|
|
818
|
+
if quiet:
|
|
819
|
+
print(json.dumps({"type": "result", "status": "error", "error": message}), flush=True)
|
|
820
|
+
else:
|
|
821
|
+
out.print(f"[red]{escape(message)}[/]")
|
|
822
|
+
api.close_agent(agent)
|
|
823
|
+
sys.exit(1)
|
|
824
|
+
if quiet:
|
|
825
|
+
result = {
|
|
826
|
+
"type": "result",
|
|
827
|
+
"status": "success",
|
|
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),
|
|
835
|
+
}
|
|
836
|
+
print(json.dumps(result, ensure_ascii=False), flush=True)
|
|
837
|
+
api.close_agent(agent)
|
|
838
|
+
sys.exit(0)
|
|
839
|
+
|
|
840
|
+
|
|
768
841
|
def compact_tokens(value: str) -> int:
|
|
769
842
|
"""`--compact-at`: a token count such as 64k, or 0."""
|
|
770
843
|
try:
|
|
@@ -836,6 +909,13 @@ def build_subparsers() -> dict[str, argparse.ArgumentParser]:
|
|
|
836
909
|
subs["setup"] = p
|
|
837
910
|
subs["models"] = argparse.ArgumentParser(prog="lcode models", description="List models and how they fit here.")
|
|
838
911
|
subs["doctor"] = argparse.ArgumentParser(prog="lcode doctor", description="Check the installation.")
|
|
912
|
+
p = argparse.ArgumentParser(
|
|
913
|
+
prog="lcode media", description="Image and video models: list them, with how they fit here, or install one."
|
|
914
|
+
)
|
|
915
|
+
p.add_argument("action", nargs="?", choices=["list", "install"], default="list")
|
|
916
|
+
p.add_argument("model", nargs="?", help="with install: a model key from the list, e.g. z-image-turbo")
|
|
917
|
+
p.add_argument("-y", "--yes", action="store_true", help="don't ask before downloading")
|
|
918
|
+
subs["media"] = p
|
|
839
919
|
p = argparse.ArgumentParser(prog="lcode config", description="Show or change settings.")
|
|
840
920
|
p.add_argument("action", nargs="?", choices=["show", "set", "unset", "path"], default="show")
|
|
841
921
|
p.add_argument("key", nargs="?", choices=list(config.SETTINGS))
|
|
@@ -992,6 +1072,55 @@ def cmd_index(args) -> None:
|
|
|
992
1072
|
)
|
|
993
1073
|
|
|
994
1074
|
|
|
1075
|
+
def cmd_media(args) -> None:
|
|
1076
|
+
"""lcode media [list] | install <model>: the media catalog, what's installed in ComfyUI, downloads."""
|
|
1077
|
+
from rich.prompt import Confirm
|
|
1078
|
+
|
|
1079
|
+
from lcode import comfy, media
|
|
1080
|
+
from lcode import media_catalog as catalog
|
|
1081
|
+
|
|
1082
|
+
try:
|
|
1083
|
+
cfg = config.load()
|
|
1084
|
+
except ConfigError as e:
|
|
1085
|
+
fail(str(e))
|
|
1086
|
+
cat, hw = catalog.load(), detect()
|
|
1087
|
+
server = comfy.Server(cfg["comfyui_url"])
|
|
1088
|
+
install = comfy.find_install(cfg["comfyui_path"])
|
|
1089
|
+
running = server.stats() is not None
|
|
1090
|
+
installed = catalog.lister(server if running else None, install) if running or install else None
|
|
1091
|
+
if args.action == "install":
|
|
1092
|
+
model = cat.find(args.model or "")
|
|
1093
|
+
if model is None:
|
|
1094
|
+
fail(f"no media model {args.model!r}: lcode media lists them")
|
|
1095
|
+
if not running and install is None:
|
|
1096
|
+
fail(f"lcode needs ComfyUI to install models into. {comfy.install_hint()}")
|
|
1097
|
+
ask = None if args.yes else (lambda q: Confirm.ask(q, default=True, console=console))
|
|
1098
|
+
try:
|
|
1099
|
+
saved = catalog.install(model, server if running else None, install, console, ask)
|
|
1100
|
+
except catalog.CatalogError as e:
|
|
1101
|
+
fail(str(e))
|
|
1102
|
+
how = f" ({len(saved)} file(s) downloaded)." if saved else "."
|
|
1103
|
+
console.print(f"[green]{model.name} is installed[/]{how}")
|
|
1104
|
+
return
|
|
1105
|
+
where = (
|
|
1106
|
+
f"ComfyUI at {server.url}" if running else (f"{install.path} (not running)" if install else "no ComfyUI found")
|
|
1107
|
+
)
|
|
1108
|
+
console.print(f"[dim]{hw.describe()} · {where} · image model: {cfg['image_model']}[/]\n")
|
|
1109
|
+
for mode in ("image", "video"):
|
|
1110
|
+
models = cat.for_mode(mode)
|
|
1111
|
+
console.print(f"[bold]{mode.capitalize()} models[/]")
|
|
1112
|
+
if not models:
|
|
1113
|
+
console.print(f" [dim]{media.SOON[mode]}[/]\n")
|
|
1114
|
+
continue
|
|
1115
|
+
for m in models:
|
|
1116
|
+
line = escape(catalog.describe(m, hw, installed))
|
|
1117
|
+
console.print(f" [bold]{m.key:<16}[/] {escape(m.name)} [dim]· {line}[/]")
|
|
1118
|
+
if m.measured:
|
|
1119
|
+
console.print(f" {'':<16} [dim]{escape(m.measured)}[/]")
|
|
1120
|
+
console.print()
|
|
1121
|
+
console.print("[dim]Install one: lcode media install <model> · choose in image mode: /model <model>[/]")
|
|
1122
|
+
|
|
1123
|
+
|
|
995
1124
|
def cmd_mcp(args) -> None:
|
|
996
1125
|
from lcode.mcp.commands import run
|
|
997
1126
|
|
|
@@ -1002,6 +1131,7 @@ COMMANDS = {
|
|
|
1002
1131
|
"setup": cmd_setup,
|
|
1003
1132
|
"models": cmd_models,
|
|
1004
1133
|
"doctor": cmd_doctor,
|
|
1134
|
+
"media": cmd_media,
|
|
1005
1135
|
"config": cmd_config,
|
|
1006
1136
|
"bench": cmd_bench,
|
|
1007
1137
|
"mcp": cmd_mcp,
|