lemming-cli 0.1.4__tar.gz → 0.2.1__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.1.4 → lemming_cli-0.2.1}/PKG-INFO +15 -7
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/README.md +13 -5
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/docs/HOOKS.md +19 -4
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/package-lock.json +4 -4
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/package.json +1 -1
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/pyproject.toml +2 -2
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/hooks_test.py +3 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/tasks.py +11 -8
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/goal.py +1 -7
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/main_test.py +8 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/operations.py +11 -15
- lemming_cli-0.2.1/src/lemming/cli/operations_test.py +93 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/progress.py +1 -8
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/tasks.py +67 -86
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/tasks_test.py +65 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/readability.py +134 -2
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/readability_test.py +80 -1
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/hooks_test.py +5 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/integration_test.py +6 -6
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/models.py +5 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/models_test.py +1 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/orchestrator.py +59 -6
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/orchestrator_test.py +37 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/persistence.py +45 -4
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/persistence_test.py +24 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/prompts/hooks/50-readability.md +20 -7
- lemming_cli-0.2.1/src/lemming/prompts/hooks/70-ux.md +55 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/prompts_test.py +18 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/runner.py +36 -4
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/runner_test.py +53 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/__init__.py +11 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/lifecycle.py +45 -29
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/lifecycle_test.py +21 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/operations.py +51 -27
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/operations_test.py +94 -1
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/queries.py +17 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/dashboard.spec.js +62 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/dashboard.test.js +160 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/index.html +38 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/index.js +66 -0
- lemming_cli-0.2.1/src/lemming/web/mancha.js +58 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/screenshots.spec.js +0 -1
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/uv.lock +5 -5
- lemming_cli-0.1.4/src/lemming/cli/operations_test.py +0 -44
- lemming_cli-0.1.4/src/lemming/web/mancha.js +0 -58
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/.github/workflows/ci.yml +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/.github/workflows/publish.yml +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/.github/workflows/screenshots.yml +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/.gitignore +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/.prettierignore +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/.prettierrc +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/Dockerfile +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/LICENSE +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/biome.json +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/docker-compose.yml +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/docs/EVALS.md +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/docs/screenshots/dashboard-desktop.png +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/docs/screenshots/dashboard-mobile.png +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/docs/screenshots/task-log-desktop.png +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/docs/screenshots/task-log-mobile.png +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/playwright.config.js +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/__init__.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/__init__.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/auth.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/auth_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/config.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/config_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/conftest.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/context.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/context_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/directories.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/directories_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/files.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/files_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/hooks.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/logging.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/logging_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/loop.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/loop_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/main.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/main_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api/tasks_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/api_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/__init__.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/config.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/config_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/goal_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/hooks.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/hooks_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/main.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/progress_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/readability_cli.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli/readability_cli_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/cli_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/conftest.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/__init__.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/__main__.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/cli.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/cli_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/container.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/container_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/fixtures.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/fixtures_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/harness.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/harness_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/roadmap.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/roadmap_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/scenarios.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/scenarios_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/suites.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/trial.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/evals/trial_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/hooks.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/main.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/main_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/paths.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/paths_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/prompts/hooks/60-testing.md +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/prompts/hooks/90-roadmap.md +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/prompts/taskrunner.md +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/prompts.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/providers.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/providers_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/progress.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/progress_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/tasks/queries_test.py +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/favicon.js +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/favicon.spec.js +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/files.html +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/files.spec.js +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/logs.html +0 -0
- {lemming_cli-0.1.4 → lemming_cli-0.2.1}/src/lemming/web/logs.test.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lemming-cli
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -15,7 +15,7 @@ Requires-Dist: fastapi>=0.124.4
|
|
|
15
15
|
Requires-Dist: markdownify>=0.14.1
|
|
16
16
|
Requires-Dist: pydantic>=2.12.5
|
|
17
17
|
Requires-Dist: pyyaml>=6.0.3
|
|
18
|
-
Requires-Dist: readability-cli>=0.
|
|
18
|
+
Requires-Dist: readability-cli>=0.5.0
|
|
19
19
|
Requires-Dist: requests>=2.32.3
|
|
20
20
|
Requires-Dist: uvicorn>=0.33.0
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
@@ -186,11 +186,17 @@ Lemming comes with several built-in hooks to help manage your project:
|
|
|
186
186
|
analyzes the results of the finished task and decides if the remaining roadmap
|
|
187
187
|
needs to be adjusted (e.g., adding a missing prerequisite, skipping obsolete
|
|
188
188
|
tasks, or breaking down a broad task).
|
|
189
|
-
- **`readability`**: A code quality hook that
|
|
190
|
-
|
|
189
|
+
- **`readability`**: A code quality and simplification hook that challenges
|
|
190
|
+
unnecessary complexity and duplicate implementations, then reviews changes
|
|
191
|
+
for adherence to the Google Style Guide and general readability using the
|
|
191
192
|
[readability](https://github.com/owahltinez/readability) tool (exposed as
|
|
192
193
|
`lemming readability`). It can record findings as task progress or suggest
|
|
193
194
|
follow-up refactoring tasks.
|
|
195
|
+
- **`testing`**: Verifies that changed behavior has focused test coverage and
|
|
196
|
+
that the relevant tests pass.
|
|
197
|
+
- **`ux`**: Reviews at most one critical user journey affected by a
|
|
198
|
+
user-visible change. It reports only concrete, reproducible continuity gaps
|
|
199
|
+
and exits immediately for non-user-facing tasks.
|
|
194
200
|
|
|
195
201
|
### Custom and Global Hooks
|
|
196
202
|
|
|
@@ -307,10 +313,12 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
|
|
|
307
313
|
- **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions`
|
|
308
314
|
- **Aider**: Adds `--yes --quiet`
|
|
309
315
|
- **Claude**: Adds `--dangerously-skip-permissions`
|
|
310
|
-
- **Codex**:
|
|
316
|
+
- **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
|
|
317
|
+
YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
|
|
311
318
|
|
|
312
|
-
You can disable
|
|
313
|
-
|
|
319
|
+
You can disable default flag injection with `--no-defaults` (`codex exec`
|
|
320
|
+
remains the Codex execution interface), or use a **template** to fully control
|
|
321
|
+
the command layout:
|
|
314
322
|
|
|
315
323
|
```bash
|
|
316
324
|
lemming run --runner "my-tool --input={{prompt}} --json"
|
|
@@ -164,11 +164,17 @@ Lemming comes with several built-in hooks to help manage your project:
|
|
|
164
164
|
analyzes the results of the finished task and decides if the remaining roadmap
|
|
165
165
|
needs to be adjusted (e.g., adding a missing prerequisite, skipping obsolete
|
|
166
166
|
tasks, or breaking down a broad task).
|
|
167
|
-
- **`readability`**: A code quality hook that
|
|
168
|
-
|
|
167
|
+
- **`readability`**: A code quality and simplification hook that challenges
|
|
168
|
+
unnecessary complexity and duplicate implementations, then reviews changes
|
|
169
|
+
for adherence to the Google Style Guide and general readability using the
|
|
169
170
|
[readability](https://github.com/owahltinez/readability) tool (exposed as
|
|
170
171
|
`lemming readability`). It can record findings as task progress or suggest
|
|
171
172
|
follow-up refactoring tasks.
|
|
173
|
+
- **`testing`**: Verifies that changed behavior has focused test coverage and
|
|
174
|
+
that the relevant tests pass.
|
|
175
|
+
- **`ux`**: Reviews at most one critical user journey affected by a
|
|
176
|
+
user-visible change. It reports only concrete, reproducible continuity gaps
|
|
177
|
+
and exits immediately for non-user-facing tasks.
|
|
172
178
|
|
|
173
179
|
### Custom and Global Hooks
|
|
174
180
|
|
|
@@ -285,10 +291,12 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
|
|
|
285
291
|
- **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions`
|
|
286
292
|
- **Aider**: Adds `--yes --quiet`
|
|
287
293
|
- **Claude**: Adds `--dangerously-skip-permissions`
|
|
288
|
-
- **Codex**:
|
|
294
|
+
- **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
|
|
295
|
+
YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
|
|
289
296
|
|
|
290
|
-
You can disable
|
|
291
|
-
|
|
297
|
+
You can disable default flag injection with `--no-defaults` (`codex exec`
|
|
298
|
+
remains the Codex execution interface), or use a **template** to fully control
|
|
299
|
+
the command layout:
|
|
292
300
|
|
|
293
301
|
```bash
|
|
294
302
|
lemming run --runner "my-tool --input={{prompt}} --json"
|
|
@@ -51,15 +51,30 @@ a broad task).
|
|
|
51
51
|
|
|
52
52
|
### `readability`
|
|
53
53
|
|
|
54
|
-
A code quality hook that
|
|
55
|
-
and
|
|
56
|
-
|
|
54
|
+
A code quality and simplification hook that challenges unnecessary complexity,
|
|
55
|
+
indirection, and duplicated implementations before reviewing changes for
|
|
56
|
+
adherence to the Google Style Guide and general readability using the bundled
|
|
57
|
+
`lemming readability` tool. It provides feedback via task progress or suggests
|
|
58
|
+
follow-up refactoring tasks.
|
|
59
|
+
|
|
60
|
+
### `testing`
|
|
61
|
+
|
|
62
|
+
Checks that changed behavior has focused test coverage and runs the relevant
|
|
63
|
+
tests. It records larger testing gaps as task progress for the roadmap hook.
|
|
64
|
+
|
|
65
|
+
### `ux`
|
|
66
|
+
|
|
67
|
+
A lightweight journey-continuity review for user-visible changes. It considers
|
|
68
|
+
at most one affected critical journey, reports only concrete and reproducible
|
|
69
|
+
gaps, and exits immediately when the task did not change user-facing behavior.
|
|
70
|
+
It runs after `testing` and before `roadmap`.
|
|
57
71
|
|
|
58
72
|
### Editing Built-in Hook Prompts
|
|
59
73
|
|
|
60
74
|
The built-in hook prompts are load-bearing: a wording change can regress
|
|
61
75
|
orchestration behavior without any unit test failing. When you edit a prompt
|
|
62
|
-
under `src/lemming/prompts/hooks
|
|
76
|
+
under `src/lemming/prompts/hooks/` that has an eval suite, run it before
|
|
77
|
+
shipping:
|
|
63
78
|
|
|
64
79
|
```bash
|
|
65
80
|
uv run python -m lemming.evals run --suite roadmap
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@biomejs/biome": "1.9.4",
|
|
13
13
|
"@playwright/test": "^1.60.0",
|
|
14
14
|
"jsdom": "latest",
|
|
15
|
-
"mancha": "^0.22.
|
|
15
|
+
"mancha": "^0.22.7",
|
|
16
16
|
"playwright": "^1.60.0"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
@@ -1019,9 +1019,9 @@
|
|
|
1019
1019
|
}
|
|
1020
1020
|
},
|
|
1021
1021
|
"node_modules/mancha": {
|
|
1022
|
-
"version": "0.22.
|
|
1023
|
-
"resolved": "https://registry.npmjs.org/mancha/-/mancha-0.22.
|
|
1024
|
-
"integrity": "sha512-
|
|
1022
|
+
"version": "0.22.7",
|
|
1023
|
+
"resolved": "https://registry.npmjs.org/mancha/-/mancha-0.22.7.tgz",
|
|
1024
|
+
"integrity": "sha512-ZClxBTYOKSD9f4KqW8lCMYVEUPhxzIRgihw5g20QrD4kzZ9JcQ383LXXkPMVTaCAjLlqYaF3brUMYACwWgQZgA==",
|
|
1025
1025
|
"dev": true,
|
|
1026
1026
|
"license": "MIT",
|
|
1027
1027
|
"dependencies": {
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lemming-cli"
|
|
7
|
-
version = "0.1
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "An autonomous, iterative task runner for AI coding agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -20,7 +20,7 @@ dependencies = [
|
|
|
20
20
|
"markdownify>=0.14.1",
|
|
21
21
|
"pydantic>=2.12.5",
|
|
22
22
|
"pyyaml>=6.0.3",
|
|
23
|
-
"readability-cli>=0.
|
|
23
|
+
"readability-cli>=0.5.0",
|
|
24
24
|
"requests>=2.32.3",
|
|
25
25
|
"uvicorn>=0.33.0",
|
|
26
26
|
]
|
|
@@ -15,6 +15,9 @@ def test_list_hooks(client, test_tasks, monkeypatch, tmp_path):
|
|
|
15
15
|
assert hooks["roadmap"]["masked"] is False
|
|
16
16
|
assert hooks["readability"]["source"] == "built-in"
|
|
17
17
|
assert hooks["readability"]["runs_on_failure"] is False
|
|
18
|
+
assert hooks["ux"]["priority"] == 70
|
|
19
|
+
assert hooks["ux"]["source"] == "built-in"
|
|
20
|
+
assert hooks["ux"]["runs_on_failure"] is False
|
|
18
21
|
|
|
19
22
|
# Hooks are sorted by ascending priority
|
|
20
23
|
priorities = [h["priority"] for h in payload]
|
|
@@ -34,14 +34,17 @@ def add_task(
|
|
|
34
34
|
):
|
|
35
35
|
"""Add a new task and start the orchestrator loop if needed."""
|
|
36
36
|
tasks_file = context.resolve_tasks_file(request.app.state, project)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
try:
|
|
38
|
+
new_task = tasks.add_task(
|
|
39
|
+
tasks_file,
|
|
40
|
+
task.description,
|
|
41
|
+
task.runner,
|
|
42
|
+
index=task.index,
|
|
43
|
+
parent=task.parent,
|
|
44
|
+
parent_tasks_file=task.parent_tasks_file,
|
|
45
|
+
)
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
raise fastapi.HTTPException(400, str(e))
|
|
45
48
|
loop.start_loop_if_needed(
|
|
46
49
|
request.app.state,
|
|
47
50
|
tasks_file,
|
|
@@ -18,13 +18,7 @@ from .main import cli
|
|
|
18
18
|
)
|
|
19
19
|
@click.pass_context
|
|
20
20
|
def goal(ctx: click.Context, goal_text: str | None, file: pathlib.Path | None):
|
|
21
|
-
"""Sets or displays the long-term goal shared across all tasks.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
ctx: The click context holding shared CLI state.
|
|
25
|
-
goal_text: The goal string to set (optional).
|
|
26
|
-
file: A file path to read the goal from (optional).
|
|
27
|
-
"""
|
|
21
|
+
"""Sets or displays the long-term goal shared across all tasks."""
|
|
28
22
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
29
23
|
|
|
30
24
|
if file:
|
|
@@ -16,6 +16,14 @@ class TestCLIMain(unittest.TestCase):
|
|
|
16
16
|
"Lemming: An autonomous, iterative task runner", result.output
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
def test_command_help_hides_internal_docstrings(self):
|
|
20
|
+
for command_name in cli.commands:
|
|
21
|
+
with self.subTest(command=command_name):
|
|
22
|
+
result = self.cli_runner.invoke(cli, [command_name, "--help"])
|
|
23
|
+
self.assertEqual(result.exit_code, 0)
|
|
24
|
+
self.assertNotIn("Args:", result.output)
|
|
25
|
+
self.assertNotIn("ctx: The click context", result.output)
|
|
26
|
+
|
|
19
27
|
|
|
20
28
|
if __name__ == "__main__":
|
|
21
29
|
unittest.main()
|
|
@@ -21,9 +21,7 @@ from .main import cli
|
|
|
21
21
|
@click.option(
|
|
22
22
|
"--retry-delay",
|
|
23
23
|
default=10,
|
|
24
|
-
help=
|
|
25
|
-
"Seconds to wait before retrying a failed task (to handle rate limits)."
|
|
26
|
-
),
|
|
24
|
+
help="Seconds to wait before retrying an incomplete task.",
|
|
27
25
|
)
|
|
28
26
|
@click.option(
|
|
29
27
|
"--yolo/--no-yolo",
|
|
@@ -50,16 +48,7 @@ def run(
|
|
|
50
48
|
no_defaults: bool,
|
|
51
49
|
runner_args: tuple,
|
|
52
50
|
) -> None:
|
|
53
|
-
"""Starts the orchestrator loop to autonomously execute pending tasks.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
ctx: The click context.
|
|
57
|
-
retry_delay: Delay between retries.
|
|
58
|
-
yolo: If True, skip runner confirmations.
|
|
59
|
-
env: Environment variables to inject.
|
|
60
|
-
no_defaults: Skip default flag injection.
|
|
61
|
-
runner_args: Raw arguments passed directly to the runner.
|
|
62
|
-
"""
|
|
51
|
+
"""Starts the orchestrator loop to autonomously execute pending tasks."""
|
|
63
52
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
64
53
|
verbose = ctx.obj["VERBOSE"]
|
|
65
54
|
|
|
@@ -78,9 +67,14 @@ def run(
|
|
|
78
67
|
if env_overrides:
|
|
79
68
|
os.environ.update(env_overrides)
|
|
80
69
|
|
|
81
|
-
|
|
70
|
+
completed = False
|
|
71
|
+
try:
|
|
72
|
+
tasks.acquire_loop_lock(tasks_file)
|
|
73
|
+
except tasks.LoopAlreadyRunningError as e:
|
|
74
|
+
click.echo(f"Error: {e}")
|
|
75
|
+
ctx.exit(1)
|
|
82
76
|
try:
|
|
83
|
-
run_loop(
|
|
77
|
+
completed = run_loop(
|
|
84
78
|
tasks_file,
|
|
85
79
|
verbose,
|
|
86
80
|
retry_delay,
|
|
@@ -91,6 +85,8 @@ def run(
|
|
|
91
85
|
)
|
|
92
86
|
finally:
|
|
93
87
|
tasks.release_loop_lock(tasks_file)
|
|
88
|
+
if not completed:
|
|
89
|
+
ctx.exit(1)
|
|
94
90
|
|
|
95
91
|
|
|
96
92
|
@cli.command(short_help="Launch the web interface")
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import shutil
|
|
3
|
+
import tempfile
|
|
4
|
+
import time
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
import click.testing
|
|
9
|
+
|
|
10
|
+
from lemming import cli, tasks
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestCLIOperations(unittest.TestCase):
|
|
14
|
+
def setUp(self):
|
|
15
|
+
self.cli_runner = click.testing.CliRunner()
|
|
16
|
+
self.test_dir = tempfile.mkdtemp()
|
|
17
|
+
self.test_tasks_file = pathlib.Path(self.test_dir) / "tasks_test.yml"
|
|
18
|
+
self.base_args = [
|
|
19
|
+
"--verbose",
|
|
20
|
+
"--tasks-file",
|
|
21
|
+
str(self.test_tasks_file),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
# Scaffold a valid file
|
|
25
|
+
data = tasks.Roadmap(
|
|
26
|
+
goal="Initial goal",
|
|
27
|
+
tasks=[],
|
|
28
|
+
)
|
|
29
|
+
tasks.save_tasks(self.test_tasks_file, data)
|
|
30
|
+
|
|
31
|
+
def tearDown(self):
|
|
32
|
+
shutil.rmtree(self.test_dir)
|
|
33
|
+
|
|
34
|
+
def test_run_help(self):
|
|
35
|
+
result = self.cli_runner.invoke(cli.cli, ["run", "--help"])
|
|
36
|
+
self.assertEqual(result.exit_code, 0)
|
|
37
|
+
self.assertIn("Starts the orchestrator loop", result.output)
|
|
38
|
+
|
|
39
|
+
def test_run_empty_queue_reports_completion(self):
|
|
40
|
+
result = self.cli_runner.invoke(cli.cli, self.base_args + ["run"])
|
|
41
|
+
|
|
42
|
+
self.assertEqual(result.exit_code, 0)
|
|
43
|
+
self.assertIn("All tasks completed!", result.output)
|
|
44
|
+
|
|
45
|
+
def test_run_blocked_queue_exits_nonzero(self):
|
|
46
|
+
tasks.save_tasks(
|
|
47
|
+
self.test_tasks_file,
|
|
48
|
+
tasks.Roadmap(
|
|
49
|
+
tasks=[
|
|
50
|
+
tasks.Task(
|
|
51
|
+
id="active123",
|
|
52
|
+
description="Active task",
|
|
53
|
+
status=tasks.TaskStatus.IN_PROGRESS,
|
|
54
|
+
pid=1234,
|
|
55
|
+
last_heartbeat=time.time(),
|
|
56
|
+
),
|
|
57
|
+
tasks.Task(
|
|
58
|
+
id="pending456",
|
|
59
|
+
description="Pending task",
|
|
60
|
+
status=tasks.TaskStatus.PENDING,
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with mock.patch(
|
|
67
|
+
"lemming.tasks.lifecycle.is_pid_alive", return_value=True
|
|
68
|
+
):
|
|
69
|
+
result = self.cli_runner.invoke(cli.cli, self.base_args + ["run"])
|
|
70
|
+
|
|
71
|
+
self.assertEqual(result.exit_code, 1)
|
|
72
|
+
self.assertIn("Queue blocked by active task active123", result.output)
|
|
73
|
+
self.assertIn("1 pending task remains", result.output)
|
|
74
|
+
self.assertNotIn("All tasks completed!", result.output)
|
|
75
|
+
|
|
76
|
+
def test_run_rejects_live_loop_owner(self):
|
|
77
|
+
tasks.acquire_loop_lock(self.test_tasks_file)
|
|
78
|
+
try:
|
|
79
|
+
result = self.cli_runner.invoke(cli.cli, self.base_args + ["run"])
|
|
80
|
+
finally:
|
|
81
|
+
tasks.release_loop_lock(self.test_tasks_file)
|
|
82
|
+
|
|
83
|
+
self.assertEqual(result.exit_code, 1)
|
|
84
|
+
self.assertIn("Another loop is already running", result.output)
|
|
85
|
+
|
|
86
|
+
def test_serve_help(self):
|
|
87
|
+
result = self.cli_runner.invoke(cli.cli, ["serve", "--help"])
|
|
88
|
+
self.assertEqual(result.exit_code, 0)
|
|
89
|
+
self.assertIn("Launches the local web dashboard", result.output)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
unittest.main()
|
|
@@ -24,14 +24,7 @@ def progress(
|
|
|
24
24
|
text: typing.Optional[str],
|
|
25
25
|
file: typing.Optional[typing.TextIO],
|
|
26
26
|
):
|
|
27
|
-
"""Records a progress entry or finding for a specific task.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
ctx: The click context holding shared CLI state.
|
|
31
|
-
task_id: The ID of the task.
|
|
32
|
-
text: The progress to record (optional if --file is used).
|
|
33
|
-
file: An optional file to read the progress from.
|
|
34
|
-
"""
|
|
27
|
+
"""Records a progress entry or finding for a specific task."""
|
|
35
28
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
36
29
|
|
|
37
30
|
if file:
|
|
@@ -20,7 +20,7 @@ from .main import cli
|
|
|
20
20
|
@click.option(
|
|
21
21
|
"--index",
|
|
22
22
|
default=-1,
|
|
23
|
-
help="
|
|
23
|
+
help="Zero-based displayed queue position (-1 appends).",
|
|
24
24
|
)
|
|
25
25
|
@click.option(
|
|
26
26
|
"--runner",
|
|
@@ -47,18 +47,7 @@ def add(
|
|
|
47
47
|
parent: str | None,
|
|
48
48
|
parent_tasks_file: str | None,
|
|
49
49
|
):
|
|
50
|
-
"""Adds a new task to the roadmap queue.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
ctx: The click context.
|
|
54
|
-
description: A text description of the task to perform (optional if
|
|
55
|
-
--file is used).
|
|
56
|
-
file: An optional file to read the description from.
|
|
57
|
-
index: The position in the roadmap to insert the task.
|
|
58
|
-
runner_name: An optional custom runner to use for this specific task.
|
|
59
|
-
parent: Optional parent task ID.
|
|
60
|
-
parent_tasks_file: Optional parent tasks file path.
|
|
61
|
-
"""
|
|
50
|
+
"""Adds a new task to the roadmap queue."""
|
|
62
51
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
63
52
|
verbose = ctx.obj["VERBOSE"]
|
|
64
53
|
|
|
@@ -74,14 +63,18 @@ def add(
|
|
|
74
63
|
click.echo("Error: Must provide either description or --file.")
|
|
75
64
|
ctx.exit(1)
|
|
76
65
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
try:
|
|
67
|
+
new_task = tasks.add_task(
|
|
68
|
+
tasks_file,
|
|
69
|
+
description,
|
|
70
|
+
runner_name,
|
|
71
|
+
index=index,
|
|
72
|
+
parent=parent,
|
|
73
|
+
parent_tasks_file=parent_tasks_file,
|
|
74
|
+
)
|
|
75
|
+
except ValueError as e:
|
|
76
|
+
click.echo(f"Error: {e}")
|
|
77
|
+
ctx.exit(1)
|
|
85
78
|
task_id = new_task.id
|
|
86
79
|
|
|
87
80
|
if verbose:
|
|
@@ -100,7 +93,11 @@ def add(
|
|
|
100
93
|
help="Read new description from a file (or - for stdin).",
|
|
101
94
|
)
|
|
102
95
|
@click.option("--runner", "runner_name", help="New custom runner for the task.")
|
|
103
|
-
@click.option(
|
|
96
|
+
@click.option(
|
|
97
|
+
"--index",
|
|
98
|
+
type=int,
|
|
99
|
+
help="New zero-based position in the displayed task queue.",
|
|
100
|
+
)
|
|
104
101
|
@click.option(
|
|
105
102
|
"--parent",
|
|
106
103
|
help="New parent task ID (use empty string to remove).",
|
|
@@ -120,18 +117,7 @@ def edit(
|
|
|
120
117
|
parent: str | None,
|
|
121
118
|
parent_tasks_file: str | None,
|
|
122
119
|
):
|
|
123
|
-
"""Edits an existing task's description, runner, position, or parent.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
ctx: The click context.
|
|
127
|
-
task_id: The ID of the task to update.
|
|
128
|
-
description: The new description (optional).
|
|
129
|
-
file: An optional file to read the new description from.
|
|
130
|
-
runner_name: The new preferred runner (optional).
|
|
131
|
-
index: The new position in the roadmap (optional).
|
|
132
|
-
parent: The new parent task ID (optional).
|
|
133
|
-
parent_tasks_file: The new parent tasks file path (optional).
|
|
134
|
-
"""
|
|
120
|
+
"""Edits an existing task's description, runner, position, or parent."""
|
|
135
121
|
if file:
|
|
136
122
|
if description:
|
|
137
123
|
click.echo("Error: Cannot provide both description and --file.")
|
|
@@ -184,14 +170,7 @@ def edit(
|
|
|
184
170
|
def delete_task(
|
|
185
171
|
ctx: click.Context, task_id: str | None, delete_all: bool, completed: bool
|
|
186
172
|
):
|
|
187
|
-
"""Deletes one or more tasks from the roadmap.
|
|
188
|
-
|
|
189
|
-
Args:
|
|
190
|
-
ctx: The click context.
|
|
191
|
-
task_id: The ID of the specific task to delete.
|
|
192
|
-
delete_all: If set, clears the entire roadmap and long-term goal.
|
|
193
|
-
completed: If set, deletes all tasks marked as 'completed'.
|
|
194
|
-
"""
|
|
173
|
+
"""Deletes one or more tasks from the roadmap."""
|
|
195
174
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
196
175
|
|
|
197
176
|
# Validate argument combinations
|
|
@@ -205,12 +184,16 @@ def delete_task(
|
|
|
205
184
|
click.echo("Error: Provide a task ID, or use --all or --completed.")
|
|
206
185
|
ctx.exit(1)
|
|
207
186
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
187
|
+
try:
|
|
188
|
+
removed = tasks.delete_tasks(
|
|
189
|
+
tasks_file,
|
|
190
|
+
task_id=task_id,
|
|
191
|
+
all_tasks=delete_all,
|
|
192
|
+
completed_only=completed,
|
|
193
|
+
)
|
|
194
|
+
except ValueError as e:
|
|
195
|
+
click.echo(f"Error: {e}")
|
|
196
|
+
ctx.exit(1)
|
|
214
197
|
|
|
215
198
|
if delete_all:
|
|
216
199
|
click.echo(
|
|
@@ -229,12 +212,7 @@ def delete_task(
|
|
|
229
212
|
@click.argument("task_id", required=False)
|
|
230
213
|
@click.pass_context
|
|
231
214
|
def status(ctx: click.Context, task_id: str | None):
|
|
232
|
-
"""Displays the roadmap status or details for a specific task.
|
|
233
|
-
|
|
234
|
-
Args:
|
|
235
|
-
ctx: The click context.
|
|
236
|
-
task_id: Optional ID of the task to inspect in detail.
|
|
237
|
-
"""
|
|
215
|
+
"""Displays the roadmap status or details for a specific task."""
|
|
238
216
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
239
217
|
verbose = ctx.obj["VERBOSE"]
|
|
240
218
|
project_data = tasks.get_project_data(tasks_file)
|
|
@@ -422,24 +400,47 @@ def logs(ctx: click.Context, task_id: str | None):
|
|
|
422
400
|
|
|
423
401
|
@cli.command(short_help="<taskid> Mark a task as completed")
|
|
424
402
|
@click.argument("task_id")
|
|
403
|
+
@click.option(
|
|
404
|
+
"--force",
|
|
405
|
+
is_flag=True,
|
|
406
|
+
help="Finalize an in-progress task immediately, without running hooks.",
|
|
407
|
+
)
|
|
425
408
|
@click.pass_context
|
|
426
|
-
def complete(ctx: click.Context, task_id: str):
|
|
427
|
-
"""Marks a task as completed (requires at least one progress entry).
|
|
428
|
-
|
|
429
|
-
Args:
|
|
430
|
-
ctx: The click context.
|
|
431
|
-
task_id: The ID of the task to mark as completed.
|
|
432
|
-
"""
|
|
409
|
+
def complete(ctx: click.Context, task_id: str, force: bool):
|
|
410
|
+
"""Marks a task as completed (requires at least one progress entry)."""
|
|
433
411
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
434
412
|
|
|
435
413
|
try:
|
|
414
|
+
data = tasks.load_tasks(tasks_file)
|
|
415
|
+
current_task = tasks.resolve_task(data.tasks, task_id)
|
|
416
|
+
task_is_active = tasks.is_task_active(current_task, time.time())
|
|
417
|
+
if (
|
|
418
|
+
current_task.status == tasks.TaskStatus.IN_PROGRESS
|
|
419
|
+
and current_task.progress
|
|
420
|
+
and not force
|
|
421
|
+
and not task_is_active
|
|
422
|
+
and not tasks.is_loop_running(tasks_file)
|
|
423
|
+
):
|
|
424
|
+
raise ValueError(
|
|
425
|
+
f"Task {current_task.id} is in progress, but no active runner "
|
|
426
|
+
"or loop can finalize it. Use --force to complete it without "
|
|
427
|
+
"running hooks, or reset it to retry."
|
|
428
|
+
)
|
|
429
|
+
|
|
436
430
|
target_task = tasks.update_task(
|
|
437
431
|
tasks_file,
|
|
438
432
|
task_id,
|
|
439
433
|
status=tasks.TaskStatus.COMPLETED,
|
|
440
434
|
require_progress=True,
|
|
435
|
+
force=force,
|
|
441
436
|
)
|
|
442
|
-
|
|
437
|
+
if target_task.requested_status == tasks.TaskStatus.COMPLETED:
|
|
438
|
+
click.echo(
|
|
439
|
+
f"Task {target_task.id} completion requested; "
|
|
440
|
+
"finalization hooks are pending."
|
|
441
|
+
)
|
|
442
|
+
else:
|
|
443
|
+
click.echo(f"Task {target_task.id} marked as completed.")
|
|
443
444
|
except ValueError as e:
|
|
444
445
|
click.echo(f"Error: {e}")
|
|
445
446
|
ctx.exit(1)
|
|
@@ -449,12 +450,7 @@ def complete(ctx: click.Context, task_id: str):
|
|
|
449
450
|
@click.argument("task_id")
|
|
450
451
|
@click.pass_context
|
|
451
452
|
def uncomplete(ctx: click.Context, task_id: str):
|
|
452
|
-
"""Unmarks a completed task, moving it back to 'pending' status.
|
|
453
|
-
|
|
454
|
-
Args:
|
|
455
|
-
ctx: The click context.
|
|
456
|
-
task_id: The ID of the task to uncomplete.
|
|
457
|
-
"""
|
|
453
|
+
"""Unmarks a completed task, moving it back to 'pending' status."""
|
|
458
454
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
459
455
|
try:
|
|
460
456
|
target_task = tasks.update_task(
|
|
@@ -470,12 +466,7 @@ def uncomplete(ctx: click.Context, task_id: str):
|
|
|
470
466
|
@click.argument("task_id")
|
|
471
467
|
@click.pass_context
|
|
472
468
|
def fail(ctx: click.Context, task_id: str):
|
|
473
|
-
"""Marks a task as failed (requires
|
|
474
|
-
|
|
475
|
-
Args:
|
|
476
|
-
ctx: The click context.
|
|
477
|
-
task_id: The ID of the task to mark as failed.
|
|
478
|
-
"""
|
|
469
|
+
"""Marks a task as failed (requires recorded progress)."""
|
|
479
470
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
480
471
|
try:
|
|
481
472
|
target_task = tasks.update_task(
|
|
@@ -494,12 +485,7 @@ def fail(ctx: click.Context, task_id: str):
|
|
|
494
485
|
@click.argument("task_id")
|
|
495
486
|
@click.pass_context
|
|
496
487
|
def cancel(ctx: click.Context, task_id: str):
|
|
497
|
-
"""Kills the runner
|
|
498
|
-
|
|
499
|
-
Args:
|
|
500
|
-
ctx: The click context.
|
|
501
|
-
task_id: The ID of the task to cancel.
|
|
502
|
-
"""
|
|
488
|
+
"""Kills the runner and cancels an in-progress task."""
|
|
503
489
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
504
490
|
if tasks.cancel_task(tasks_file, task_id):
|
|
505
491
|
click.echo(f"Task {task_id} cancelled.")
|
|
@@ -512,12 +498,7 @@ def cancel(ctx: click.Context, task_id: str):
|
|
|
512
498
|
@click.argument("task_id")
|
|
513
499
|
@click.pass_context
|
|
514
500
|
def reset(ctx: click.Context, task_id: str):
|
|
515
|
-
"""Clears
|
|
516
|
-
|
|
517
|
-
Args:
|
|
518
|
-
ctx: The click context.
|
|
519
|
-
task_id: The ID of the task to reset.
|
|
520
|
-
"""
|
|
501
|
+
"""Clears a task's attempts, progress, and logs."""
|
|
521
502
|
tasks_file = ctx.obj["TASKS_FILE"]
|
|
522
503
|
try:
|
|
523
504
|
target_task = tasks.reset_task(tasks_file, task_id)
|