lemming-cli 0.4.2__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.
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/Dockerfile +2 -1
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/PKG-INFO +14 -11
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/README.md +12 -9
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/SKILL.md +2 -2
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/pyproject.toml +2 -2
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/config_test.py +1 -1
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/config.py +1 -1
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/container.py +1 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/models.py +1 -1
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/runner.py +65 -8
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/runner_test.py +103 -2
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/dashboard.spec.js +1 -1
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/favicon.spec.js +1 -1
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/screenshots.spec.js +2 -2
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/uv.lock +1 -1
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/.dockerignore +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/.github/workflows/ci.yml +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/.github/workflows/publish.yml +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/.github/workflows/screenshots.yml +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/.gitignore +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/.prettierignore +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/.prettierrc +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/LICENSE +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/biome.json +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/docker-compose.yml +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/docs/EVALS.md +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/docs/HOOKS.md +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/docs/screenshots/dashboard-desktop.png +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/docs/screenshots/dashboard-mobile.png +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/docs/screenshots/task-log-desktop.png +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/docs/screenshots/task-log-mobile.png +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/package-lock.json +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/package.json +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/playwright.config.js +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/__init__.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/__init__.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/auth.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/auth_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/config.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/conftest.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/context.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/context_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/directories.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/directories_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/files.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/files_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/hooks.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/hooks_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/logging.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/logging_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/loop.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/loop_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/main.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/main_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/tasks.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api/tasks_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/api_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/brief_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/__init__.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/brief.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/config_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/exec_cli.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/exec_cli_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/goal.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/goal_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/hooks.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/hooks_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/machine_output_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/main.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/main_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/operations.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/operations_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/progress.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/progress_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/readability_cli.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/readability_cli_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/skill.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/skill_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/stop_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/tasks.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli/tasks_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/cli_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/conftest.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/__init__.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/__main__.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/cli.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/cli_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/container_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/fixtures.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/fixtures_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/harness.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/harness_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/readability.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/readability_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/roadmap.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/roadmap_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/scenarios.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/scenarios_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/suites.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/trial.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/evals/trial_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/hooks.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/hooks_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/integration_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/main.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/main_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/models_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/orchestrator.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/orchestrator_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/paths.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/paths_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/persistence.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/persistence_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/50-readability.md +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/60-testing.md +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/70-ux.md +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/90-roadmap.md +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/prompts/taskrunner.md +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/prompts.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/prompts_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/providers.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/providers_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/scope.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/scope_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/shutdown.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/shutdown_integration_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/shutdown_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/__init__.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/lifecycle.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/lifecycle_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/limits.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/operations.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/operations_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/progress.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/progress_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/queries.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/tasks/queries_test.py +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/dashboard.test.js +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/favicon.js +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/files.html +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/files.spec.js +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/index.html +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/index.js +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/logs.html +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/logs.test.js +0 -0
- {lemming_cli-0.4.2 → lemming_cli-0.4.3}/src/lemming/web/mancha.js +0 -0
|
@@ -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
|
|
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
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lemming-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: An autonomous, iterative task runner for AI coding agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/owahltinez/lemming
|
|
6
6
|
Project-URL: Repository, https://github.com/owahltinez/lemming
|
|
7
7
|
Author: Lemming Contributors
|
|
8
8
|
License: MIT
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Keywords: agents,ai,
|
|
10
|
+
Keywords: agents,ai,antigravity,automation,claude,opencode
|
|
11
11
|
Requires-Python: >=3.12
|
|
12
12
|
Requires-Dist: beautifulsoup4>=4.13.3
|
|
13
13
|
Requires-Dist: click>=8.0.0
|
|
@@ -38,8 +38,8 @@ human-readable `tasks.yml` file.
|
|
|
38
38
|
update is recorded in a human-readable `tasks.yml` file. You can step in,
|
|
39
39
|
adjust the roadmap, or swap agents at any time.
|
|
40
40
|
- **Tool Agnostic**: Lemming doesn't care which agent you use. It works
|
|
41
|
-
out-of-the-box with `agy`, `
|
|
42
|
-
scripts.
|
|
41
|
+
out-of-the-box with `agy`, `opencode`, `claude`, `codex`, or even your own
|
|
42
|
+
custom scripts.
|
|
43
43
|
- **Resilient Execution**: With built-in heartbeat monitoring, automatic
|
|
44
44
|
retries, and progress tracking, Lemming handles process crashes and rate
|
|
45
45
|
limits gracefully.
|
|
@@ -93,7 +93,7 @@ Start the autonomous loop.
|
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
# Run using the project's configured agent (defaults to the first
|
|
96
|
-
# supported runner found on PATH: agy,
|
|
96
|
+
# supported runner found on PATH: agy, opencode, claude, or codex)
|
|
97
97
|
lemming run
|
|
98
98
|
|
|
99
99
|
# Flags passed after -- are sent directly to the underlying runner
|
|
@@ -150,15 +150,15 @@ inherit the permission prompts of whatever launched it.
|
|
|
150
150
|
|
|
151
151
|
### Teaching Your Agent to Use It
|
|
152
152
|
|
|
153
|
-
Lemming ships an Agent Skill so that an agent already running — Claude
|
|
154
|
-
Gemini CLI, Cursor — can reach any other agent CLI without knowing how
|
|
155
|
-
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:
|
|
156
156
|
|
|
157
157
|
```bash
|
|
158
158
|
# Cross-tool location (~/.agents/skills), and report any tool-specific ones
|
|
159
159
|
lemming skill install
|
|
160
160
|
|
|
161
|
-
# Cover
|
|
161
|
+
# Cover detected tools that require their own skill directory too
|
|
162
162
|
lemming skill install --all
|
|
163
163
|
|
|
164
164
|
# This repository only
|
|
@@ -293,7 +293,7 @@ lemming hooks list
|
|
|
293
293
|
lemming config list
|
|
294
294
|
|
|
295
295
|
# Persist configuration to tasks.yml
|
|
296
|
-
lemming config set runner
|
|
296
|
+
lemming config set runner opencode
|
|
297
297
|
```
|
|
298
298
|
|
|
299
299
|
### Evaluating Prompt Changes
|
|
@@ -475,7 +475,10 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
|
|
|
475
475
|
|
|
476
476
|
- **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
|
|
477
477
|
the project workspace with `--add-dir`
|
|
478
|
-
- **
|
|
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.
|
|
479
482
|
- **Claude**: Adds `--dangerously-skip-permissions`
|
|
480
483
|
- **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
|
|
481
484
|
YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
|
|
@@ -16,8 +16,8 @@ human-readable `tasks.yml` file.
|
|
|
16
16
|
update is recorded in a human-readable `tasks.yml` file. You can step in,
|
|
17
17
|
adjust the roadmap, or swap agents at any time.
|
|
18
18
|
- **Tool Agnostic**: Lemming doesn't care which agent you use. It works
|
|
19
|
-
out-of-the-box with `agy`, `
|
|
20
|
-
scripts.
|
|
19
|
+
out-of-the-box with `agy`, `opencode`, `claude`, `codex`, or even your own
|
|
20
|
+
custom scripts.
|
|
21
21
|
- **Resilient Execution**: With built-in heartbeat monitoring, automatic
|
|
22
22
|
retries, and progress tracking, Lemming handles process crashes and rate
|
|
23
23
|
limits gracefully.
|
|
@@ -71,7 +71,7 @@ Start the autonomous loop.
|
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
73
|
# Run using the project's configured agent (defaults to the first
|
|
74
|
-
# supported runner found on PATH: agy,
|
|
74
|
+
# supported runner found on PATH: agy, opencode, claude, or codex)
|
|
75
75
|
lemming run
|
|
76
76
|
|
|
77
77
|
# Flags passed after -- are sent directly to the underlying runner
|
|
@@ -128,15 +128,15 @@ inherit the permission prompts of whatever launched it.
|
|
|
128
128
|
|
|
129
129
|
### Teaching Your Agent to Use It
|
|
130
130
|
|
|
131
|
-
Lemming ships an Agent Skill so that an agent already running — Claude
|
|
132
|
-
Gemini CLI, Cursor — can reach any other agent CLI without knowing how
|
|
133
|
-
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:
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
136
|
# Cross-tool location (~/.agents/skills), and report any tool-specific ones
|
|
137
137
|
lemming skill install
|
|
138
138
|
|
|
139
|
-
# Cover
|
|
139
|
+
# Cover detected tools that require their own skill directory too
|
|
140
140
|
lemming skill install --all
|
|
141
141
|
|
|
142
142
|
# This repository only
|
|
@@ -271,7 +271,7 @@ lemming hooks list
|
|
|
271
271
|
lemming config list
|
|
272
272
|
|
|
273
273
|
# Persist configuration to tasks.yml
|
|
274
|
-
lemming config set runner
|
|
274
|
+
lemming config set runner opencode
|
|
275
275
|
```
|
|
276
276
|
|
|
277
277
|
### Evaluating Prompt Changes
|
|
@@ -453,7 +453,10 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
|
|
|
453
453
|
|
|
454
454
|
- **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
|
|
455
455
|
the project workspace with `--add-dir`
|
|
456
|
-
- **
|
|
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.
|
|
457
460
|
- **Claude**: Adds `--dangerously-skip-permissions`
|
|
458
461
|
- **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
|
|
459
462
|
YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
|
|
@@ -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 `
|
|
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.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lemming-cli"
|
|
7
|
-
version = "0.4.
|
|
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", "
|
|
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", "
|
|
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
|
|
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
|
|
@@ -209,15 +209,21 @@ def build_runner_command(
|
|
|
209
209
|
|
|
210
210
|
runner_base = os.path.basename(parts[0])
|
|
211
211
|
is_codex = runner_base.startswith("codex")
|
|
212
|
+
is_opencode = runner_base.startswith("opencode")
|
|
212
213
|
|
|
213
|
-
# Codex
|
|
214
|
-
# explicitly configured
|
|
215
|
-
# subcommand, while still making a bare
|
|
214
|
+
# Codex and OpenCode expose their non-interactive interfaces as
|
|
215
|
+
# subcommands. Accept explicitly configured forms without duplicating the
|
|
216
|
+
# subcommand, while still making a bare executable suitable for automation.
|
|
216
217
|
if is_codex:
|
|
217
218
|
if extra_parts and extra_parts[0] in {"exec", "e"}:
|
|
218
219
|
cmd.append(extra_parts.pop(0))
|
|
219
220
|
else:
|
|
220
221
|
cmd.append("exec")
|
|
222
|
+
elif is_opencode:
|
|
223
|
+
if extra_parts and extra_parts[0] == "run":
|
|
224
|
+
cmd.append(extra_parts.pop(0))
|
|
225
|
+
else:
|
|
226
|
+
cmd.append("run")
|
|
221
227
|
|
|
222
228
|
if not no_defaults:
|
|
223
229
|
if runner_base.startswith("agy"):
|
|
@@ -252,11 +258,19 @@ def build_runner_command(
|
|
|
252
258
|
|
|
253
259
|
prompt_arg = "--prompt"
|
|
254
260
|
elif runner_base.startswith("aider"):
|
|
261
|
+
# Backward compatibility for persisted/custom Aider runners. Aider
|
|
262
|
+
# is no longer advertised as a first-class runner.
|
|
255
263
|
if yolo:
|
|
256
264
|
cmd.append("--yes")
|
|
257
265
|
if not verbose:
|
|
258
266
|
cmd.append("--quiet")
|
|
259
267
|
prompt_arg = "--message"
|
|
268
|
+
elif is_opencode:
|
|
269
|
+
if yolo:
|
|
270
|
+
cmd.append("--auto")
|
|
271
|
+
# Raw JSON events expose completed text, tool activity, and errors
|
|
272
|
+
# in Lemming's live task log.
|
|
273
|
+
cmd.extend(["--format", "json"])
|
|
260
274
|
elif runner_base.startswith("claude"):
|
|
261
275
|
if yolo:
|
|
262
276
|
cmd.append("--dangerously-skip-permissions")
|
|
@@ -342,8 +356,19 @@ def extract_error_message(
|
|
|
342
356
|
|
|
343
357
|
kind = event.get("type")
|
|
344
358
|
message = None
|
|
345
|
-
if kind == "error"
|
|
346
|
-
|
|
359
|
+
if kind == "error":
|
|
360
|
+
if isinstance(event.get("message"), str):
|
|
361
|
+
message = event["message"]
|
|
362
|
+
else:
|
|
363
|
+
# OpenCode's JSON stream carries the provider error object
|
|
364
|
+
# under ``error``, usually with its text in ``data.message``.
|
|
365
|
+
nested = event.get("error")
|
|
366
|
+
if isinstance(nested, dict):
|
|
367
|
+
data = nested.get("data")
|
|
368
|
+
if isinstance(data, dict):
|
|
369
|
+
message = data.get("message")
|
|
370
|
+
if not isinstance(message, str):
|
|
371
|
+
message = nested.get("message")
|
|
347
372
|
elif kind == "turn.failed":
|
|
348
373
|
nested = event.get("error")
|
|
349
374
|
if isinstance(nested, dict):
|
|
@@ -402,6 +427,16 @@ def _final_message_text(event: dict) -> str | None:
|
|
|
402
427
|
response = result.get("response")
|
|
403
428
|
return response if isinstance(response, str) else None
|
|
404
429
|
|
|
430
|
+
# OpenCode emits completed assistant text as its own event type. Partial
|
|
431
|
+
# text is not emitted in JSON mode, so the last such event is the closing
|
|
432
|
+
# response even when tool and step events follow it.
|
|
433
|
+
if event.get("type") == "text":
|
|
434
|
+
part = event.get("part")
|
|
435
|
+
if not isinstance(part, dict) or part.get("type") != "text":
|
|
436
|
+
return None
|
|
437
|
+
text = part.get("text")
|
|
438
|
+
return text if isinstance(text, str) else None
|
|
439
|
+
|
|
405
440
|
return None
|
|
406
441
|
|
|
407
442
|
|
|
@@ -493,6 +528,30 @@ def _kill_process_tree(process: subprocess.Popen) -> None:
|
|
|
493
528
|
pass
|
|
494
529
|
|
|
495
530
|
|
|
531
|
+
def _runner_environment(
|
|
532
|
+
cmd: list[str], tasks_file: pathlib.Path, task_id: str
|
|
533
|
+
) -> dict[str, str]:
|
|
534
|
+
"""Builds the subprocess environment, including runner compatibility.
|
|
535
|
+
|
|
536
|
+
OpenCode's native Google provider uses the longer environment variable
|
|
537
|
+
name, while existing Lemming eval and user environments commonly expose
|
|
538
|
+
``GOOGLE_API_KEY`` or ``GEMINI_API_KEY``. Preserve an explicitly
|
|
539
|
+
configured native value and otherwise make the existing key available
|
|
540
|
+
under OpenCode's spelling.
|
|
541
|
+
"""
|
|
542
|
+
env = os.environ.copy()
|
|
543
|
+
env["LEMMING_PARENT_TASK_ID"] = task_id
|
|
544
|
+
env["LEMMING_PARENT_TASKS_FILE"] = str(tasks_file.resolve())
|
|
545
|
+
|
|
546
|
+
runner_base = os.path.basename(cmd[0]) if cmd else ""
|
|
547
|
+
if runner_base.startswith("opencode"):
|
|
548
|
+
google_api_key = env.get("GOOGLE_API_KEY") or env.get("GEMINI_API_KEY")
|
|
549
|
+
if google_api_key and not env.get("GOOGLE_GENERATIVE_AI_API_KEY"):
|
|
550
|
+
env["GOOGLE_GENERATIVE_AI_API_KEY"] = google_api_key
|
|
551
|
+
|
|
552
|
+
return env
|
|
553
|
+
|
|
554
|
+
|
|
496
555
|
def run_with_heartbeat(
|
|
497
556
|
cmd: list[str],
|
|
498
557
|
tasks_file: pathlib.Path,
|
|
@@ -545,9 +604,7 @@ def run_with_heartbeat(
|
|
|
545
604
|
|
|
546
605
|
# Start the process in a new session so we can kill its entire process
|
|
547
606
|
# tree if needed.
|
|
548
|
-
env =
|
|
549
|
-
env["LEMMING_PARENT_TASK_ID"] = task_id
|
|
550
|
-
env["LEMMING_PARENT_TASKS_FILE"] = str(tasks_file.resolve())
|
|
607
|
+
env = _runner_environment(cmd, tasks_file, task_id)
|
|
551
608
|
|
|
552
609
|
process = subprocess.Popen(
|
|
553
610
|
cmd,
|
|
@@ -80,11 +80,50 @@ def test_build_runner_command_time_limit_ignored_by_other_runners():
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def test_build_runner_command_aider():
|
|
83
|
+
"""Explicit legacy Aider configurations remain usable."""
|
|
83
84
|
cmd = runner.build_runner_command("aider", "my prompt", yolo=True)
|
|
84
85
|
assert "--yes" in cmd
|
|
85
86
|
assert "--message" in cmd
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
def test_build_runner_command_opencode():
|
|
90
|
+
cmd = runner.build_runner_command("opencode", "my prompt", yolo=True)
|
|
91
|
+
assert cmd == [
|
|
92
|
+
"opencode",
|
|
93
|
+
"run",
|
|
94
|
+
"--auto",
|
|
95
|
+
"--format",
|
|
96
|
+
"json",
|
|
97
|
+
"my prompt",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_build_runner_command_opencode_without_yolo():
|
|
102
|
+
cmd = runner.build_runner_command("opencode", "my prompt", yolo=False)
|
|
103
|
+
assert cmd == ["opencode", "run", "--format", "json", "my prompt"]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_build_runner_command_opencode_without_defaults_still_uses_run():
|
|
107
|
+
cmd = runner.build_runner_command(
|
|
108
|
+
"opencode", "my prompt", yolo=True, no_defaults=True
|
|
109
|
+
)
|
|
110
|
+
assert cmd == ["opencode", "run", "my prompt"]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_build_runner_command_opencode_does_not_duplicate_explicit_run():
|
|
114
|
+
cmd = runner.build_runner_command(
|
|
115
|
+
"opencode run --pure", "my prompt", yolo=False
|
|
116
|
+
)
|
|
117
|
+
assert cmd == [
|
|
118
|
+
"opencode",
|
|
119
|
+
"run",
|
|
120
|
+
"--format",
|
|
121
|
+
"json",
|
|
122
|
+
"--pure",
|
|
123
|
+
"my prompt",
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
|
|
88
127
|
def test_build_runner_command_codex():
|
|
89
128
|
cmd = runner.build_runner_command("codex", "my prompt", yolo=True)
|
|
90
129
|
assert cmd == [
|
|
@@ -214,10 +253,13 @@ def test_conflicting_boolean_flag_does_not_consume_next_flag():
|
|
|
214
253
|
def test_model_is_appended_for_known_runners():
|
|
215
254
|
"""A configured model reaches the runner as --model."""
|
|
216
255
|
cmd = runner.build_runner_command(
|
|
217
|
-
"
|
|
256
|
+
"opencode",
|
|
257
|
+
"prompt",
|
|
258
|
+
yolo=True,
|
|
259
|
+
model="google/gemini-flash-latest",
|
|
218
260
|
)
|
|
219
261
|
|
|
220
|
-
assert cmd[cmd.index("--model") + 1] == "gemini-
|
|
262
|
+
assert cmd[cmd.index("--model") + 1] == "google/gemini-flash-latest"
|
|
221
263
|
|
|
222
264
|
|
|
223
265
|
def test_model_defers_to_explicit_runner_string_flag():
|
|
@@ -444,6 +486,42 @@ def test_run_with_heartbeat_log_header(tmp_path):
|
|
|
444
486
|
assert "=" * 80 not in content_2
|
|
445
487
|
|
|
446
488
|
|
|
489
|
+
def test_opencode_environment_aliases_google_api_key(tmp_path, monkeypatch):
|
|
490
|
+
monkeypatch.setenv("GOOGLE_API_KEY", "existing-key")
|
|
491
|
+
monkeypatch.delenv("GOOGLE_GENERATIVE_AI_API_KEY", raising=False)
|
|
492
|
+
|
|
493
|
+
env = runner._runner_environment(
|
|
494
|
+
["/usr/local/bin/opencode", "run"], tmp_path / "tasks.yml", "abc"
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
assert env["GOOGLE_GENERATIVE_AI_API_KEY"] == "existing-key"
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def test_opencode_environment_preserves_native_google_key(
|
|
501
|
+
tmp_path, monkeypatch
|
|
502
|
+
):
|
|
503
|
+
monkeypatch.setenv("GOOGLE_API_KEY", "legacy-key")
|
|
504
|
+
monkeypatch.setenv("GOOGLE_GENERATIVE_AI_API_KEY", "native-key")
|
|
505
|
+
|
|
506
|
+
env = runner._runner_environment(
|
|
507
|
+
["opencode", "run"], tmp_path / "tasks.yml", "abc"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
assert env["GOOGLE_GENERATIVE_AI_API_KEY"] == "native-key"
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def test_opencode_environment_accepts_gemini_api_key(tmp_path, monkeypatch):
|
|
514
|
+
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
|
|
515
|
+
monkeypatch.delenv("GOOGLE_GENERATIVE_AI_API_KEY", raising=False)
|
|
516
|
+
monkeypatch.setenv("GEMINI_API_KEY", "gemini-key")
|
|
517
|
+
|
|
518
|
+
env = runner._runner_environment(
|
|
519
|
+
["opencode", "run"], tmp_path / "tasks.yml", "abc"
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
assert env["GOOGLE_GENERATIVE_AI_API_KEY"] == "gemini-key"
|
|
523
|
+
|
|
524
|
+
|
|
447
525
|
def test_run_with_heartbeat_records_runner_and_hook_times(tmp_path):
|
|
448
526
|
tasks_file = tmp_path / "tasks.yml"
|
|
449
527
|
task_id = "timed_task"
|
|
@@ -814,6 +892,15 @@ def test_extract_error_message_from_turn_failed():
|
|
|
814
892
|
assert runner.extract_error_message(output) == "quota exhausted"
|
|
815
893
|
|
|
816
894
|
|
|
895
|
+
def test_extract_error_message_from_opencode_error_event():
|
|
896
|
+
output = (
|
|
897
|
+
'{"type":"error","sessionID":"ses_123","error":'
|
|
898
|
+
'{"name":"ProviderError","data":{"message":"quota exhausted"}}}\n'
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
assert runner.extract_error_message(output) == "quota exhausted"
|
|
902
|
+
|
|
903
|
+
|
|
817
904
|
def test_extract_error_message_ignores_tool_failures():
|
|
818
905
|
"""A failed shell command inside the agent is not the runner failing."""
|
|
819
906
|
output = (
|
|
@@ -917,6 +1004,15 @@ _AGY_FINAL = (
|
|
|
917
1004
|
'{"event":"result","result":{"status":"SUCCESS",'
|
|
918
1005
|
'"response":"All tests pass.\\n","num_turns":1}}\n'
|
|
919
1006
|
)
|
|
1007
|
+
_OPENCODE_FINAL = (
|
|
1008
|
+
'{"type":"step_start","sessionID":"ses_123","part":'
|
|
1009
|
+
'{"type":"step-start"}}\n'
|
|
1010
|
+
'{"type":"text","sessionID":"ses_123","part":'
|
|
1011
|
+
'{"type":"text","text":"All tests pass.",'
|
|
1012
|
+
'"time":{"start":1,"end":2}}}\n'
|
|
1013
|
+
'{"type":"step_finish","sessionID":"ses_123","part":'
|
|
1014
|
+
'{"type":"step-finish"}}\n'
|
|
1015
|
+
)
|
|
920
1016
|
|
|
921
1017
|
|
|
922
1018
|
def test_extract_final_message_from_claude_result_event():
|
|
@@ -934,6 +1030,11 @@ def test_extract_final_message_from_agy_result_event():
|
|
|
934
1030
|
assert runner.extract_final_message(_AGY_FINAL) == "All tests pass."
|
|
935
1031
|
|
|
936
1032
|
|
|
1033
|
+
def test_extract_final_message_from_opencode_text_event():
|
|
1034
|
+
"""OpenCode reports completed assistant text in a text-part event."""
|
|
1035
|
+
assert runner.extract_final_message(_OPENCODE_FINAL) == "All tests pass."
|
|
1036
|
+
|
|
1037
|
+
|
|
937
1038
|
def test_extract_final_message_skips_non_json_preamble():
|
|
938
1039
|
"""Codex prints a plain-text notice before its first event."""
|
|
939
1040
|
output = "Reading additional input from stdin...\n" + _CODEX_FINAL
|
|
@@ -55,7 +55,7 @@ test.describe('Dashboard E2E', () => {
|
|
|
55
55
|
} else if (url.includes('/api/runners')) {
|
|
56
56
|
await route.fulfill({
|
|
57
57
|
contentType: 'application/json',
|
|
58
|
-
json: ['agy', '
|
|
58
|
+
json: ['agy', 'opencode'],
|
|
59
59
|
});
|
|
60
60
|
} else if (url.includes('/api/hooks')) {
|
|
61
61
|
await route.fulfill({
|
|
@@ -58,7 +58,7 @@ const mockTasks = [
|
|
|
58
58
|
attempts: 0,
|
|
59
59
|
has_runner_log: false,
|
|
60
60
|
pid: null,
|
|
61
|
-
runner: '
|
|
61
|
+
runner: 'opencode',
|
|
62
62
|
parent: 'd4e5f6',
|
|
63
63
|
completed_at: null,
|
|
64
64
|
run_time: null,
|
|
@@ -200,7 +200,7 @@ test.describe('Screenshot Generation', () => {
|
|
|
200
200
|
await page.route('**/api/runners', async (route) => {
|
|
201
201
|
await route.fulfill({
|
|
202
202
|
contentType: 'application/json',
|
|
203
|
-
json: ['agy', '
|
|
203
|
+
json: ['agy', 'opencode', 'claude', 'codex'],
|
|
204
204
|
});
|
|
205
205
|
});
|
|
206
206
|
await page.route('**/api/hooks', async (route) => {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|