lcode-cli 0.38.0__tar.gz → 0.39.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/CHANGELOG.md +23 -1
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/PKG-INFO +1 -1
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/configuration.md +2 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/mcp.md +5 -81
- lcode_cli-0.39.0/docs/modes.md +58 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/quickstart.md +1 -1
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/usage.md +4 -3
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/mkdocs.yml +1 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/agent.py +5 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/api.py +3 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/cli.py +12 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/config.py +5 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/catalog.toml +0 -48
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/commands.py +6 -0
- lcode_cli-0.39.0/src/lcode/modes.py +102 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/repl.py +57 -10
- lcode_cli-0.39.0/tests/test_modes.py +154 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_xai.py +0 -11
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.editorconfig +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.gitignore +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/AGENTS.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/LICENSE +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/README.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/SECURITY.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/action.yml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/agents.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/automation.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/bench.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/changelog.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/commands.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/contributing.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/editors.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/git.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/hooks.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/how-it-works.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/index.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/installation.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/lsp.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/memory.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/models.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/sandbox.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/search.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/servers.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/examples/README.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/examples/api/fix_tests.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/examples/commands/review.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/examples/skills/commit-message/SKILL.md +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/install.sh +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/overrides/main.html +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/pyproject.toml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/scripts/make_logos.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/acp.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/action.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/backends.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/bench.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/bench_backend.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/bench_runtime.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/checks.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/clipboard.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/cloud.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/codesearch.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/context.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/escalate.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/extensions.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/frontmatter.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/gitflow.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/hooks.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/jobs.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/lsp.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/matching.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/catalog.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/server.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/memory.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/notify.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/permissions.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/planning.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/repeats.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/repomap.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/residency.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/review.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/screenshots.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/secretscan.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/servers/__init__.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/servers/encord.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/servers/valohai.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/servers/xai.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/steer.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/subagents.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/tools.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/unreachable.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/update.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/vision.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/conftest.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/fake_lsp_server.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_acp.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_action.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_api.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_backends.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_bench.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_checks.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_cli.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_clipboard.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_cloud.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_codesearch.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_config.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_context.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_escalate.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_extensions.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_gitflow.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_hooks.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_jobs.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_look.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_lsp.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_matching.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_mcp.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_memory.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_model_picker.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_plan_mode.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_repeats.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_repomap.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_residency.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_review.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_robustness.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_screenshots.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_secretscan.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_servers.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_steer.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_stuck.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_subagents.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_tools.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_update.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_vision.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/tests/test_web.py +0 -0
- {lcode_cli-0.38.0 → lcode_cli-0.39.0}/uv.lock +0 -0
|
@@ -6,6 +6,27 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.39.0] - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Modes: code, image and video** (#148). `/mode image`, `/mode video` and `/mode code` switch
|
|
14
|
+
what lcode makes. `lcode --mode` starts in one, and the `mode` setting is the default. The
|
|
15
|
+
banner, status bar and prompt show the mode, and sessions keep theirs.
|
|
16
|
+
- **The GPU goes to one model at a time:** switching to image or video mode unloads the coding
|
|
17
|
+
model, and switching back asks ComfyUI to free its memory.
|
|
18
|
+
- **Image and video mode don't generate yet:** they say what's coming (#149–#157) instead of
|
|
19
|
+
sending your words to the coding model. See [Modes](https://nasser1941.github.io/lcode/modes/).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Code mode makes code only.** The MCP servers that generated images and videos (`comfyui`,
|
|
24
|
+
`xai`) are out of the MCP catalog, and lcode doesn't load them in code mode even if they're
|
|
25
|
+
still in `mcp.json` (it says so once). Image and video generation are moving to their own modes.
|
|
26
|
+
- **`/mode` switches code, image and video.** The permission mode is set with `/permissions` now;
|
|
27
|
+
`/mode yolo` and the like still work, with a note. Shift+Tab cycles the permission mode as
|
|
28
|
+
before.
|
|
29
|
+
|
|
9
30
|
## [0.38.0] - 2026-10-21
|
|
10
31
|
|
|
11
32
|
### Added
|
|
@@ -855,7 +876,8 @@ First public release.
|
|
|
855
876
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
856
877
|
- One-line installer for Linux and macOS.
|
|
857
878
|
|
|
858
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
879
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.39.0...HEAD
|
|
880
|
+
[0.39.0]: https://github.com/nasser1941/lcode/compare/v0.38.0...v0.39.0
|
|
859
881
|
[0.38.0]: https://github.com/nasser1941/lcode/compare/v0.37.0...v0.38.0
|
|
860
882
|
[0.37.0]: https://github.com/nasser1941/lcode/compare/v0.36.0...v0.37.0
|
|
861
883
|
[0.36.0]: https://github.com/nasser1941/lcode/compare/v0.35.1...v0.36.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.39.0
|
|
4
4
|
Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
|
|
5
5
|
Project-URL: Homepage, https://nasser1941.github.io/lcode/
|
|
6
6
|
Project-URL: Documentation, https://nasser1941.github.io/lcode/
|
|
@@ -23,6 +23,8 @@ lcode config path # print the file location
|
|
|
23
23
|
| `backend` | `ollama` | The model server: `ollama`, `lmstudio`, `llama.cpp`, `vllm`, `mlx` or `openai` (any OpenAI-compatible API); see [Other model servers](servers.md) |
|
|
24
24
|
| `base_url` | the backend's | The server's OpenAI-compatible address, e.g. `http://localhost:1234/v1` |
|
|
25
25
|
| `permission_mode` | `ask` | `ask`, `plan`, `auto-edit` or `yolo` |
|
|
26
|
+
| `mode` | `code` | What lcode makes when it starts: `code`, `image` or `video` ([modes](modes.md)) |
|
|
27
|
+
| `comfyui_url` | `http://127.0.0.1:8188` | ComfyUI's address, for image and video mode |
|
|
26
28
|
| `think` | `true` | Let the model reason before answering |
|
|
27
29
|
| `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
|
|
28
30
|
| `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
|
|
@@ -28,8 +28,6 @@ and find the code for the top one."*
|
|
|
28
28
|
| `gcp` | `gcloud` commands on your Google Cloud projects | `npx` and the `gcloud` CLI, signed in |
|
|
29
29
|
| `github` | Repositories, issues, pull requests, Actions | a GitHub token (or the GitHub CLI) |
|
|
30
30
|
| `playwright` | A real (headless) browser: open pages, click, fill forms | `npx` |
|
|
31
|
-
| `comfyui` | Generate and edit images with local models (FLUX, SDXL, SD 1.5, Qwen-Image) | `uvx` and [ComfyUI](#images-with-comfyui) running locally |
|
|
32
|
-
| `xai` | Generate and edit images, and generate videos and speech, with xAI's Grok Imagine (paid, in the cloud) | `uvx`, an xAI API key ([below](#images-videos-and-speech-with-xai)) |
|
|
33
31
|
| `context7` | Up-to-date docs and examples for thousands of libraries | nothing (an API key is optional) |
|
|
34
32
|
| `sentry` | Errors, issues, traces and releases | a Sentry account (browser sign-in) |
|
|
35
33
|
| `postgres` | Schemas, read-only queries, query performance | `uvx`, a connection URL |
|
|
@@ -87,86 +85,12 @@ Google Cloud project:
|
|
|
87
85
|
See [Google's guide](https://developers.google.com/workspace/guides/configure-mcp-servers) for
|
|
88
86
|
details.
|
|
89
87
|
|
|
90
|
-
### Images
|
|
88
|
+
### Images and videos
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
uv tool install comfy-cli && comfy install # ComfyUI, once
|
|
98
|
-
comfy launch --background -- --disable-smart-memory # start it (http://127.0.0.1:8188)
|
|
99
|
-
lcode mcp add comfyui
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Then add models in ComfyUI (its model manager, or ask lcode: *"download FLUX.2 Klein 4B in
|
|
103
|
-
ComfyUI"*), and ask for images: *"make a 512×512 app icon of a paper plane and save it in
|
|
104
|
-
assets/"*. To edit an image, ask lcode to upload it and run an editing workflow (Kontext or
|
|
105
|
-
Qwen-Image-Edit).
|
|
106
|
-
|
|
107
|
-
| Model | Good for | On a 12 GB GPU |
|
|
108
|
-
|---|---|---|
|
|
109
|
-
| FLUX.2 Klein 4B | Generation and editing, fast | Fits (about 8 GB) |
|
|
110
|
-
| SDXL and its community models | Generation, inpainting | Fits |
|
|
111
|
-
| Stable Diffusion 1.5 and its community models | Light generation, inpainting | Fits easily |
|
|
112
|
-
| FLUX.1 Dev and finetunes | High-quality generation | Needs an fp8 or GGUF version; slower |
|
|
113
|
-
| FLUX.1 Kontext Dev | Editing an image from instructions | Needs an fp8 or GGUF version; slower |
|
|
114
|
-
| Qwen-Image / Qwen-Image-Edit | Generation and editing, good text in images | Heavy: a GGUF version and RAM offloading |
|
|
115
|
-
|
|
116
|
-
Only ComfyUI's local tools are turned on; Comfy Cloud's partner tools are left out, so prompts and
|
|
117
|
-
images stay on your machine.
|
|
118
|
-
|
|
119
|
-
**Sharing one GPU.** A GPU rarely holds a coding model and an image model at the same time, so the
|
|
120
|
-
two take turns: before ComfyUI generates, lcode unloads its own model (the `free_gpu` setting), and
|
|
121
|
-
ComfyUI started with `--disable-smart-memory` gives the GPU back after each image. lcode's model
|
|
122
|
-
then reloads for the next step, which adds a few seconds to half a minute per image request. With
|
|
123
|
-
enough GPU memory for both (or on a Mac with plenty of memory), remove `free_gpu` from the server's
|
|
124
|
-
settings in `mcp.json`. lcode can also look at the results ([Images](usage.md#images)).
|
|
125
|
-
|
|
126
|
-
Measured on an RTX 4080 Laptop GPU (12 GB) with qwen3.6-35b at 64K context and FLUX.2 Klein Base 4B
|
|
127
|
-
(fp8, 512×512, 20 steps):
|
|
128
|
-
|
|
129
|
-
| | |
|
|
130
|
-
|---|---|
|
|
131
|
-
| Generating while lcode's model is loaded | Fails: ComfyUI runs out of GPU memory |
|
|
132
|
-
| Freeing the GPU (lcode unloads its model) | 0.2 s |
|
|
133
|
-
| Generating one image | about 12 s |
|
|
134
|
-
| Reloading lcode's model afterwards | about 20 s |
|
|
135
|
-
|
|
136
|
-
The first time, the model has to find the right template and fill in its settings (model file names,
|
|
137
|
-
size, prompt), which can take several minutes of trial and error. Once an image comes out right, ask
|
|
138
|
-
lcode to save that workflow in your project (for example `assets/icon.workflow.json`) and reuse it:
|
|
139
|
-
later images are a single `run_workflow` call.
|
|
140
|
-
|
|
141
|
-
### Images, videos and speech with xAI
|
|
142
|
-
|
|
143
|
-
xAI's [Grok Imagine](https://docs.x.ai/developers/model-capabilities/imagine) and
|
|
144
|
-
[text to speech](https://docs.x.ai/developers/models/text-to-speech) APIs make images, videos and
|
|
145
|
-
speech in the cloud: no GPU needed, but each call is billed to your xAI account. lcode ships an MCP
|
|
146
|
-
server for them, `lcode-mcp-xai`.
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
lcode mcp add xai # asks for your xAI API key (create one at https://console.x.ai)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
The key is typed hidden and kept in `~/.config/lcode/mcp.json`, readable only by you. If
|
|
153
|
-
`XAI_API_KEY` is already set in your environment, lcode uses that and doesn't store the key.
|
|
154
|
-
|
|
155
|
-
| Tool | | Costs |
|
|
156
|
-
|---|---|---|
|
|
157
|
-
| `generate_image` | Images from a description: 1–10 at a time, aspect ratio, 1k or 2k | $0.02–0.05 an image |
|
|
158
|
-
| `edit_image` | Change a local image as described | the same |
|
|
159
|
-
| `generate_video` | A 1–15 second MP4 from a description, optionally starting from a local image, up to 1080p. Waits for it (a minute or more) | $0.02–0.08 a second |
|
|
160
|
-
| `video_status` | Fetch a video that wasn't ready within 10 minutes | |
|
|
161
|
-
| `text_to_speech` | Speech in one of 26 voices and 20 languages, as MP3 or WAV | per character |
|
|
162
|
-
|
|
163
|
-
Files are saved in `generated/` in the project, or where you ask (*"generate an app icon and save
|
|
164
|
-
it as assets/icon.png"*). xAI has no music generation API.
|
|
165
|
-
|
|
166
|
-
Your prompts, and the images you edit or animate, go to xAI. lcode asks before each call, since each
|
|
167
|
-
one costs money. To stop asking for some tools, list them under `"allow"` for the server in
|
|
168
|
-
`mcp.json`, for example `"allow": ["generate_image", "text_to_speech"]`. xAI applies its own usage
|
|
169
|
-
policies to what it generates; when it refuses a prompt, lcode passes its message to the model.
|
|
90
|
+
Code mode makes code only, so the servers that generated images and videos in earlier versions
|
|
91
|
+
(`comfyui`, `xai`) aren't in the catalog any more, and lcode doesn't load them in code mode if
|
|
92
|
+
they're still in your `mcp.json`. Image and video generation get their own [modes](modes.md),
|
|
93
|
+
with their own models and controls.
|
|
170
94
|
|
|
171
95
|
### Metabase
|
|
172
96
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Modes: code, image and video
|
|
2
|
+
|
|
3
|
+
lcode makes three kinds of things, each in its own mode with its own models:
|
|
4
|
+
|
|
5
|
+
| Mode | What it makes | The model doing the work |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| **code** (default) | Code: it reads, writes, runs and checks your project | A coding language model in Ollama (or another server) |
|
|
8
|
+
| **image** | Images from a description and reference images | An image model, run by ComfyUI on your GPU |
|
|
9
|
+
| **video** | Videos from a description, a start image or references | A video model, run by ComfyUI on your GPU |
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
❯ /mode image
|
|
13
|
+
Mode: image (local image generation).
|
|
14
|
+
Unloaded qwen3.6-35b to free the GPU for images.
|
|
15
|
+
image ❯
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **Switching:** `/mode code`, `/mode image` and `/mode video` switch in a session. `lcode --mode image`
|
|
19
|
+
starts in one, and `lcode config set mode video` makes it the default. `/mode` alone shows
|
|
20
|
+
the current one.
|
|
21
|
+
- **Where it shows:** the banner, the status bar and the prompt (`image ❯`) show the mode.
|
|
22
|
+
- **Sessions:** a session keeps the mode it was made in, and resuming it brings that mode back.
|
|
23
|
+
|
|
24
|
+
## Code mode makes code only
|
|
25
|
+
|
|
26
|
+
Code mode is the coding agent, as before, and it no longer generates images or videos. The MCP
|
|
27
|
+
servers that did (`comfyui` and `xai`) are gone from the MCP catalog, and lcode doesn't load them
|
|
28
|
+
in code mode even if they're still in your `mcp.json`. It says so once.
|
|
29
|
+
|
|
30
|
+
## One model on the GPU at a time
|
|
31
|
+
|
|
32
|
+
Image and video modes don't need the big coding model: the image or video model does the work. A
|
|
33
|
+
small optional model will help with prompts later. Image and video models need the GPU to
|
|
34
|
+
themselves:
|
|
35
|
+
|
|
36
|
+
- **Into image or video mode:** lcode unloads its coding model from Ollama.
|
|
37
|
+
- **Back to code mode:** lcode asks ComfyUI to free its memory (at `comfyui_url`, by default
|
|
38
|
+
`http://127.0.0.1:8188`), then loads the coding model again in the background.
|
|
39
|
+
|
|
40
|
+
## Coming next
|
|
41
|
+
|
|
42
|
+
Image and video modes don't generate yet. Until the items below arrive, a request in those modes
|
|
43
|
+
says what's coming instead of going anywhere.
|
|
44
|
+
|
|
45
|
+
- **#149 Local generation engine:** lcode drives ComfyUI itself: finding or installing it, jobs,
|
|
46
|
+
progress, cancelling, and saving outputs with their settings.
|
|
47
|
+
- **#150 Media model catalog:** image and video models that fit your machine, installed on
|
|
48
|
+
demand.
|
|
49
|
+
- **#151, #152 Image mode:** generating, variations, sizes and seeds, a gallery, editing,
|
|
50
|
+
references, inpainting, upscaling.
|
|
51
|
+
- **#153, #154 Video mode:** from text or a start image, first and last frames, several
|
|
52
|
+
references, camera moves, longer clips, sound.
|
|
53
|
+
- **#155 Movies:** storyboards with a consistent cast, shots stitched into one film.
|
|
54
|
+
- **#156 Prompt assistant:** a small local model for prompts and follow-ups.
|
|
55
|
+
- **#157 Cloud generation**, with costs shown (Grok Imagine first).
|
|
56
|
+
|
|
57
|
+
The permission mode (`ask`, `plan`, `auto-edit`, `yolo`) is a different setting: `/permissions`
|
|
58
|
+
sets it, and Shift+Tab cycles it as before.
|
|
@@ -79,7 +79,8 @@ reasoning is on.
|
|
|
79
79
|
| `/models`, `/model [number or name]` | List the models and how they fit this machine, numbered: type a number to switch, or `/model 2`, `/model qwen3.5-9b` |
|
|
80
80
|
| `/think [on\|off]` | Toggle reasoning |
|
|
81
81
|
| `/verbose` | Toggle showing the reasoning text |
|
|
82
|
-
| `/mode [
|
|
82
|
+
| `/mode [code\|image\|video]` | Switch what lcode makes: [code, images or videos](modes.md) |
|
|
83
|
+
| `/permissions [ask\|plan\|auto-edit\|yolo]` | Set the permission mode |
|
|
83
84
|
| `/cd DIR` | Change the working directory |
|
|
84
85
|
| `/todos` | Show the model's task list |
|
|
85
86
|
| `/status` | The task sheet: your latest requests, the plan and the model's notes, files changed, the last check |
|
|
@@ -315,7 +316,7 @@ stays loaded:
|
|
|
315
316
|
| The model was loaded | `◆ Loaded qwen3.6-35b in 15s: 22.3 GB, 45% on the GPU and the rest in system RAM.`, and a warning when less of it is on the GPU than usual, because another program or model is using GPU memory |
|
|
316
317
|
| A dense model doesn't fit on the GPU (at startup and on `/model`) | `⚠ qwen3.8-27b is a dense model: all 27B parameters work on every token. It's capable, but slow here. Only about 61% of it fits in the 12 GB of GPU memory…`, with what to do instead |
|
|
317
318
|
| The model that looks at images needs the memory | `Loading the vision model … to look at bug.png; qwen3.6-35b makes room and reloads afterwards`, then `◆ qwen3.6-35b was unloaded to make room…`, and the next step says `Reloading qwen3.6-35b into memory (it made room for the vision model …)` |
|
|
318
|
-
| A tool needs the GPU to itself
|
|
319
|
+
| A tool needs the GPU to itself (an MCP server with `free_gpu`) | `◆ Unloaded qwen3.6-35b so the server can use the GPU; it reloads afterwards.` |
|
|
319
320
|
| Ollama ran out of memory | `⚠` what ran out (the prompt batch, or the context next to the model), what lcode retries with, and what it remembers for next time |
|
|
320
321
|
|
|
321
322
|
While the model loads in the background at startup, the status bar says
|
|
@@ -470,7 +471,7 @@ a screen and sees what you would, measured:
|
|
|
470
471
|
| `auto-edit` | automatic | ask, except read-only commands |
|
|
471
472
|
| `yolo` | automatic | automatic |
|
|
472
473
|
|
|
473
|
-
++shift+tab++ cycles through them in this order, `/
|
|
474
|
+
++shift+tab++ cycles through them in this order, `/permissions` sets one, and `--plan`, `--auto-edit` and
|
|
474
475
|
`--yolo` start in one.
|
|
475
476
|
|
|
476
477
|
Read-only commands run without asking: `ls`, `cat`, `head`, `tail`, `grep`, `rg`, `find`, `tree`,
|
|
@@ -304,6 +304,8 @@ class Progress:
|
|
|
304
304
|
class Settings:
|
|
305
305
|
model: str # Ollama model name to call
|
|
306
306
|
context: int
|
|
307
|
+
mode: str = "code" # what lcode makes: code, image or video (lcode.modes)
|
|
308
|
+
comfyui_url: str = "http://127.0.0.1:8188"
|
|
307
309
|
num_batch: int | None = None
|
|
308
310
|
keep_alive: str = "30m"
|
|
309
311
|
think: bool = True
|
|
@@ -680,6 +682,7 @@ class Agent:
|
|
|
680
682
|
data = {
|
|
681
683
|
"cwd": str(self.workspace), # where `lcode -c` there finds it, wherever the shell moved
|
|
682
684
|
"model": self.settings.model,
|
|
685
|
+
"mode": self.settings.mode,
|
|
683
686
|
"name": self.session_name,
|
|
684
687
|
"title": self.session_title,
|
|
685
688
|
"messages": self.messages,
|
|
@@ -726,6 +729,8 @@ class Agent:
|
|
|
726
729
|
self.session_id = info.id
|
|
727
730
|
self.checkpoints.load(info.id, data.get("checkpoints") or [])
|
|
728
731
|
self.session_name = data.get("name", "")
|
|
732
|
+
if data.get("mode") in ("code", "image", "video"):
|
|
733
|
+
self.settings.mode = data["mode"] # a session belongs to the mode it was made in
|
|
729
734
|
self.session_title = data.get("title") or sessions.title_from(self.messages)
|
|
730
735
|
self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
|
|
731
736
|
self.tools.todos = list(data.get("todos") or [])
|
|
@@ -54,6 +54,7 @@ class Options:
|
|
|
54
54
|
model: str | None = None
|
|
55
55
|
context: int | str | None = None
|
|
56
56
|
permission_mode: str | None = None # ask, plan, auto-edit or yolo
|
|
57
|
+
mode: str | None = None # code, image or video
|
|
57
58
|
allowed_tools: list[str] | None = None # only these tools (default: all)
|
|
58
59
|
max_steps: int | None = None # model steps per request
|
|
59
60
|
think: bool | None = None
|
|
@@ -179,6 +180,8 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
179
180
|
think=cfg["think"] if options.think is None else options.think,
|
|
180
181
|
show_thinking=options.show_thinking,
|
|
181
182
|
permission_mode=options.permission_mode or cfg["permission_mode"],
|
|
183
|
+
mode=options.mode or cfg["mode"],
|
|
184
|
+
comfyui_url=cfg["comfyui_url"],
|
|
182
185
|
web=switch(options.web, cfg["web"], "on"),
|
|
183
186
|
search_backend=cfg["search_backend"],
|
|
184
187
|
searxng_url=cfg["searxng_url"],
|
|
@@ -714,6 +714,7 @@ def cmd_chat(args) -> None:
|
|
|
714
714
|
model=args.model,
|
|
715
715
|
context=args.context,
|
|
716
716
|
permission_mode=mode,
|
|
717
|
+
mode=args.mode,
|
|
717
718
|
allowed_tools=tools,
|
|
718
719
|
max_steps=args.max_steps,
|
|
719
720
|
think=False if args.no_think else None,
|
|
@@ -733,6 +734,16 @@ def cmd_chat(args) -> None:
|
|
|
733
734
|
sys.exit(1)
|
|
734
735
|
fail(str(e))
|
|
735
736
|
exit_code = 0
|
|
737
|
+
if args.prompt and agent.settings.mode != "code":
|
|
738
|
+
from lcode import modes
|
|
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)
|
|
736
747
|
if args.escalate and args.prompt:
|
|
737
748
|
agent.escalate_approved = True # the flag is the user's OK for this run
|
|
738
749
|
try:
|
|
@@ -798,6 +809,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
798
809
|
help="work in a git worktree of its own, on branch NAME (new, or continue one); removed if nothing changed",
|
|
799
810
|
)
|
|
800
811
|
parser.add_argument("--plan", action="store_true", help="start in plan mode: agree on a plan before any change")
|
|
812
|
+
parser.add_argument("--mode", choices=config.WORK_MODES, help="what lcode makes: code (default), image or video")
|
|
801
813
|
parser.add_argument("--auto-edit", action="store_true", help="apply file edits without asking")
|
|
802
814
|
parser.add_argument("--yolo", action="store_true", help="never ask for permission (edits and commands)")
|
|
803
815
|
parser.add_argument(
|
|
@@ -23,6 +23,7 @@ else:
|
|
|
23
23
|
|
|
24
24
|
DEFAULT_MODEL = "qwen3.6-35b"
|
|
25
25
|
PERMISSION_MODES = ("ask", "plan", "auto-edit", "yolo") # Shift+Tab cycles in this order
|
|
26
|
+
WORK_MODES = ("code", "image", "video") # what lcode makes (lcode.modes)
|
|
26
27
|
WEB_MODES = ("on", "ask", "off")
|
|
27
28
|
SEARCH_BACKENDS = ("auto", "ollama", "brave", "tavily", "searxng")
|
|
28
29
|
MCP_TOOL_MODES = ("auto", "direct", "search")
|
|
@@ -40,6 +41,8 @@ SETTINGS: dict[str, tuple[object, type, str]] = {
|
|
|
40
41
|
"backend": ("ollama", str, "model server: ollama | lmstudio | llama.cpp | vllm | mlx | openai (any OpenAI API)"),
|
|
41
42
|
"base_url": (None, str, "OpenAI-compatible server address, e.g. http://localhost:1234/v1 (default: the backend's)"),
|
|
42
43
|
"permission_mode": ("ask", str, "ask | plan | auto-edit | yolo"),
|
|
44
|
+
"mode": ("code", str, "what lcode makes when it starts: code | image | video"),
|
|
45
|
+
"comfyui_url": ("http://127.0.0.1:8188", str, "ComfyUI, for image and video mode"),
|
|
43
46
|
"think": (True, bool, "let the model reason before answering (slower, better)"),
|
|
44
47
|
"web": ("on", str, "web search and page fetching for the model: on | ask | off"),
|
|
45
48
|
"search_backend": ("auto", str, "auto | ollama | brave | tavily | searxng (keys come from environment variables)"),
|
|
@@ -203,6 +206,8 @@ def coerce(key: str, value: object) -> object:
|
|
|
203
206
|
raise ConfigError(f"search_backend must be one of {', '.join(SEARCH_BACKENDS)}")
|
|
204
207
|
if key == "permission_mode" and value not in PERMISSION_MODES:
|
|
205
208
|
raise ConfigError(f"permission_mode must be one of {', '.join(PERMISSION_MODES)}")
|
|
209
|
+
if key == "mode" and value not in WORK_MODES:
|
|
210
|
+
raise ConfigError(f"mode must be one of {', '.join(WORK_MODES)}")
|
|
206
211
|
if typ is bool and isinstance(value, str):
|
|
207
212
|
if value.lower() not in ("true", "false", "1", "0", "yes", "no", "on", "off"):
|
|
208
213
|
raise ConfigError(f"{key} must be true or false")
|
|
@@ -130,34 +130,6 @@ requires = ["npx"]
|
|
|
130
130
|
setup = "Runs a headless browser; the first use may download it."
|
|
131
131
|
server = { command = "npx", args = ["-y", "@playwright/mcp@latest", "--headless"] }
|
|
132
132
|
|
|
133
|
-
[comfyui]
|
|
134
|
-
name = "ComfyUI"
|
|
135
|
-
description = "Generate and edit images with models you run locally in ComfyUI: FLUX, SDXL, SD 1.5, Qwen-Image"
|
|
136
|
-
homepage = "https://github.com/Comfy-Org/comfy-mcp"
|
|
137
|
-
requires = ["uvx"]
|
|
138
|
-
setup = """Needs ComfyUI on this machine (http://127.0.0.1:8188) with at least one image model:
|
|
139
|
-
uv tool install comfy-cli && comfy install
|
|
140
|
-
comfy launch --background -- --disable-smart-memory
|
|
141
|
-
--disable-smart-memory makes ComfyUI give the GPU back after each image. Before generating, lcode
|
|
142
|
-
unloads its own model so ComfyUI has the GPU; it reloads afterwards. Only the local tools are on;
|
|
143
|
-
Comfy Cloud's partner tools are left out, so nothing leaves this machine."""
|
|
144
|
-
|
|
145
|
-
[comfyui.server]
|
|
146
|
-
command = "uvx"
|
|
147
|
-
args = ["--python", "3.12", "--from", "comfy-mcp", "--with", "comfy-cli>=1.14.0", "comfy-mcp"]
|
|
148
|
-
env = { COMFYUI_URL = "${COMFYUI_URL}" }
|
|
149
|
-
tools = [
|
|
150
|
-
"server_info", "generate_image", "run_workflow", "run_template", "search_templates", "get_template", "fetch_template",
|
|
151
|
-
"list_workflow_slots", "set_workflow_slot", "job", "fetch_outputs", "upload_file", "search_models",
|
|
152
|
-
"download_model", "system_stats", "free_memory", "launch_comfyui",
|
|
153
|
-
]
|
|
154
|
-
free_gpu = ["generate_image", "run_workflow", "run_template"]
|
|
155
|
-
|
|
156
|
-
[[comfyui.inputs]]
|
|
157
|
-
var = "COMFYUI_URL"
|
|
158
|
-
prompt = "ComfyUI address (Enter for this machine, 127.0.0.1:8188)"
|
|
159
|
-
optional = true
|
|
160
|
-
|
|
161
133
|
[context7]
|
|
162
134
|
name = "Context7"
|
|
163
135
|
description = "Current documentation and code examples for thousands of libraries"
|
|
@@ -282,23 +254,3 @@ var = "VALOHAI_HOST"
|
|
|
282
254
|
prompt = "Valohai address (Enter for app.valohai.com; for a self-hosted installation, its URL)"
|
|
283
255
|
optional = true
|
|
284
256
|
|
|
285
|
-
[xai]
|
|
286
|
-
name = "xAI Grok Imagine"
|
|
287
|
-
description = "Generate and edit images, and generate videos and speech, with xAI's Grok Imagine and voice API (paid, in the cloud)"
|
|
288
|
-
homepage = "https://nasser1941.github.io/lcode/mcp/#xai"
|
|
289
|
-
requires = ["uvx"]
|
|
290
|
-
setup = """Uses xAI's API: your prompts, and images you edit or animate, go to xAI, and every call is billed to your xAI
|
|
291
|
-
account (an image $0.02-0.05, video $0.02-0.08 a second, speech per character). Create an API key at https://console.x.ai.
|
|
292
|
-
Files are saved in generated/ in the project. lcode asks before each call; to stop asking for some tools, list them
|
|
293
|
-
under "allow" for this server in mcp.json. xAI has no music generation."""
|
|
294
|
-
|
|
295
|
-
[xai.server]
|
|
296
|
-
command = "uvx"
|
|
297
|
-
args = ["--isolated", "--python", "3.12", "--from", "lcode-cli", "lcode-mcp-xai"]
|
|
298
|
-
env = { XAI_API_KEY = "${XAI_API_KEY}" }
|
|
299
|
-
timeout = 900 # a video takes minutes
|
|
300
|
-
|
|
301
|
-
[[xai.inputs]]
|
|
302
|
-
var = "XAI_API_KEY"
|
|
303
|
-
prompt = "xAI API key (create one at https://console.x.ai)"
|
|
304
|
-
secret = true
|
|
@@ -60,6 +60,12 @@ def start_session(cwd: Path, tool_mode: str, console: Console, interactive: bool
|
|
|
60
60
|
servers, problems = mcp_config.load_all(cwd, include_project=use_project)
|
|
61
61
|
for problem in problems:
|
|
62
62
|
console.print(f"[yellow]MCP: {escape(problem)}[/]")
|
|
63
|
+
from lcode import modes
|
|
64
|
+
|
|
65
|
+
servers, left_out = modes.code_mode_servers(servers) # images and videos have their own modes
|
|
66
|
+
note = modes.note_once(left_out)
|
|
67
|
+
if note and interactive:
|
|
68
|
+
console.print(f"[dim]{escape(note)}[/]")
|
|
63
69
|
if not servers:
|
|
64
70
|
return None
|
|
65
71
|
manager = McpManager(cwd, servers, tool_mode)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""What lcode makes: code, images or videos.
|
|
2
|
+
|
|
3
|
+
- **code** is the coding agent: a language model with tools, and nothing else. It doesn't generate images or
|
|
4
|
+
videos: the MCP servers that do (`comfyui`, `xai`) aren't loaded in code mode.
|
|
5
|
+
- **image** and **video** generate media on the GPU with image and video models (through ComfyUI, #149). They
|
|
6
|
+
don't need the big coding model: lcode unloads it when you switch to them, and frees ComfyUI's memory when you
|
|
7
|
+
switch back, so one model has the GPU at a time.
|
|
8
|
+
|
|
9
|
+
Until the generation engine lands, image and video mode say what's coming instead of sending the prompt anywhere.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
|
|
19
|
+
from lcode import config
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from lcode.agent import Agent
|
|
23
|
+
from lcode.mcp.config import ServerConfig
|
|
24
|
+
|
|
25
|
+
MODES = ("code", "image", "video")
|
|
26
|
+
TITLES = {"code": "local coding agent", "image": "local image generation", "video": "local video generation"}
|
|
27
|
+
MEDIA_SERVERS = frozenset({"comfyui", "xai"}) # the MCP catalog's image and video servers
|
|
28
|
+
MEDIA_COMMANDS = ("comfy-mcp", "lcode-mcp-xai") # the same servers, under any name
|
|
29
|
+
NOTED_FILE = "media-servers-noted"
|
|
30
|
+
COMING = {
|
|
31
|
+
"image": (
|
|
32
|
+
"Image mode doesn't generate yet: lcode's own ComfyUI engine (#149), the image models (#150) and "
|
|
33
|
+
"generation (#151) come next. /mode code switches back."
|
|
34
|
+
),
|
|
35
|
+
"video": (
|
|
36
|
+
"Video mode doesn't generate yet: lcode's own ComfyUI engine (#149), the video models (#150) and "
|
|
37
|
+
"generation (#153) come next. /mode code switches back."
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_media_server(server: ServerConfig) -> bool:
|
|
43
|
+
"""An MCP server that generates images or videos: those belong to image and video mode, not code mode."""
|
|
44
|
+
if server.name.lower() in MEDIA_SERVERS:
|
|
45
|
+
return True
|
|
46
|
+
target = " ".join(str(part) for part in [*server.command, server.url])
|
|
47
|
+
return any(name in target for name in MEDIA_COMMANDS)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def code_mode_servers(servers: list[ServerConfig]) -> tuple[list[ServerConfig], list[str]]:
|
|
51
|
+
"""The servers code mode loads, and the names of the media servers it leaves out."""
|
|
52
|
+
kept = [s for s in servers if not is_media_server(s)]
|
|
53
|
+
return kept, [s.name for s in servers if is_media_server(s)]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def note_once(left_out: list[str]) -> str:
|
|
57
|
+
"""The note about media servers left out of code mode, the first time only."""
|
|
58
|
+
if not left_out:
|
|
59
|
+
return ""
|
|
60
|
+
marker = config.STATE_DIR / NOTED_FILE
|
|
61
|
+
if marker.exists():
|
|
62
|
+
return ""
|
|
63
|
+
try:
|
|
64
|
+
marker.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
marker.write_text(json.dumps(sorted(left_out)) + "\n")
|
|
66
|
+
except OSError:
|
|
67
|
+
pass
|
|
68
|
+
names = ", ".join(left_out)
|
|
69
|
+
return (
|
|
70
|
+
f"Code mode makes code only, so the MCP servers that make images and videos ({names}) aren't loaded "
|
|
71
|
+
"here any more. Image and video generation move to their own modes: /mode image, /mode video."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def free_comfyui(url: str) -> bool:
|
|
76
|
+
"""Ask ComfyUI at `url` to give the GPU back (unload its models, free its memory). Whether it answered."""
|
|
77
|
+
try:
|
|
78
|
+
r = requests.post(url.rstrip("/") + "/free", json={"unload_models": True, "free_memory": True}, timeout=3)
|
|
79
|
+
except requests.RequestException:
|
|
80
|
+
return False
|
|
81
|
+
return r.status_code == 200
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def switch(agent: Agent, new: str) -> list[str]:
|
|
85
|
+
"""Switch the agent to another mode, handing the GPU over. The lines to show."""
|
|
86
|
+
old = agent.settings.mode
|
|
87
|
+
if new == old:
|
|
88
|
+
return [f"Already in {new} mode."]
|
|
89
|
+
lines = []
|
|
90
|
+
if old == "code" and getattr(agent.ollama, "kind", "ollama") == "ollama":
|
|
91
|
+
try:
|
|
92
|
+
agent.ollama.unload(agent.settings.model) # the image or video model needs the GPU
|
|
93
|
+
lines.append(f"Unloaded {agent.settings.model} to free the GPU for {new}s.")
|
|
94
|
+
except Exception: # not loaded, or Ollama gone: nothing to free
|
|
95
|
+
pass
|
|
96
|
+
if new == "code" and free_comfyui(agent.settings.comfyui_url):
|
|
97
|
+
lines.append("ComfyUI gave the GPU back.")
|
|
98
|
+
agent.settings.mode = new
|
|
99
|
+
lines.insert(0, f"Mode: {new} ({TITLES[new]}).")
|
|
100
|
+
if new != "code":
|
|
101
|
+
lines.append(COMING[new])
|
|
102
|
+
return lines
|