lcode-cli 0.40.0__tar.gz → 0.42.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.40.0 → lcode_cli-0.42.0}/CHANGELOG.md +39 -1
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/PKG-INFO +1 -1
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/configuration.md +2 -0
- lcode_cli-0.42.0/docs/modes.md +211 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/quickstart.md +1 -1
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/usage.md +2 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/agent.py +17 -1
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/api.py +2 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/cli.py +72 -14
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/comfy.py +18 -22
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/config.py +13 -0
- lcode_cli-0.42.0/src/lcode/imageopts.py +207 -0
- lcode_cli-0.42.0/src/lcode/media.py +473 -0
- lcode_cli-0.42.0/src/lcode/media.toml +80 -0
- lcode_cli-0.42.0/src/lcode/media_catalog.py +340 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/modes.py +1 -1
- lcode_cli-0.42.0/src/lcode/preview.py +152 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/repl.py +150 -12
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/sessions.py +2 -2
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/workflows/flux2-klein-4b-t2i.json +2 -9
- lcode_cli-0.42.0/src/lcode/workflows/z-image-turbo-t2i.json +31 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/comfy_standin.py +15 -2
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_comfy.py +10 -11
- lcode_cli-0.42.0/tests/test_image_generate.py +253 -0
- lcode_cli-0.42.0/tests/test_media_catalog.py +312 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_modes.py +2 -1
- lcode_cli-0.40.0/docs/modes.md +0 -109
- lcode_cli-0.40.0/src/lcode/media.py +0 -262
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.editorconfig +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.gitignore +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/AGENTS.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/LICENSE +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/README.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/SECURITY.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/action.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/agents.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/automation.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/bench.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/changelog.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/commands.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/contributing.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/editors.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/git.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/hooks.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/how-it-works.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/index.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/installation.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/lsp.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/mcp.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/memory.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/models.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/sandbox.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/search.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/servers.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/examples/README.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/examples/api/fix_tests.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/examples/commands/review.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/examples/skills/commit-message/SKILL.md +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/install.sh +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/mkdocs.yml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/overrides/main.html +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/pyproject.toml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/scripts/make_logos.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/acp.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/action.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/backends.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/bench.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/bench_backend.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/bench_runtime.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/checks.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/clipboard.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/cloud.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/codesearch.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/context.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/escalate.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/extensions.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/frontmatter.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/gitflow.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/hooks.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/jobs.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/lsp.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/matching.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/catalog.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/catalog.toml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/commands.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/server.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/memory.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/notify.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/permissions.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/planning.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/repeats.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/repomap.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/residency.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/review.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/screenshots.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/secretscan.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/servers/__init__.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/servers/encord.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/servers/valohai.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/servers/xai.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/steer.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/subagents.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/tools.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/unreachable.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/update.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/vision.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/src/lcode/workflows/__init__.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/conftest.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/fake_lsp_server.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_acp.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_action.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_api.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_backends.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_bench.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_checks.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_cli.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_clipboard.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_cloud.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_codesearch.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_config.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_context.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_escalate.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_extensions.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_gitflow.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_hooks.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_jobs.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_look.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_lsp.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_matching.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_mcp.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_memory.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_model_picker.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_plan_mode.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_repeats.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_repomap.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_residency.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_review.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_robustness.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_screenshots.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_secretscan.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_servers.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_steer.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_stuck.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_subagents.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_tools.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_update.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_vision.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_web.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/tests/test_xai.py +0 -0
- {lcode_cli-0.40.0 → lcode_cli-0.42.0}/uv.lock +0 -0
|
@@ -6,6 +6,42 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.42.0] - 2026-10-24
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Image mode's options, results and follow-ups** (#151). See
|
|
14
|
+
[Image mode](https://nasser1941.github.io/lcode/modes/#image-mode).
|
|
15
|
+
- **Options in a request** (`a red fox --n 4 --ar 16:9 --seed 42 --quality best --style photo`)
|
|
16
|
+
or for the session (`/options n=4 ar=16:9`): how many images, the shape, the seed (counting up
|
|
17
|
+
over several images), a quality preset (0.5, 1 or 2 megapixels), ten style presets, and a
|
|
18
|
+
negative prompt for models that use one.
|
|
19
|
+
- **Numbered results, shown in the terminal:** the picture itself in kitty, Ghostty, iTerm2 and
|
|
20
|
+
WezTerm, and a half-block colour preview elsewhere (the `preview` setting).
|
|
21
|
+
- **Follow-ups:** `/again` (new seeds), `/vary 3` (four variations of image #3), `/gallery` and
|
|
22
|
+
`/open 3`. The gallery and options are saved with the session.
|
|
23
|
+
- `lcode --mode image -p "… --n 2"` reports every image's file and seed.
|
|
24
|
+
- One image is now named `<time>-<prompt>.png` (no `-1`).
|
|
25
|
+
|
|
26
|
+
## [0.41.0] - 2026-10-23
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Image models to choose from, installed by lcode** (#150). lcode's media catalog lists the image
|
|
31
|
+
models it can run, with their size, license, measured speed and whether they fit your GPU.
|
|
32
|
+
**Z-Image Turbo** (photorealistic) joins FLUX.2 klein 4B (fast): 17 s and 3.5 s per 1024×1024
|
|
33
|
+
image on a 12 GB laptop GPU. See
|
|
34
|
+
[Image models](https://nasser1941.github.io/lcode/modes/#image-models).
|
|
35
|
+
- **Choose one:** `/models` in image mode (numbered) or `/model z-image-turbo` switches; the
|
|
36
|
+
`image_model` setting is the default, and sessions keep theirs.
|
|
37
|
+
- **Installed on demand:** a model that isn't installed downloads with your next image, after you
|
|
38
|
+
say yes, or ahead with `lcode media install <model>`. lcode downloads only the missing files
|
|
39
|
+
(models share text encoders and VAEs) into ComfyUI's model folders, checks the disk first,
|
|
40
|
+
verifies each file's SHA-256, and resumes interrupted downloads. `HF_TOKEN` works for gated
|
|
41
|
+
models.
|
|
42
|
+
- **`lcode media`** lists image and video models with what fits and what's installed, and
|
|
43
|
+
`lcode doctor` says how to install the image model when it's missing.
|
|
44
|
+
|
|
9
45
|
## [0.40.0] - 2026-10-23
|
|
10
46
|
|
|
11
47
|
### Added
|
|
@@ -904,7 +940,9 @@ First public release.
|
|
|
904
940
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
905
941
|
- One-line installer for Linux and macOS.
|
|
906
942
|
|
|
907
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
943
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.42.0...HEAD
|
|
944
|
+
[0.42.0]: https://github.com/nasser1941/lcode/compare/v0.41.0...v0.42.0
|
|
945
|
+
[0.41.0]: https://github.com/nasser1941/lcode/compare/v0.40.0...v0.41.0
|
|
908
946
|
[0.40.0]: https://github.com/nasser1941/lcode/compare/v0.39.0...v0.40.0
|
|
909
947
|
[0.39.0]: https://github.com/nasser1941/lcode/compare/v0.38.0...v0.39.0
|
|
910
948
|
[0.38.0]: https://github.com/nasser1941/lcode/compare/v0.37.0...v0.38.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.42.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,8 @@ 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)) |
|
|
31
|
+
| `image_model` | `flux2-klein-4b` | The model image mode uses: a key from `lcode media` ([image models](modes.md#image-models)) |
|
|
30
32
|
| `think` | `true` | Let the model reason before answering |
|
|
31
33
|
| `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
|
|
32
34
|
| `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
|
|
@@ -0,0 +1,211 @@
|
|
|
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
|
+
◆ #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
|
+
```
|
|
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
|
+
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
|
+
- **Progress:** the status line says what ComfyUI is doing (loading the model, reading the
|
|
85
|
+
prompt, drawing, decoding), the step, the time left and the time so far. If other jobs are ahead
|
|
86
|
+
in ComfyUI's queue, it says how many.
|
|
87
|
+
- **Cancel** with Esc or Ctrl+C: lcode cancels the job in ComfyUI too, so it stops using the GPU.
|
|
88
|
+
- **Type ahead:** what you type while an image is being made waits in the next prompt.
|
|
89
|
+
- **Files:** each image goes to `.lcode/media/` in the project (the `media_dir` setting), named
|
|
90
|
+
after the time and the prompt, with a `.json` next to it holding the prompt, model, seed, size,
|
|
91
|
+
steps, quality and style. PNGs carry the same in their own metadata (an `lcode` text chunk), and ComfyUI's workflow
|
|
92
|
+
as well. `.lcode/media/` keeps itself out of git; a folder of your own (`lcode config set
|
|
93
|
+
media_dir assets/generated`) doesn't.
|
|
94
|
+
- **Nothing is lost:** lcode writes each job down while it runs. If lcode crashes or the terminal
|
|
95
|
+
closes before an image arrives, the next request in image mode saves it.
|
|
96
|
+
- **From scripts:** `lcode --mode image -p "a red fox in the snow --n 2"` makes the images and
|
|
97
|
+
exits; with `--output json` it prints a result with the file paths, the model, the seeds, the size
|
|
98
|
+
and the time.
|
|
99
|
+
- **No Ollama needed:** `lcode --mode image` starts even when Ollama isn't running or the coding
|
|
100
|
+
model isn't installed; `/mode code` then says what's missing.
|
|
101
|
+
|
|
102
|
+
## ComfyUI
|
|
103
|
+
|
|
104
|
+
ComfyUI runs the image and video models; lcode drives it through its API.
|
|
105
|
+
|
|
106
|
+
- **One that's running** at `comfyui_url` (by default `http://127.0.0.1:8188`) is used as it is.
|
|
107
|
+
- **Otherwise lcode starts one** in the background when you switch to image mode, so the first
|
|
108
|
+
image waits less, and stops it when you quit or go back to code mode, or when the terminal
|
|
109
|
+
closes. It looks in the `comfyui_path` setting, then the folder the `comfy` CLI uses, then
|
|
110
|
+
`~/comfy/ComfyUI`, `~/ComfyUI`, `~/comfyui` and `~/Documents/ComfyUI`. It uses that install's
|
|
111
|
+
own Python (its `.venv` or `venv`), and logs to `comfyui.log` in lcode's state folder.
|
|
112
|
+
- **With none,** your first image request offers to install ComfyUI into lcode's state folder with
|
|
113
|
+
[comfy-cli](https://github.com/Comfy-Org/comfy-cli) and the PyTorch build for your GPU (NVIDIA,
|
|
114
|
+
Apple Silicon or CPU). That downloads several GB. To install it yourself:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
uvx --from comfy-cli comfy install
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **`lcode doctor`** shows whether ComfyUI is running or found, and whether the image model is
|
|
121
|
+
installed.
|
|
122
|
+
|
|
123
|
+
## Image models
|
|
124
|
+
|
|
125
|
+
lcode's media catalog lists the image models it can run, with what each needs and whether it fits
|
|
126
|
+
your machine. `/models` in image mode shows them, numbered; pick one with its number, or `/model
|
|
127
|
+
z-image-turbo`. `lcode config set image_model z-image-turbo` makes one the default.
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
image ❯ /models
|
|
131
|
+
1. ● FLUX.2 klein 4B (flux2-klein-4b) text-to-image · 12.4 GB · installed · fits (~9 GB of 12 GB GPU memory)
|
|
132
|
+
1024×1024: 3.5 s per image, 14 s for the first with ComfyUI starting (RTX 4080 Laptop, 12 GB)
|
|
133
|
+
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)
|
|
134
|
+
1024×1024: 17 s per image (1.9 s per step), 23 s for the first (RTX 4080 Laptop, 12 GB)
|
|
135
|
+
Switch to which model? (number or name, Enter to keep FLUX.2 klein 4B): 2
|
|
136
|
+
Image model: Z-Image Turbo.
|
|
137
|
+
Not installed yet: 6.5 GB download with your next image (you're asked first).
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Model | Made by | License | Steps | Good at |
|
|
141
|
+
|---|---|---|---|---|
|
|
142
|
+
| **FLUX.2 klein 4B** (default) | Black Forest Labs | Apache 2.0 | 4 | Speed: a few seconds per image |
|
|
143
|
+
| **Z-Image Turbo** | Tongyi-MAI (Alibaba) | Apache 2.0 | 8 | Photorealism, text in images; slower |
|
|
144
|
+
|
|
145
|
+
Measured speeds are in the list (`/models`, `lcode media`) and in [Measured](#measured).
|
|
146
|
+
|
|
147
|
+
- **Installing:** a model that isn't installed downloads with your next image, after you say yes.
|
|
148
|
+
You can also install it ahead with `lcode media install z-image-turbo`. lcode downloads only
|
|
149
|
+
the files ComfyUI doesn't have: models share text encoders and VAEs, so Z-Image Turbo needs
|
|
150
|
+
6.5 GB once FLUX.2 klein is there. Files go into ComfyUI's model folders, and the folders of
|
|
151
|
+
`extra_model_paths.yaml` count when ComfyUI is running.
|
|
152
|
+
- **Safe downloads:** lcode checks the disk first and keeps 2 GB free. The progress shows the
|
|
153
|
+
speed and the time left. Each file is checked against Hugging Face's SHA-256 before it's used.
|
|
154
|
+
- **Interrupted?** Ctrl+C pauses a download, and so does a dropped connection. Run the same install
|
|
155
|
+
again and it continues where it stopped (a `.part` file holds what arrived).
|
|
156
|
+
- **Gated models:** for a model whose license you must accept on Hugging Face, accept it there
|
|
157
|
+
and set `HF_TOKEN`. lcode sends the token to huggingface.co only.
|
|
158
|
+
- **Will it fit?** On NVIDIA GPUs the list compares an estimate of the GPU memory the model needs
|
|
159
|
+
with your GPU: the largest of its files plus 1.5 GB. ComfyUI loads the text encoder, the model and
|
|
160
|
+
the VAE one after another, so the largest file sets the need. A model too big for the GPU still
|
|
161
|
+
runs, slower, because ComfyUI keeps part of it in RAM. On Apple Silicon, the list compares it
|
|
162
|
+
with unified memory.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
lcode media # image and video models, what fits, what's installed
|
|
166
|
+
lcode media install z-image-turbo # download what's missing (asks first; --yes doesn't)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Measured
|
|
170
|
+
|
|
171
|
+
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
|
|
172
|
+
`--quality fast` image (720×720) in 1.3 to 2.0 s once it's loaded.
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
On an RTX 4080 Laptop GPU (12 GB) with 32 GB of RAM, ComfyUI 0.38, 1024×1024, four different
|
|
176
|
+
prompts each:
|
|
177
|
+
|
|
178
|
+
| Model | First image (ComfyUI starting, model loading) | Each next image | Per step |
|
|
179
|
+
|---|---|---|---|
|
|
180
|
+
| FLUX.2 klein 4B (fp8) | 14.3 s | 3.5 s | 0.6 s (4 steps) |
|
|
181
|
+
| Z-Image Turbo (int8) | 22.9 s | 17.4 s | 1.9 s (8 steps) |
|
|
182
|
+
|
|
183
|
+
ComfyUI uses whatever GPU memory is free to keep models loaded (11.3 to 11.5 GB here), so the
|
|
184
|
+
memory a model *needs* is lower than what `nvidia-smi` shows while it runs.
|
|
185
|
+
|
|
186
|
+
## One model on the GPU at a time
|
|
187
|
+
|
|
188
|
+
Image and video modes don't need the big coding model: the image or video model does the work. A
|
|
189
|
+
small optional model will help with prompts later. Image and video models need the GPU to
|
|
190
|
+
themselves:
|
|
191
|
+
|
|
192
|
+
- **Into image or video mode:** lcode unloads its coding model from Ollama, and checks again
|
|
193
|
+
before each image.
|
|
194
|
+
- **Back to code mode:** lcode stops the ComfyUI it started, or asks yours to free its memory,
|
|
195
|
+
then loads the coding model again in the background.
|
|
196
|
+
|
|
197
|
+
## Coming next
|
|
198
|
+
|
|
199
|
+
Video mode doesn't generate yet: a request there says what's coming instead of going anywhere.
|
|
200
|
+
|
|
201
|
+
- **#150 Media model catalog** (image models done; video models come with video generation).
|
|
202
|
+
- **#151, #152 Image mode:** generating, variations, sizes and seeds, a gallery, editing,
|
|
203
|
+
references, inpainting, upscaling.
|
|
204
|
+
- **#153, #154 Video mode:** from text or a start image, first and last frames, several
|
|
205
|
+
references, camera moves, longer clips, sound.
|
|
206
|
+
- **#155 Movies:** storyboards with a consistent cast, shots stitched into one film.
|
|
207
|
+
- **#156 Prompt assistant:** a small local model for prompts and follow-ups.
|
|
208
|
+
- **#157 Cloud generation**, with costs shown (Grok Imagine first).
|
|
209
|
+
|
|
210
|
+
The permission mode (`ask`, `plan`, `auto-edit`, `yolo`) is a different setting: `/permissions`
|
|
211
|
+
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)
|
|
@@ -80,6 +81,7 @@ reasoning is on.
|
|
|
80
81
|
| `/think [on\|off]` | Toggle reasoning |
|
|
81
82
|
| `/verbose` | Toggle showing the reasoning text |
|
|
82
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) |
|
|
83
85
|
| `/permissions [ask\|plan\|auto-edit\|yolo]` | Set the permission mode |
|
|
84
86
|
| `/cd DIR` | Change the working directory |
|
|
85
87
|
| `/todos` | Show the model's task list |
|
|
@@ -308,6 +308,8 @@ class Settings:
|
|
|
308
308
|
comfyui_url: str = "http://127.0.0.1:8188"
|
|
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
|
+
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)
|
|
311
313
|
num_batch: int | None = None
|
|
312
314
|
keep_alive: str = "30m"
|
|
313
315
|
think: bool = True
|
|
@@ -374,6 +376,11 @@ class Agent:
|
|
|
374
376
|
self.interactive = True # False for `lcode -p`: no end-of-session questions
|
|
375
377
|
self.comfy_process = None # the ComfyUI lcode started for image and video mode, stopped when it exits
|
|
376
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: tuple[str, ImageOptions] | None = None # for /again
|
|
377
384
|
self.reflected = 0 # messages before this index were already checked for notes to remember
|
|
378
385
|
self._memory: memory_notes.Memory | None = None
|
|
379
386
|
self._agent_types: tuple[Path, dict[str, subagents.AgentType], list[str]] | None = None
|
|
@@ -678,8 +685,10 @@ class Agent:
|
|
|
678
685
|
return any(m.get("role") == "user" for m in self.messages)
|
|
679
686
|
|
|
680
687
|
def save(self) -> None:
|
|
681
|
-
if not (self.has_conversation() or self.session_name):
|
|
688
|
+
if not (self.has_conversation() or self.session_name or self.media_results):
|
|
682
689
|
return # nothing worth resuming
|
|
690
|
+
if not self.session_title and not self.has_conversation() and self.media_results:
|
|
691
|
+
self.session_title = " ".join(self.media_results[0]["prompt"].split())[:60] # an image session
|
|
683
692
|
self.session_title = self.session_title or sessions.title_from(self.messages)
|
|
684
693
|
f = self.session_file()
|
|
685
694
|
f.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -687,6 +696,8 @@ class Agent:
|
|
|
687
696
|
"cwd": str(self.workspace), # where `lcode -c` there finds it, wherever the shell moved
|
|
688
697
|
"model": self.settings.model,
|
|
689
698
|
"mode": self.settings.mode,
|
|
699
|
+
"image_model": self.settings.image_model,
|
|
700
|
+
"media": {"results": self.media_results, "options": self.image_options.to_json()},
|
|
690
701
|
"name": self.session_name,
|
|
691
702
|
"title": self.session_title,
|
|
692
703
|
"messages": self.messages,
|
|
@@ -735,6 +746,11 @@ class Agent:
|
|
|
735
746
|
self.session_name = data.get("name", "")
|
|
736
747
|
if data.get("mode") in ("code", "image", "video"):
|
|
737
748
|
self.settings.mode = data["mode"] # a session belongs to the mode it was made in
|
|
749
|
+
if isinstance(data.get("image_model"), str):
|
|
750
|
+
self.settings.image_model = data["image_model"]
|
|
751
|
+
media = data.get("media") or {}
|
|
752
|
+
self.media_results = [r for r in media.get("results") or [] if isinstance(r, dict) and "path" in r]
|
|
753
|
+
self.image_options = type(self.image_options).from_json(media.get("options") or {})
|
|
738
754
|
self.session_title = data.get("title") or sessions.title_from(self.messages)
|
|
739
755
|
self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
|
|
740
756
|
self.tools.todos = list(data.get("todos") or [])
|
|
@@ -200,6 +200,8 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
200
200
|
comfyui_url=cfg["comfyui_url"],
|
|
201
201
|
comfyui_path=cfg["comfyui_path"],
|
|
202
202
|
media_dir=cfg["media_dir"],
|
|
203
|
+
image_model=cfg["image_model"],
|
|
204
|
+
preview=cfg["preview"],
|
|
203
205
|
web=switch(options.web, cfg["web"], "on"),
|
|
204
206
|
search_backend=cfg["search_backend"],
|
|
205
207
|
searxng_url=cfg["searxng_url"],
|
|
@@ -777,9 +777,10 @@ def exit_on_hangup() -> None:
|
|
|
777
777
|
|
|
778
778
|
def comfyui_status(cfg: dict) -> tuple[str, bool | None]:
|
|
779
779
|
"""lcode doctor's ComfyUI line: running or found, and whether the image model is there."""
|
|
780
|
-
from lcode import comfy
|
|
780
|
+
from lcode import comfy
|
|
781
|
+
from lcode import media_catalog as catalog
|
|
781
782
|
|
|
782
|
-
|
|
783
|
+
model = catalog.load().find(cfg.get("image_model") or "") or catalog.load().models[catalog.DEFAULT_IMAGE]
|
|
783
784
|
server = comfy.Server(cfg["comfyui_url"])
|
|
784
785
|
stats = server.stats()
|
|
785
786
|
install = None if stats else comfy.find_install(cfg["comfyui_path"])
|
|
@@ -787,14 +788,13 @@ def comfyui_status(cfg: dict) -> tuple[str, bool | None]:
|
|
|
787
788
|
return "not found: image mode offers to install it (or lcode config set comfyui_path <folder>)", None
|
|
788
789
|
if stats:
|
|
789
790
|
where = f"{(stats.get('system') or {}).get('comfyui_version', '?')} running at {server.url}"
|
|
790
|
-
installed = {folder: set(server.models(folder)) for folder in workflow.models}
|
|
791
791
|
else:
|
|
792
792
|
where = f"{install.path} (lcode starts it in image mode)"
|
|
793
|
-
|
|
794
|
-
missing = [f"{f}/{names[0]}" for f, names in workflow.models.items() if not installed[f] & set(names)]
|
|
793
|
+
_, missing = catalog.chosen_files(model, catalog.lister(server if stats else None, install))
|
|
795
794
|
if missing:
|
|
796
|
-
|
|
797
|
-
|
|
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
798
|
|
|
799
799
|
|
|
800
800
|
def media_request(agent, prompt: str, out: Console, quiet: bool) -> None:
|
|
@@ -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.
|
|
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
|
|
830
|
-
"model": made.model,
|
|
831
|
-
"seed": made.seed,
|
|
832
|
-
"
|
|
833
|
-
"
|
|
834
|
-
"
|
|
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)
|
|
@@ -909,6 +910,13 @@ def build_subparsers() -> dict[str, argparse.ArgumentParser]:
|
|
|
909
910
|
subs["setup"] = p
|
|
910
911
|
subs["models"] = argparse.ArgumentParser(prog="lcode models", description="List models and how they fit here.")
|
|
911
912
|
subs["doctor"] = argparse.ArgumentParser(prog="lcode doctor", description="Check the installation.")
|
|
913
|
+
p = argparse.ArgumentParser(
|
|
914
|
+
prog="lcode media", description="Image and video models: list them, with how they fit here, or install one."
|
|
915
|
+
)
|
|
916
|
+
p.add_argument("action", nargs="?", choices=["list", "install"], default="list")
|
|
917
|
+
p.add_argument("model", nargs="?", help="with install: a model key from the list, e.g. z-image-turbo")
|
|
918
|
+
p.add_argument("-y", "--yes", action="store_true", help="don't ask before downloading")
|
|
919
|
+
subs["media"] = p
|
|
912
920
|
p = argparse.ArgumentParser(prog="lcode config", description="Show or change settings.")
|
|
913
921
|
p.add_argument("action", nargs="?", choices=["show", "set", "unset", "path"], default="show")
|
|
914
922
|
p.add_argument("key", nargs="?", choices=list(config.SETTINGS))
|
|
@@ -1065,6 +1073,55 @@ def cmd_index(args) -> None:
|
|
|
1065
1073
|
)
|
|
1066
1074
|
|
|
1067
1075
|
|
|
1076
|
+
def cmd_media(args) -> None:
|
|
1077
|
+
"""lcode media [list] | install <model>: the media catalog, what's installed in ComfyUI, downloads."""
|
|
1078
|
+
from rich.prompt import Confirm
|
|
1079
|
+
|
|
1080
|
+
from lcode import comfy, media
|
|
1081
|
+
from lcode import media_catalog as catalog
|
|
1082
|
+
|
|
1083
|
+
try:
|
|
1084
|
+
cfg = config.load()
|
|
1085
|
+
except ConfigError as e:
|
|
1086
|
+
fail(str(e))
|
|
1087
|
+
cat, hw = catalog.load(), detect()
|
|
1088
|
+
server = comfy.Server(cfg["comfyui_url"])
|
|
1089
|
+
install = comfy.find_install(cfg["comfyui_path"])
|
|
1090
|
+
running = server.stats() is not None
|
|
1091
|
+
installed = catalog.lister(server if running else None, install) if running or install else None
|
|
1092
|
+
if args.action == "install":
|
|
1093
|
+
model = cat.find(args.model or "")
|
|
1094
|
+
if model is None:
|
|
1095
|
+
fail(f"no media model {args.model!r}: lcode media lists them")
|
|
1096
|
+
if not running and install is None:
|
|
1097
|
+
fail(f"lcode needs ComfyUI to install models into. {comfy.install_hint()}")
|
|
1098
|
+
ask = None if args.yes else (lambda q: Confirm.ask(q, default=True, console=console))
|
|
1099
|
+
try:
|
|
1100
|
+
saved = catalog.install(model, server if running else None, install, console, ask)
|
|
1101
|
+
except catalog.CatalogError as e:
|
|
1102
|
+
fail(str(e))
|
|
1103
|
+
how = f" ({len(saved)} file(s) downloaded)." if saved else "."
|
|
1104
|
+
console.print(f"[green]{model.name} is installed[/]{how}")
|
|
1105
|
+
return
|
|
1106
|
+
where = (
|
|
1107
|
+
f"ComfyUI at {server.url}" if running else (f"{install.path} (not running)" if install else "no ComfyUI found")
|
|
1108
|
+
)
|
|
1109
|
+
console.print(f"[dim]{hw.describe()} · {where} · image model: {cfg['image_model']}[/]\n")
|
|
1110
|
+
for mode in ("image", "video"):
|
|
1111
|
+
models = cat.for_mode(mode)
|
|
1112
|
+
console.print(f"[bold]{mode.capitalize()} models[/]")
|
|
1113
|
+
if not models:
|
|
1114
|
+
console.print(f" [dim]{media.SOON[mode]}[/]\n")
|
|
1115
|
+
continue
|
|
1116
|
+
for m in models:
|
|
1117
|
+
line = escape(catalog.describe(m, hw, installed))
|
|
1118
|
+
console.print(f" [bold]{m.key:<16}[/] {escape(m.name)} [dim]· {line}[/]")
|
|
1119
|
+
if m.measured:
|
|
1120
|
+
console.print(f" {'':<16} [dim]{escape(m.measured)}[/]")
|
|
1121
|
+
console.print()
|
|
1122
|
+
console.print("[dim]Install one: lcode media install <model> · choose in image mode: /model <model>[/]")
|
|
1123
|
+
|
|
1124
|
+
|
|
1068
1125
|
def cmd_mcp(args) -> None:
|
|
1069
1126
|
from lcode.mcp.commands import run
|
|
1070
1127
|
|
|
@@ -1075,6 +1132,7 @@ COMMANDS = {
|
|
|
1075
1132
|
"setup": cmd_setup,
|
|
1076
1133
|
"models": cmd_models,
|
|
1077
1134
|
"doctor": cmd_doctor,
|
|
1135
|
+
"media": cmd_media,
|
|
1078
1136
|
"config": cmd_config,
|
|
1079
1137
|
"bench": cmd_bench,
|
|
1080
1138
|
"mcp": cmd_mcp,
|
|
@@ -254,6 +254,14 @@ class Server:
|
|
|
254
254
|
except (requests.RequestException, ValueError):
|
|
255
255
|
return []
|
|
256
256
|
|
|
257
|
+
def folder_paths(self) -> dict[str, list[str]]:
|
|
258
|
+
"""Where ComfyUI keeps each kind of model (its own folders and extra_model_paths.yaml's)."""
|
|
259
|
+
try:
|
|
260
|
+
r = requests.get(self.url + "/internal/folder_paths", timeout=10)
|
|
261
|
+
return r.json() if r.status_code == 200 else {}
|
|
262
|
+
except (requests.RequestException, ValueError):
|
|
263
|
+
return {}
|
|
264
|
+
|
|
257
265
|
def upload(self, path: Path) -> str:
|
|
258
266
|
"""Upload an input image; the name to use in a workflow."""
|
|
259
267
|
with path.open("rb") as f:
|
|
@@ -420,32 +428,18 @@ class Events:
|
|
|
420
428
|
|
|
421
429
|
@dataclass
|
|
422
430
|
class Workflow:
|
|
431
|
+
"""A graph in ComfyUI's API format with `$name` placeholders: the model's files (by role, from media.toml),
|
|
432
|
+
the prompt, the seed and the settings in `defaults`."""
|
|
433
|
+
|
|
423
434
|
key: str
|
|
424
|
-
name: str
|
|
425
|
-
task: str
|
|
426
|
-
models: dict[str, list[str]] # ComfyUI folder -> file names it can use, preferred first
|
|
427
|
-
defaults: dict
|
|
428
435
|
graph: dict
|
|
436
|
+
defaults: dict = field(default_factory=dict)
|
|
429
437
|
labels: dict[str, str] = field(default_factory=dict) # node id -> what it does, for progress
|
|
430
438
|
|
|
431
439
|
|
|
432
440
|
def load_workflow(key: str) -> Workflow:
|
|
433
441
|
data = json.loads(resources.files("lcode.workflows").joinpath(f"{key}.json").read_text())
|
|
434
|
-
return Workflow(key, data["
|
|
435
|
-
data.get("labels", {})) # fmt: skip
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def choose_files(server: Server, workflow: Workflow) -> tuple[dict[str, str], list[str]]:
|
|
439
|
-
"""For each model the workflow needs, the installed file to use; and the folders where none is installed."""
|
|
440
|
-
chosen, missing = {}, []
|
|
441
|
-
for folder, names in workflow.models.items():
|
|
442
|
-
installed = set(server.models(folder))
|
|
443
|
-
found = next((n for n in names if n in installed), None)
|
|
444
|
-
if found:
|
|
445
|
-
chosen[folder] = found
|
|
446
|
-
else:
|
|
447
|
-
missing.append(f"{folder}/{names[0]}")
|
|
448
|
-
return chosen, missing
|
|
442
|
+
return Workflow(key, data["graph"], data.get("defaults", {}), data.get("labels", {}))
|
|
449
443
|
|
|
450
444
|
|
|
451
445
|
PLACEHOLDER = re.compile(r"^\$([a-z_]+)$")
|
|
@@ -566,13 +560,15 @@ def describe_history_error(entry: dict) -> str:
|
|
|
566
560
|
|
|
567
561
|
|
|
568
562
|
def save(server: Server, outputs: dict, folder: Path, stem: str, meta: dict) -> list[Path]:
|
|
569
|
-
"""Fetch the job's files into `folder`: stem-
|
|
563
|
+
"""Fetch the job's files into `folder`: stem.png (stem-2.png if that's taken), or stem-1.png, … for several,
|
|
564
|
+
each with its settings in a .json next to it."""
|
|
570
565
|
folder.mkdir(parents=True, exist_ok=True)
|
|
571
566
|
items = [item for node in outputs.values() for kind in OUTPUT_KINDS for item in node.get(kind, [])]
|
|
567
|
+
items = [it for it in items if it.get("type", "output") == "output"]
|
|
572
568
|
saved = []
|
|
573
|
-
for i, item in enumerate(
|
|
569
|
+
for i, item in enumerate(items, 1):
|
|
574
570
|
ext = Path(item["filename"]).suffix or ".png"
|
|
575
|
-
path = unique(folder / f"{stem}-{i}{ext}")
|
|
571
|
+
path = unique(folder / (f"{stem}{ext}" if len(items) == 1 else f"{stem}-{i}{ext}"))
|
|
576
572
|
data = server.fetch(item)
|
|
577
573
|
if ext.lower() == ".png":
|
|
578
574
|
data = with_text(data, "lcode", json.dumps(meta, ensure_ascii=False))
|
|
@@ -45,6 +45,8 @@ SETTINGS: dict[str, tuple[object, type, str]] = {
|
|
|
45
45
|
"comfyui_url": ("http://127.0.0.1:8188", str, "ComfyUI, for image and video mode"),
|
|
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
|
+
"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"),
|
|
48
50
|
"think": (True, bool, "let the model reason before answering (slower, better)"),
|
|
49
51
|
"web": ("on", str, "web search and page fetching for the model: on | ask | off"),
|
|
50
52
|
"search_backend": ("auto", str, "auto | ollama | brave | tavily | searxng (keys come from environment variables)"),
|
|
@@ -210,6 +212,17 @@ def coerce(key: str, value: object) -> object:
|
|
|
210
212
|
raise ConfigError(f"permission_mode must be one of {', '.join(PERMISSION_MODES)}")
|
|
211
213
|
if key == "mode" and value not in WORK_MODES:
|
|
212
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)}")
|
|
220
|
+
if key == "image_model":
|
|
221
|
+
from lcode import media_catalog
|
|
222
|
+
|
|
223
|
+
keys = [m.key for m in media_catalog.load().for_mode("image")]
|
|
224
|
+
if value not in keys:
|
|
225
|
+
raise ConfigError(f"image_model must be one of {', '.join(keys)} (see lcode media)")
|
|
213
226
|
if typ is bool and isinstance(value, str):
|
|
214
227
|
if value.lower() not in ("true", "false", "1", "0", "yes", "no", "on", "off"):
|
|
215
228
|
raise ConfigError(f"{key} must be true or false")
|