lemming-cli 0.4.1__tar.gz → 0.4.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. lemming_cli-0.4.3/.dockerignore +30 -0
  2. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.github/workflows/ci.yml +1 -1
  3. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/Dockerfile +10 -3
  4. lemming_cli-0.4.1/README.md → lemming_cli-0.4.3/PKG-INFO +51 -16
  5. lemming_cli-0.4.1/PKG-INFO → lemming_cli-0.4.3/README.md +29 -38
  6. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/SKILL.md +24 -7
  7. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/pyproject.toml +2 -2
  8. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/config_test.py +1 -1
  9. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/config.py +1 -1
  10. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/exec_cli.py +6 -0
  11. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/exec_cli_test.py +18 -0
  12. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/cli.py +4 -0
  13. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/container.py +36 -1
  14. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/container_test.py +31 -0
  15. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/models.py +1 -1
  16. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/runner.py +79 -11
  17. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/runner_test.py +107 -4
  18. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/dashboard.spec.js +1 -1
  19. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/favicon.spec.js +1 -1
  20. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/screenshots.spec.js +2 -2
  21. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/uv.lock +1 -1
  22. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.github/workflows/publish.yml +0 -0
  23. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.github/workflows/screenshots.yml +0 -0
  24. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.gitignore +0 -0
  25. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.prettierignore +0 -0
  26. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.prettierrc +0 -0
  27. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/LICENSE +0 -0
  28. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/biome.json +0 -0
  29. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docker-compose.yml +0 -0
  30. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/EVALS.md +0 -0
  31. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/HOOKS.md +0 -0
  32. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/dashboard-desktop.png +0 -0
  33. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/dashboard-mobile.png +0 -0
  34. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/task-log-desktop.png +0 -0
  35. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/task-log-mobile.png +0 -0
  36. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/package-lock.json +0 -0
  37. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/package.json +0 -0
  38. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/playwright.config.js +0 -0
  39. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/__init__.py +0 -0
  40. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/__init__.py +0 -0
  41. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/auth.py +0 -0
  42. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/auth_test.py +0 -0
  43. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/config.py +0 -0
  44. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/conftest.py +0 -0
  45. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/context.py +0 -0
  46. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/context_test.py +0 -0
  47. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/directories.py +0 -0
  48. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/directories_test.py +0 -0
  49. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/files.py +0 -0
  50. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/files_test.py +0 -0
  51. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/hooks.py +0 -0
  52. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/hooks_test.py +0 -0
  53. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/logging.py +0 -0
  54. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/logging_test.py +0 -0
  55. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/loop.py +0 -0
  56. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/loop_test.py +0 -0
  57. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/main.py +0 -0
  58. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/main_test.py +0 -0
  59. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/tasks.py +0 -0
  60. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/tasks_test.py +0 -0
  61. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api_test.py +0 -0
  62. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/brief_test.py +0 -0
  63. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/__init__.py +0 -0
  64. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/brief.py +0 -0
  65. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/config_test.py +0 -0
  66. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/goal.py +0 -0
  67. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/goal_test.py +0 -0
  68. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/hooks.py +0 -0
  69. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/hooks_test.py +0 -0
  70. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/machine_output_test.py +0 -0
  71. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/main.py +0 -0
  72. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/main_test.py +0 -0
  73. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/operations.py +0 -0
  74. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/operations_test.py +0 -0
  75. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/progress.py +0 -0
  76. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/progress_test.py +0 -0
  77. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/readability_cli.py +0 -0
  78. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/readability_cli_test.py +0 -0
  79. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/skill.py +0 -0
  80. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/skill_test.py +0 -0
  81. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/stop_test.py +0 -0
  82. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/tasks.py +0 -0
  83. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/tasks_test.py +0 -0
  84. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli_test.py +0 -0
  85. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/conftest.py +0 -0
  86. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/__init__.py +0 -0
  87. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/__main__.py +0 -0
  88. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/cli_test.py +0 -0
  89. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/fixtures.py +0 -0
  90. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/fixtures_test.py +0 -0
  91. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/harness.py +0 -0
  92. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/harness_test.py +0 -0
  93. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/readability.py +0 -0
  94. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/readability_test.py +0 -0
  95. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/roadmap.py +0 -0
  96. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/roadmap_test.py +0 -0
  97. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/scenarios.py +0 -0
  98. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/scenarios_test.py +0 -0
  99. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/suites.py +0 -0
  100. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/trial.py +0 -0
  101. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/trial_test.py +0 -0
  102. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/hooks.py +0 -0
  103. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/hooks_test.py +0 -0
  104. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/integration_test.py +0 -0
  105. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/main.py +0 -0
  106. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/main_test.py +0 -0
  107. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/models_test.py +0 -0
  108. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/orchestrator.py +0 -0
  109. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/orchestrator_test.py +0 -0
  110. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/paths.py +0 -0
  111. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/paths_test.py +0 -0
  112. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/persistence.py +0 -0
  113. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/persistence_test.py +0 -0
  114. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/50-readability.md +0 -0
  115. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/60-testing.md +0 -0
  116. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/70-ux.md +0 -0
  117. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/90-roadmap.md +0 -0
  118. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/taskrunner.md +0 -0
  119. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts.py +0 -0
  120. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts_test.py +0 -0
  121. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/providers.py +0 -0
  122. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/providers_test.py +0 -0
  123. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/scope.py +0 -0
  124. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/scope_test.py +0 -0
  125. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/shutdown.py +0 -0
  126. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/shutdown_integration_test.py +0 -0
  127. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/shutdown_test.py +0 -0
  128. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/__init__.py +0 -0
  129. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/lifecycle.py +0 -0
  130. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/lifecycle_test.py +0 -0
  131. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/limits.py +0 -0
  132. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/operations.py +0 -0
  133. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/operations_test.py +0 -0
  134. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/progress.py +0 -0
  135. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/progress_test.py +0 -0
  136. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/queries.py +0 -0
  137. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/queries_test.py +0 -0
  138. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/dashboard.test.js +0 -0
  139. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/favicon.js +0 -0
  140. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/files.html +0 -0
  141. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/files.spec.js +0 -0
  142. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/index.html +0 -0
  143. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/index.js +0 -0
  144. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/logs.html +0 -0
  145. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/logs.test.js +0 -0
  146. {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/mancha.js +0 -0
@@ -0,0 +1,30 @@
1
+ # The image builds its own environments, so host-built ones are not just
2
+ # wasted bytes in the context — they are the wrong platform's binaries
3
+ # landing on top of what `uv sync` and `npm install` are about to create.
4
+ .venv
5
+ node_modules
6
+
7
+ # History and build products the image never reads.
8
+ .git
9
+ .github
10
+ dist
11
+ test-results
12
+ docs/screenshots
13
+
14
+ # Agent worktrees are whole checkouts of this repo, each with its own
15
+ # dependencies installed, and they dwarf the source they sit beside.
16
+ .claude
17
+
18
+ # Local state and caches.
19
+ .pytest_cache
20
+ .ruff_cache
21
+ __pycache__
22
+ *.pyc
23
+ .DS_Store
24
+
25
+ # Roadmaps and their locks belong to whoever is running lemming, not to
26
+ # the image; a stale one baked in would be read as that project's state.
27
+ tasks.yml
28
+ *.lock
29
+ !uv.lock
30
+ !package-lock.json
@@ -19,7 +19,7 @@ jobs:
19
19
  python-version: '3.14'
20
20
 
21
21
  - name: Install Python dependencies
22
- run: uv sync
22
+ run: uv sync --locked
23
23
 
24
24
  - name: Run Python linting
25
25
  run: uv run ruff check .
@@ -11,7 +11,8 @@ RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
11
11
  && rm -rf /var/lib/apt/lists/*
12
12
 
13
13
  # Install AI coding CLIs (antigravity ships a native binary, not an npm package)
14
- RUN npm install -g @openai/codex @anthropic-ai/claude-code \
14
+ RUN npm install -g --allow-scripts=opencode-ai \
15
+ @openai/codex @anthropic-ai/claude-code opencode-ai \
15
16
  && curl -fsSL https://antigravity.google/cli/install.sh | bash -s -- --dir /usr/local/bin
16
17
 
17
18
  # Copy cloudflared and uv binaries directly from their official images
@@ -22,8 +23,14 @@ WORKDIR /opt/lemming
22
23
  COPY . .
23
24
  RUN uv sync --no-dev --frozen
24
25
 
25
- # Install frontend dependencies and Playwright browsers
26
- RUN npm install && npx playwright install --with-deps
26
+ # Frontend dependencies are always needed: postinstall vendors mancha into
27
+ # the web assets. The browsers are not, and they are more than half the
28
+ # image, so anything that will never drive one builds without them.
29
+ ARG INSTALL_BROWSERS=true
30
+ RUN npm install \
31
+ && if [ "$INSTALL_BROWSERS" = "true" ]; then \
32
+ npx playwright install --with-deps; \
33
+ fi
27
34
 
28
35
  # Pre-create the lemming home directory so users can bind-mount a .env file into it
29
36
  RUN mkdir -p /root/.local/lemming
@@ -1,3 +1,25 @@
1
+ Metadata-Version: 2.5
2
+ Name: lemming-cli
3
+ Version: 0.4.3
4
+ Summary: An autonomous, iterative task runner for AI coding agents.
5
+ Project-URL: Homepage, https://github.com/owahltinez/lemming
6
+ Project-URL: Repository, https://github.com/owahltinez/lemming
7
+ Author: Lemming Contributors
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,ai,antigravity,automation,claude,opencode
11
+ Requires-Python: >=3.12
12
+ Requires-Dist: beautifulsoup4>=4.13.3
13
+ Requires-Dist: click>=8.0.0
14
+ Requires-Dist: fastapi>=0.124.4
15
+ Requires-Dist: markdownify>=0.14.1
16
+ Requires-Dist: pydantic>=2.12.5
17
+ Requires-Dist: pyyaml>=6.0.3
18
+ Requires-Dist: readability-cli>=0.7.0
19
+ Requires-Dist: requests>=2.32.3
20
+ Requires-Dist: uvicorn>=0.33.0
21
+ Description-Content-Type: text/markdown
22
+
1
23
  # Lemming 🐹
2
24
 
3
25
  **The transparent, tool-agnostic orchestrator for autonomous AI coding agents.**
@@ -16,8 +38,8 @@ human-readable `tasks.yml` file.
16
38
  update is recorded in a human-readable `tasks.yml` file. You can step in,
17
39
  adjust the roadmap, or swap agents at any time.
18
40
  - **Tool Agnostic**: Lemming doesn't care which agent you use. It works
19
- out-of-the-box with `agy`, `aider`, `claude`, `codex`, or even your own custom
20
- scripts.
41
+ out-of-the-box with `agy`, `opencode`, `claude`, `codex`, or even your own
42
+ custom scripts.
21
43
  - **Resilient Execution**: With built-in heartbeat monitoring, automatic
22
44
  retries, and progress tracking, Lemming handles process crashes and rate
23
45
  limits gracefully.
@@ -71,7 +93,7 @@ Start the autonomous loop.
71
93
 
72
94
  ```bash
73
95
  # Run using the project's configured agent (defaults to the first
74
- # supported runner found on PATH: agy, aider, claude, or codex)
96
+ # supported runner found on PATH: agy, opencode, claude, or codex)
75
97
  lemming run
76
98
 
77
99
  # Flags passed after -- are sent directly to the underlying runner
@@ -118,22 +140,25 @@ removed unless it failed — in which case it is kept, and its path printed, so
118
140
  the log can be read. Kept directories live in `~/.local/lemming/exec-*` and
119
141
  are retired automatically a week later, so a recent failure is always still
120
142
  there to inspect. Stdout carries the agent's message alone and everything
121
- else goes to stderr, so the output can be consumed directly.
143
+ else goes to stderr, so the output can be consumed directly. Stderr reports
144
+ the ephemeral tasks file and task ID at startup; pass those to `lemming
145
+ --tasks-file <path> status <id>` or `logs <id>` to inspect an active run. Use
146
+ the global verbose flag (`lemming -v exec ...`) to stream runner activity.
122
147
 
123
148
  Note that the agent runs unattended (`--yolo` by default), so it does not
124
149
  inherit the permission prompts of whatever launched it.
125
150
 
126
151
  ### Teaching Your Agent to Use It
127
152
 
128
- Lemming ships an Agent Skill so that an agent already running — Claude Code,
129
- Gemini CLI, Cursor — can reach any other agent CLI without knowing how each
130
- one spells its flags. Install it once:
153
+ Lemming ships an Agent Skill so that an agent already running — Codex, Claude
154
+ Code, Gemini CLI, Cursor — can reach any other agent CLI without knowing how
155
+ each one spells its flags. Install it once:
131
156
 
132
157
  ```bash
133
158
  # Cross-tool location (~/.agents/skills), and report any tool-specific ones
134
159
  lemming skill install
135
160
 
136
- # Cover every agent tool detected on this machine
161
+ # Cover detected tools that require their own skill directory too
137
162
  lemming skill install --all
138
163
 
139
164
  # This repository only
@@ -268,7 +293,7 @@ lemming hooks list
268
293
  lemming config list
269
294
 
270
295
  # Persist configuration to tasks.yml
271
- lemming config set runner aider
296
+ lemming config set runner opencode
272
297
  ```
273
298
 
274
299
  ### Evaluating Prompt Changes
@@ -373,8 +398,9 @@ These come before the subcommand and apply to all of them.
373
398
  per-task `--runner`/`--model` overrides anything passed here.
374
399
  - **`exec [<description>]`**: Run one task, or one set of reviews, outside any
375
400
  roadmap. Prints the agent's closing message to stdout and everything else to
376
- stderr. Exits non-zero if the task did not complete. See
377
- [One-off tasks](#one-off-tasks-without-a-roadmap).
401
+ stderr, including its tasks file and task ID at startup. Use `lemming -v
402
+ exec ...` to stream runner activity. Exits non-zero if the task did not
403
+ complete. See [One-off tasks](#one-off-tasks-without-a-roadmap).
378
404
  - `-f/--file`: Read the description from a file, or `-` for stdin. Unlike
379
405
  `add`, there is no length cap.
380
406
  - `--review <names>`: Reviews to run after the task, comma-separated or
@@ -449,7 +475,10 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
449
475
 
450
476
  - **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
451
477
  the project workspace with `--add-dir`
452
- - **Aider**: Adds `--yes --quiet`
478
+ - **OpenCode**: Runs non-interactively via `opencode run`, adds `--format json`,
479
+ and, in YOLO mode, adds `--auto`. For its Google provider, Lemming makes an
480
+ existing `GOOGLE_API_KEY` or `GEMINI_API_KEY` available under OpenCode's
481
+ native `GOOGLE_GENERATIVE_AI_API_KEY` name.
453
482
  - **Claude**: Adds `--dangerously-skip-permissions`
454
483
  - **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
455
484
  YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
@@ -511,10 +540,16 @@ pushing a `v*` tag triggers the `publish.yml` GitHub Actions workflow, which
511
540
  builds the package with `uv build` and uploads it.
512
541
 
513
542
  ```bash
514
- # 1. Bump the version in pyproject.toml, commit, and push
515
- # 2. Tag the release and push the tag
516
- git tag v0.1.1
517
- git push origin v0.1.1
543
+ # 1. Bump pyproject.toml and uv.lock together, commit, and push
544
+ uv version --bump patch --no-sync
545
+ version="$(uv version --short)"
546
+ git add pyproject.toml uv.lock
547
+ git commit -m "Bump version to $version"
548
+ git push origin main
549
+
550
+ # 2. After CI passes, tag the release and push the tag
551
+ git tag "v$version"
552
+ git push origin "v$version"
518
553
  ```
519
554
 
520
555
  ---
@@ -1,25 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: lemming-cli
3
- Version: 0.4.1
4
- Summary: An autonomous, iterative task runner for AI coding agents.
5
- Project-URL: Homepage, https://github.com/owahltinez/lemming
6
- Project-URL: Repository, https://github.com/owahltinez/lemming
7
- Author: Lemming Contributors
8
- License: MIT
9
- License-File: LICENSE
10
- Keywords: agents,ai,aider,antigravity,automation,claude
11
- Requires-Python: >=3.12
12
- Requires-Dist: beautifulsoup4>=4.13.3
13
- Requires-Dist: click>=8.0.0
14
- Requires-Dist: fastapi>=0.124.4
15
- Requires-Dist: markdownify>=0.14.1
16
- Requires-Dist: pydantic>=2.12.5
17
- Requires-Dist: pyyaml>=6.0.3
18
- Requires-Dist: readability-cli>=0.7.0
19
- Requires-Dist: requests>=2.32.3
20
- Requires-Dist: uvicorn>=0.33.0
21
- Description-Content-Type: text/markdown
22
-
23
1
  # Lemming 🐹
24
2
 
25
3
  **The transparent, tool-agnostic orchestrator for autonomous AI coding agents.**
@@ -38,8 +16,8 @@ human-readable `tasks.yml` file.
38
16
  update is recorded in a human-readable `tasks.yml` file. You can step in,
39
17
  adjust the roadmap, or swap agents at any time.
40
18
  - **Tool Agnostic**: Lemming doesn't care which agent you use. It works
41
- out-of-the-box with `agy`, `aider`, `claude`, `codex`, or even your own custom
42
- scripts.
19
+ out-of-the-box with `agy`, `opencode`, `claude`, `codex`, or even your own
20
+ custom scripts.
43
21
  - **Resilient Execution**: With built-in heartbeat monitoring, automatic
44
22
  retries, and progress tracking, Lemming handles process crashes and rate
45
23
  limits gracefully.
@@ -93,7 +71,7 @@ Start the autonomous loop.
93
71
 
94
72
  ```bash
95
73
  # Run using the project's configured agent (defaults to the first
96
- # supported runner found on PATH: agy, aider, claude, or codex)
74
+ # supported runner found on PATH: agy, opencode, claude, or codex)
97
75
  lemming run
98
76
 
99
77
  # Flags passed after -- are sent directly to the underlying runner
@@ -140,22 +118,25 @@ removed unless it failed — in which case it is kept, and its path printed, so
140
118
  the log can be read. Kept directories live in `~/.local/lemming/exec-*` and
141
119
  are retired automatically a week later, so a recent failure is always still
142
120
  there to inspect. Stdout carries the agent's message alone and everything
143
- else goes to stderr, so the output can be consumed directly.
121
+ else goes to stderr, so the output can be consumed directly. Stderr reports
122
+ the ephemeral tasks file and task ID at startup; pass those to `lemming
123
+ --tasks-file <path> status <id>` or `logs <id>` to inspect an active run. Use
124
+ the global verbose flag (`lemming -v exec ...`) to stream runner activity.
144
125
 
145
126
  Note that the agent runs unattended (`--yolo` by default), so it does not
146
127
  inherit the permission prompts of whatever launched it.
147
128
 
148
129
  ### Teaching Your Agent to Use It
149
130
 
150
- Lemming ships an Agent Skill so that an agent already running — Claude Code,
151
- Gemini CLI, Cursor — can reach any other agent CLI without knowing how each
152
- one spells its flags. Install it once:
131
+ Lemming ships an Agent Skill so that an agent already running — Codex, Claude
132
+ Code, Gemini CLI, Cursor — can reach any other agent CLI without knowing how
133
+ each one spells its flags. Install it once:
153
134
 
154
135
  ```bash
155
136
  # Cross-tool location (~/.agents/skills), and report any tool-specific ones
156
137
  lemming skill install
157
138
 
158
- # Cover every agent tool detected on this machine
139
+ # Cover detected tools that require their own skill directory too
159
140
  lemming skill install --all
160
141
 
161
142
  # This repository only
@@ -290,7 +271,7 @@ lemming hooks list
290
271
  lemming config list
291
272
 
292
273
  # Persist configuration to tasks.yml
293
- lemming config set runner aider
274
+ lemming config set runner opencode
294
275
  ```
295
276
 
296
277
  ### Evaluating Prompt Changes
@@ -395,8 +376,9 @@ These come before the subcommand and apply to all of them.
395
376
  per-task `--runner`/`--model` overrides anything passed here.
396
377
  - **`exec [<description>]`**: Run one task, or one set of reviews, outside any
397
378
  roadmap. Prints the agent's closing message to stdout and everything else to
398
- stderr. Exits non-zero if the task did not complete. See
399
- [One-off tasks](#one-off-tasks-without-a-roadmap).
379
+ stderr, including its tasks file and task ID at startup. Use `lemming -v
380
+ exec ...` to stream runner activity. Exits non-zero if the task did not
381
+ complete. See [One-off tasks](#one-off-tasks-without-a-roadmap).
400
382
  - `-f/--file`: Read the description from a file, or `-` for stdin. Unlike
401
383
  `add`, there is no length cap.
402
384
  - `--review <names>`: Reviews to run after the task, comma-separated or
@@ -471,7 +453,10 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
471
453
 
472
454
  - **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
473
455
  the project workspace with `--add-dir`
474
- - **Aider**: Adds `--yes --quiet`
456
+ - **OpenCode**: Runs non-interactively via `opencode run`, adds `--format json`,
457
+ and, in YOLO mode, adds `--auto`. For its Google provider, Lemming makes an
458
+ existing `GOOGLE_API_KEY` or `GEMINI_API_KEY` available under OpenCode's
459
+ native `GOOGLE_GENERATIVE_AI_API_KEY` name.
475
460
  - **Claude**: Adds `--dangerously-skip-permissions`
476
461
  - **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
477
462
  YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
@@ -533,10 +518,16 @@ pushing a `v*` tag triggers the `publish.yml` GitHub Actions workflow, which
533
518
  builds the package with `uv build` and uploads it.
534
519
 
535
520
  ```bash
536
- # 1. Bump the version in pyproject.toml, commit, and push
537
- # 2. Tag the release and push the tag
538
- git tag v0.1.1
539
- git push origin v0.1.1
521
+ # 1. Bump pyproject.toml and uv.lock together, commit, and push
522
+ uv version --bump patch --no-sync
523
+ version="$(uv version --short)"
524
+ git add pyproject.toml uv.lock
525
+ git commit -m "Bump version to $version"
526
+ git push origin main
527
+
528
+ # 2. After CI passes, tag the release and push the tag
529
+ git tag "v$version"
530
+ git push origin "v$version"
540
531
  ```
541
532
 
542
533
  ---
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: lemming
3
- description: One interface over the AI coding agent CLIs (agy, claude, codex). Delegate a single task to another agent to spare a quota or play to its strengths; run a readability, testing, or UX review over changed files, a branch, or a path; or drive a multi-task project through a roadmap that survives context limits. Use whenever work should run under a different agent than the current one, when reviewing a diff before opening a pull request, or when a job is too large for one agent run.
3
+ description: One interface over the AI coding agent CLIs (agy, claude, codex, opencode). Delegate a single task to another agent to spare a quota or play to its strengths; run a readability, testing, or UX review over changed files, a branch, or a path; or drive a multi-task project through a roadmap that survives context limits. Use whenever work should run under a different agent than the current one, when reviewing a diff before opening a pull request, or when a job is too large for one agent run.
4
4
  license: MIT
5
5
  ---
6
6
 
7
7
  # lemming
8
8
 
9
9
  One interface over several agent CLIs. You do not need to know how `agy`,
10
- `claude`, or `codex` spell their flags — only `lemming`.
10
+ `claude`, `codex`, or `opencode` spell their flags — only `lemming`.
11
11
 
12
12
  Use `exec` for one unit of work, and a roadmap for anything larger than a
13
13
  single agent run can hold.
@@ -15,16 +15,32 @@ single agent run can hold.
15
15
  ## Delegate one task
16
16
 
17
17
  ```sh
18
- lemming exec "Fix the flaky heartbeat test in src/runner_test.py" --runner codex
18
+ lemming -v exec "Fix the flaky heartbeat test in src/runner_test.py" --runner codex
19
19
 
20
20
  # Pipe a longer handoff rather than fighting shell quoting; no length limit
21
- cat handoff.md | lemming exec -f - --runner agy
21
+ cat handoff.md | lemming -v exec -f - --runner agy
22
22
  ```
23
23
 
24
24
  The agent's closing message comes back on **stdout**, everything else on
25
25
  stderr, and the exit code is 0 only if the task completed. Read that message
26
26
  instead of hunting through logs.
27
27
 
28
+ When you launch `exec` as a subprocess, use the global `-v` flag (before
29
+ `exec`) and continue monitoring its output until it exits. Verbose mode streams
30
+ the runner's activity on stderr. At startup, stderr also reports the exact
31
+ `Tasks file` and `Task ID`; use them from another process to inspect the
32
+ agent's durable findings or full log without guessing which `exec-*` directory
33
+ belongs to the run:
34
+
35
+ ```sh
36
+ lemming --tasks-file <reported-path> status <reported-task-id>
37
+ lemming --tasks-file <reported-path> logs <reported-task-id>
38
+ ```
39
+
40
+ The task runner is required to record its approach first and concise findings
41
+ as it works. These entries are checkpoints rather than a continuous trace, so
42
+ use verbose output or the log to tell whether it is actively making progress.
43
+
28
44
  The task starts with an empty context and sees nothing of your conversation,
29
45
  so give it everything it needs.
30
46
 
@@ -94,9 +110,10 @@ turns one off for the project.
94
110
  atomicity to rely on.
95
111
  - **Concurrent runs in one checkout interleave their edits.** Give each
96
112
  writing run its own worktree and address it with `-C`.
97
- - **Failures keep their state.** The directory is printed on stderr and holds
98
- the run's log directly, so `cat <that dir>/*-runner.log` is the whole
99
- recovery. It is retired automatically after a week.
113
+ - **Every run reports its state.** The tasks file and task ID are printed on
114
+ stderr at startup. Successful state is removed on exit unless `--keep` is
115
+ used; failures keep their directory and print it again for recovery. Kept
116
+ failure state is retired automatically after a week.
100
117
  - **`--tasks-file` and `-C` are group options.** They go before the
101
118
  subcommand — `lemming -C ../other exec ...`, never `exec -C ../other`.
102
119
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lemming-cli"
7
- version = "0.4.1"
7
+ version = "0.4.3"
8
8
  description = "An autonomous, iterative task runner for AI coding agents."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -12,7 +12,7 @@ authors = [
12
12
  ]
13
13
  license = { text = "MIT" }
14
14
  requires-python = ">=3.12"
15
- keywords = ["ai", "agents", "automation", "antigravity", "claude", "aider"]
15
+ keywords = ["ai", "agents", "automation", "antigravity", "claude", "opencode"]
16
16
  dependencies = [
17
17
  "beautifulsoup4>=4.13.3",
18
18
  "click>=8.0.0",
@@ -20,7 +20,7 @@ def test_run_loop(client, test_tasks):
20
20
  def test_get_runners(client):
21
21
  response = client.get("/api/runners")
22
22
  assert response.status_code == 200
23
- assert response.json() == ["agy", "aider", "claude", "codex"]
23
+ assert response.json() == ["agy", "opencode", "claude", "codex"]
24
24
 
25
25
 
26
26
  def test_project_goal_isolation(client, test_tasks):
@@ -50,7 +50,7 @@ def config_set(ctx: click.Context, key: str, value: str):
50
50
  discard the pinned model.
51
51
 
52
52
  Examples:
53
- lemming config set runner aider
53
+ lemming config set runner opencode
54
54
  lemming config set model gemini-3.6-flash-high
55
55
  lemming config set model default
56
56
  lemming config set retries 5
@@ -324,6 +324,12 @@ def exec_command(
324
324
  else _create_review_task(exec_dir, reviews, config)
325
325
  )
326
326
 
327
+ # A supervising process needs an exact, race-free handle to the ephemeral
328
+ # roadmap while the run is active. Keep it on stderr so stdout remains the
329
+ # agent's closing-message interface.
330
+ click.echo(f"Tasks file: {tasks_file}", err=True)
331
+ click.echo(f"Task ID: {task_id}", err=True)
332
+
327
333
  shutdown.clear_drain()
328
334
  shutdown.install_handlers()
329
335
 
@@ -80,6 +80,24 @@ def test_exec_keeps_progress_chatter_off_stdout(workspace):
80
80
  assert "Attempt" in result.stderr
81
81
 
82
82
 
83
+ def test_exec_announces_monitoring_coordinates_on_stderr(workspace):
84
+ """A supervisor can inspect an active exec without guessing its state."""
85
+ seen = {}
86
+
87
+ def finish(cmd, tasks_file, task_id, *args, **kwargs):
88
+ seen["tasks_file"] = tasks_file
89
+ seen["task_id"] = task_id
90
+ return _finish("Done.")(cmd, tasks_file, task_id, *args, **kwargs)
91
+
92
+ with mock.patch("lemming.runner.run_with_heartbeat", finish):
93
+ result = CliRunner().invoke(cli, ["exec", "Fix the flaky test"])
94
+
95
+ assert result.exit_code == 0, result.stderr
96
+ assert result.stdout.strip() == "Done."
97
+ assert f"Tasks file: {seen['tasks_file']}" in result.stderr
98
+ assert f"Task ID: {seen['task_id']}" in result.stderr
99
+
100
+
83
101
  def test_exec_removes_its_state_directory_on_success(workspace):
84
102
  """A one-shot leaves nothing behind when it worked."""
85
103
  with mock.patch("lemming.runner.run_with_heartbeat", _finish("Done.")):
@@ -178,6 +178,10 @@ def run(
178
178
  )
179
179
  click.echo(f"Building eval image '{image}'...")
180
180
  container.build_image(context, image=image, docker=docker)
181
+ # The image is what the trials need; the cache that produced it is
182
+ # dead weight, and it outgrows the image several times over across
183
+ # repeated runs.
184
+ container.prune_build_cache(docker=docker)
181
185
 
182
186
  if run_dir is None:
183
187
  run_dir = pathlib.Path(tempfile.mkdtemp(prefix="lemming-evals-"))
@@ -19,6 +19,7 @@ DEFAULT_FORWARD_ENV = (
19
19
  "CLAUDE_CODE_OAUTH_TOKEN",
20
20
  "GEMINI_API_KEY",
21
21
  "GOOGLE_API_KEY",
22
+ "GOOGLE_GENERATIVE_AI_API_KEY",
22
23
  )
23
24
 
24
25
  # Paths inside the container; the harness mounts host dirs onto these.
@@ -41,7 +42,41 @@ def build_image(
41
42
  image: Tag for the built image.
42
43
  docker: Docker-compatible CLI binary to invoke.
43
44
  """
44
- subprocess.run([docker, "build", "--tag", image, str(context)], check=True)
45
+ # No scenario drives a browser, and they are more than half the image.
46
+ subprocess.run(
47
+ [
48
+ docker,
49
+ "build",
50
+ "--build-arg",
51
+ "INSTALL_BROWSERS=false",
52
+ "--tag",
53
+ image,
54
+ str(context),
55
+ ],
56
+ check=True,
57
+ )
58
+
59
+
60
+ def prune_build_cache(docker: str = "docker") -> None:
61
+ """Discards the build cache left behind by an eval image build.
62
+
63
+ Cache accumulates faster than images do — a run rebuilding this image a
64
+ few times leaves tens of gigabytes behind — and nothing reuses it once
65
+ the trials have finished.
66
+
67
+ Args:
68
+ docker: Docker-compatible CLI binary to invoke.
69
+ """
70
+ try:
71
+ subprocess.run(
72
+ [docker, "builder", "prune", "--force", "--all"],
73
+ check=False,
74
+ capture_output=True,
75
+ )
76
+ except OSError:
77
+ # This runs after the results are in; failing to tidy up must never
78
+ # be the reason a completed eval run reports an error.
79
+ pass
45
80
 
46
81
 
47
82
  def trial_command(
@@ -84,3 +84,34 @@ class TestRunTrial(unittest.TestCase):
84
84
 
85
85
  if __name__ == "__main__":
86
86
  unittest.main()
87
+
88
+
89
+ class BuildImageTest(unittest.TestCase):
90
+ """The build must not carry what the eval trials never use."""
91
+
92
+ @unittest.mock.patch("subprocess.run")
93
+ def test_builds_without_browsers(
94
+ self, run: unittest.mock.MagicMock
95
+ ) -> None:
96
+ """Browsers are over half the image and no trial drives one."""
97
+ container.build_image(pathlib.Path("/repo"))
98
+
99
+ argv = run.call_args[0][0]
100
+ self.assertIn("--build-arg", argv)
101
+ self.assertIn("INSTALL_BROWSERS=false", argv)
102
+
103
+ @unittest.mock.patch("subprocess.run")
104
+ def test_prunes_the_build_cache(self, run: unittest.mock.MagicMock) -> None:
105
+ """Repeated eval runs accumulate cache faster than images."""
106
+ container.prune_build_cache()
107
+
108
+ argv = run.call_args[0][0]
109
+ self.assertEqual(argv[:3], ["docker", "builder", "prune"])
110
+ self.assertIn("--force", argv)
111
+
112
+ @unittest.mock.patch("subprocess.run", side_effect=OSError("no docker"))
113
+ def test_pruning_never_fails_a_run(
114
+ self, run: unittest.mock.MagicMock
115
+ ) -> None:
116
+ """Cleanup runs after the results are in; it must not lose them."""
117
+ container.prune_build_cache()
@@ -8,7 +8,7 @@ import time
8
8
  import pydantic
9
9
 
10
10
  # Runner CLIs the orchestrator knows how to drive, in default-preference order.
11
- KNOWN_RUNNERS = ("agy", "aider", "claude", "codex")
11
+ KNOWN_RUNNERS = ("agy", "opencode", "claude", "codex")
12
12
 
13
13
 
14
14
  @functools.cache