ellf-cli 18.0.28__tar.gz → 18.0.30__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.
- {ellf_cli-18.0.28/ellf_cli.egg-info → ellf_cli-18.0.30}/PKG-INFO +1 -1
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/actions.py +4 -2
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/agents.py +13 -3
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/projects.py +19 -1
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/services.py +7 -1
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/tasks.py +4 -6
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/users.py +42 -4
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf.json +1 -1
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_agent_recipe.py +2 -2
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/messages.py +5 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ui.py +0 -15
- ellf_cli-18.0.30/ellf_cli/version/18.0.28_2026-09-21-09-44-01_patch_4430a721-0b3c-4f6a-b9bc-a53e667161bb +3 -0
- ellf_cli-18.0.30/ellf_cli/version/18.0.29_2026-09-21-10-58-07_patch_83b2b942-60a4-4bef-afd1-99690ba52b69 +3 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30/ellf_cli.egg-info}/PKG-INFO +1 -1
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli.egg-info/SOURCES.txt +6 -0
- ellf_cli-18.0.30/tests/test_agents_info_stats.py +69 -0
- ellf_cli-18.0.30/tests/test_jobs_info_hides_creator.py +59 -0
- ellf_cli-18.0.30/tests/test_projects_delete_confirm.py +101 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_ui_extras.py +0 -16
- ellf_cli-18.0.30/tests/test_users_info_stats.py +115 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/LICENSE +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/MANIFEST.in +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/README.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/__main__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/about.json +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/about.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/appdirs.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/auth.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/cli.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/cloud/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/cloud/gcp.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/cluster_config.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/_cluster_select.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/_org_select.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/_project_scope.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/_recipe_file.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/_recipe_subcommand.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/_state.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/_usage.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/assets.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/auth.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/clusters.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/config.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/datasets.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/files/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/files/cp.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/files/ls.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/files/rm.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/files/rsync.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/files/stats.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/general.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/import_export.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/_config_io.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/_helpers.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/configure.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/deploy.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/init.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/init_values.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/provision.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/register.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/setup.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/start.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/terraform.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/infra/tls.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/jobs.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/packages.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/paths.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/plans.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/publish_code.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/publish_data.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/recipes.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/secrets.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/support.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/commands/todos.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/config.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/.claude-plugin/plugin.json +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/.gitignore +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skill_variants.json +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-annotate.assistant/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-annotate.assistant/references/annotation_audit.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-annotate.assistant/references/builtin_ellf_annotation_recipes.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/references/annotation_audit.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/references/builtin_ellf_annotation_recipes.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/references/builtin_prodigy_recipes.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-ask/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-handoff/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-monitor.assistant/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-monitor.assistant/references/annotation_metrics.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-monitor.assistant/references/training_monitoring.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/references/annotation_metrics.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/references/training_monitoring.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/scripts/check_training.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-ops.assistant/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-ops.coding/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-ops.coding/references/data_infra_cli.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-ops.coding/scripts/run_job.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-patterns/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-patterns/references/pattern_strategies.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_action_recipe.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_blocks_ui.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_correct.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_custom_ui.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_manual.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_pages_ui.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_routing.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_task_recipe.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_teach.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/builtin_recipes.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/ellf_recipe_sdk.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/lint_recipe.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/prodigy_recipe_api.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/template_index.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.assistant/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.assistant/references/consulting_patterns.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.assistant/references/explosion_strategy.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.assistant/references/prodigy_llm_bot.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.coding/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.coding/references/consulting_patterns.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.coding/references/explosion_strategy.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-project.coding/references/prodigy_llm_bot.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-support.assistant/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-support.coding/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-todo/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.assistant/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/diagnostics.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/evaluation_guide.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/model_selection.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/training_paradigms.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/workflow.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/SKILL.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/config_advanced.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/config_architectures.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/config_training.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/diagnostics.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/evaluation_guide.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/experiment_patterns.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/model_selection.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/training_paradigms.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/training_troubleshooting.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/workflow.md +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ellf_skills/skills/ellf-train.coding/scripts/ellf_logger.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/errors.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/helm.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/interactivity.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/key_pair.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/main.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/pypi_auth.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/query.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/cookiecutter.json +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/.gitignore +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/README.md.tmpl +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/requirements-dev.in +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/requirements.in +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/setup.py.tmpl +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/about.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/recipes/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/recipes/example_task.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/testing/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/ty.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/url.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/util.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.114_2026-08-19-08-36-57__1a4e37c1-471e-4a81-b4d5-1ffe7dec3a61 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.114_2026-08-19-09-25-21_patch_33ff1e26-0e5e-46d8-8052-ab1d032e8b6c +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.115_2026-08-19-09-27-13_patch_4ac728c3-0342-4a36-adbc-e28d2d99b1ba +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.116_2026-08-19-09-30-47_patch_c706fef6-6813-4183-b268-12e7b00d7d0b +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.117_2026-08-19-09-39-14_patch_c6b0495f-2f88-4aa8-9ebf-0a235b4c407e +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.118_2026-08-19-09-52-00_patch_b395655a-8d78-4344-9266-6901c20f6517 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.119_2026-08-19-10-22-38_patch_8f95c49d-aedc-41b8-816d-ccb95f048831 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.120_2026-08-19-10-26-58_patch_7fbf35e6-cc01-4b75-8c9a-f5d054bcaf93 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.121_2026-08-19-10-42-02_patch_31a81b02-a886-4dea-84e0-67cc6ca412d5 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.122_2026-08-19-11-16-49_patch_1c4d7307-48d4-493e-936d-80af799d644b +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.123_2026-08-19-11-47-32_patch_d3d78d04-9989-45b1-91b1-93b571d8814a +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.124_2026-08-20-08-03-17_patch_57f4c389-2d19-41ce-aca4-13b61c62f387 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.125_2026-08-20-08-25-05_patch_7f368148-0d1c-4525-bbdf-c6d257ad861a +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.126_2026-08-20-14-51-36_patch_a99abcc8-3371-4c63-a759-bb7ea5b32432 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.127_2026-08-21-05-48-58_patch_34b4ccee-7596-4f57-93e9-774973cca58e +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.128_2026-08-21-10-47-20_patch_693112b9-927c-4a8f-8553-b06eef86079d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.129_2026-08-21-11-16-54_patch_7a981666-01d9-4d1b-9ba3-2129744e3bd5 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.130_2026-08-21-13-09-36_patch_b7ad1a76-ce70-4d70-9d1a-7c414eba5bb4 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.131_2026-08-21-14-31-10_patch_9128ef66-193e-4caf-8710-afd86128e638 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.132_2026-08-24-08-59-34_patch_c600fc14-86a5-460f-9c4e-f0177056f756 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.133_2026-08-24-10-28-29_patch_b6ce3e32-9d7b-48ac-981a-270c22c180dd +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.134_2026-08-24-11-00-51_patch_a4fc0484-4a64-46a2-94f8-aa805254c7b0 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.135_2026-08-24-12-12-41_patch_a870a56f-9a65-49eb-92da-f572b26667d8 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.136_2026-08-24-12-42-46_patch_9b399356-6482-4291-8da7-e748a99c4b53 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.137_2026-08-24-12-53-57_patch_f7979348-1910-40cd-a535-90299eca84ee +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.138_2026-08-24-14-36-30_patch_ed0f8750-f663-498b-85e7-0d3f2213fcfe +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.139_2026-08-25-08-37-26_patch_3ba87e19-902b-4f76-b64c-90e5c0ca1fc6 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.140_2026-08-25-09-52-45_patch_abc830f2-df15-4385-b797-cdaf72c10752 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.141_2026-08-25-11-37-20_patch_1d9ec01e-06f6-4c8e-b095-af17d5b62b00 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.142_2026-08-26-07-38-49_patch_3dd23e36-e7df-494c-986f-a412861a8d40 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.143_2026-08-26-09-32-32_patch_7b7ebd34-de27-4b73-98a7-3e1501e16cd5 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.144_2026-08-26-10-56-43_patch_166064f2-2208-4c9b-9e25-25dc4cd32084 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.145_2026-08-26-11-37-29_patch_b997f3ee-d7d3-4408-9f2f-c8b6d08bdb3b +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.146_2026-08-26-12-15-20_patch_afbbb893-25db-468c-81db-2663ca373068 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.147_2026-08-26-14-10-50_patch_fd753b1f-2fa1-4ddb-a9e7-de690da85254 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.148_2026-08-27-09-08-35_patch_23814c0b-7f77-489c-b4a8-ceadd18146e3 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.149_2026-08-27-09-40-17_patch_8879d72e-a3d8-4abc-a16a-d9dcf75f0fa1 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.150_2026-08-27-10-57-25_patch_afbe60ed-3e23-4d88-be3c-f800d7002a7d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.151_2026-08-28-09-02-46_patch_1d4ca23b-aeae-4ea8-96b1-8cf57371f29d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.152_2026-08-28-15-37-24_patch_c9d0bdc1-a7e1-432b-92b9-92dc76fe0c78 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.153_2026-08-29-09-03-27_patch_7531573a-1b33-4092-aa9c-0e65f47b201f +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.154_2026-08-29-11-56-35_patch_919f5787-39c8-4dc1-ae6a-f27c28380f21 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.155_2026-08-29-15-15-59_patch_822f9ed8-c366-4e5f-9791-dee8467cf3ce +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.156_2026-08-29-16-19-01_patch_3ac2bf36-20f7-4c9f-9fdb-295d4466d59e +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.157_2026-08-30-17-32-17_patch_4302354c-c9c5-4be7-acd3-542872a748d8 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.158_2026-08-31-09-28-33_patch_3523774f-c25d-4102-8c1a-92fff046a48d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.159_2026-08-31-10-31-01_patch_fe3e6290-2553-4154-a0a0-8b8a418280ed +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.160_2026-08-31-11-23-39_patch_e701a085-3d23-4212-b0af-73b1d419a4c1 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.161_2026-08-31-12-41-01_patch_074d7bde-c493-44db-bc15-e81fd91e2921 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.162_2026-08-31-13-58-06_patch_84db1d60-4280-433b-bd3a-22e5611cd315 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.163_2026-08-31-15-25-54_patch_1931c5ba-73df-48cb-8dcb-555ace0af267 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.163_2026-08-31-15-44-37_patch_3962e805-2a92-494a-a87b-b6e6d66c40a0 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.165_2026-08-31-19-48-59_patch_86e5b923-2b99-4b86-b542-469b197614be +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.166_2026-09-01-07-39-57_patch_c344892f-d542-4e7b-8f27-02365a411e15 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.167_2026-09-01-08-21-53_patch_e199d419-282a-4dbb-8756-cf98b7037e2b +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.168_2026-09-01-09-12-31_patch_235c9fe0-da15-431d-bb56-01e2b9b15f4b +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.169_2026-09-01-11-54-19_patch_98b617ab-d130-42b5-93f2-19cae56d4c4c +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.170_2026-09-02-09-27-28_patch_e448d2e6-b7ec-4235-96bf-0173b3623c87 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.171_2026-09-02-12-52-44_patch_e6a241c2-1d62-482c-a9eb-9e3373395c50 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.172_2026-09-04-08-47-04_patch_daae079f-c104-4fcd-bb57-7bff68e80ad9 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.173_2026-09-04-12-34-42_patch_399c4cbd-0c28-4dfd-a4ec-e569420a36e4 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.174_2026-09-04-13-08-48_patch_f5cfe3d5-808e-45e4-bafc-00d0554838ee +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.175_2026-09-04-17-20-46_patch_1ef542fa-5d8e-4d90-aa15-9fc4cea3dfec +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.176_2026-09-05-08-25-05_patch_ced5521f-f9da-4640-a921-95fcb5cd2e6d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.177_2026-09-05-09-00-16_patch_803c1ad4-a9af-4953-92c5-d882dec03ea2 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.178_2026-09-05-09-30-34_patch_012f6259-2e3f-4591-9806-1920fd06cb1f +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.179_2026-09-05-09-33-43_patch_f4bdf28c-d584-47a5-b699-8c883330162c +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.180_2026-09-05-12-02-21_patch_34a14b01-d22d-44a8-ac63-be0370a61422 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.181_2026-09-05-12-47-46_patch_56b18867-99f6-4d5c-8215-7b9d60d34010 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.182_2026-09-05-13-35-18_patch_61b7d4d1-73c8-4603-9b0c-593e7405587f +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.183_2026-09-06-07-27-09_patch_d76331b2-e3a3-4c0b-8412-ecd131b6ac02 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.184_2026-09-06-10-21-47_patch_7680b7a1-49f0-4eef-b12e-4c854407448c +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.185_2026-09-06-10-30-40_patch_e56c2183-77f8-44ef-a92e-7377ea6ce8c1 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.186_2026-09-07-10-00-51_patch_67de5683-d208-4bc0-8fb6-7677377908e7 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.187_2026-09-07-10-18-13_patch_2086cee2-276b-48f7-80ce-6a17397b9541 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.188_2026-09-07-12-51-28_patch_d6590822-068f-4a19-814d-34bbf204ba6e +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.189_2026-09-07-12-55-23_patch_7e0fb04b-4d40-4490-974f-c74ba2ae55a1 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.190_2026-09-08-08-58-03_patch_15e3ac1e-4475-4272-bdd8-d00bb0db8f17 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.191_2026-09-08-09-55-58_patch_3d350e50-ee53-4282-bc75-d2251142c46e +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.192_2026-09-08-11-12-21_patch_0733223b-1ae2-4b5b-897f-dfc57574ff83 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.193_2026-09-08-21-48-17_patch_8d07d4e6-3555-4720-85c9-9452570fae2d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.194_2026-09-09-07-53-43_patch_69559429-aa42-4b62-9bd7-cdfded1c00d0 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.195_2026-09-09-09-55-40_patch_bbf67f1d-3b9a-43a2-b1bc-22df3fed8a34 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.196_2026-09-09-09-59-47_patch_70d76812-7b3c-4192-ad49-fcaccc29b0da +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.197_2026-09-09-13-06-06_patch_d455be0b-5421-492a-b952-fab0fd8110f8 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.198_2026-09-09-13-14-34_patch_267f54e5-311e-435e-882f-8e19191325ea +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.199_2026-09-09-22-24-18_patch_81e3e453-ca0e-42fa-9c21-8f94d43c05f2 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.200_2026-09-10-07-19-50_patch_911714fa-1fa3-4165-bfe6-d10c650bf5bf +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.201_2026-09-10-10-14-19_patch_864015f2-b0b0-4563-a1c8-155629cb9126 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.202_2026-09-10-11-00-05_patch_18270ecf-e770-4e69-8bcf-e5143c39929b +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.203_2026-09-10-11-10-41_patch_ff273ab1-44f3-4bc0-b184-3396e943df04 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.204_2026-09-10-13-02-02_patch_d4ee41e2-d13c-4941-8605-3f1a42b56b22 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.205_2026-09-10-13-57-50_patch_284df67e-5957-4b0c-bc64-5581db6ddecc +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.206_2026-09-11-10-18-50_patch_1ac7e410-0e62-4eef-bbe4-ebf93c9d0869 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/17.0.207_2026-09-11-15-26-02_major_3f1b3ed7-eaac-41f5-b740-0af3331595d9 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.0_2026-09-12-10-01-10_patch_8995783a-a9fa-4c4e-90b3-6386a4c481eb +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.10_2026-09-14-16-09-38_patch_9403d562-eed0-4125-a8b3-09cd92a13298 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.11_2026-09-15-09-05-57_patch_8b22dba2-d931-4c4d-ae60-db3238d5e73d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.12_2026-09-15-11-18-52_patch_81314afa-e70d-4ad8-b45b-3f8d62b983e7 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.13_2026-09-16-08-00-55_patch_bfa30fc0-3281-4de6-bb56-d7290c98d1c3 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.14_2026-09-16-14-38-26_patch_aae6e9b0-88f4-4bcf-b611-3308c7fcd6eb +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.15_2026-09-17-07-58-54_patch_43d40582-effd-4925-a8bd-e70fecf1faab +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.16_2026-09-17-08-12-37_patch_b4db2d23-88f1-45c1-82d9-78d38014fcb6 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.17_2026-09-18-09-38-19_patch_25697fc3-e9a8-4980-962b-e6cb0a4760c5 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.18_2026-09-18-09-54-33_patch_d37d9c71-c469-4d94-90d7-4898915ee991 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.19_2026-09-18-10-47-33_patch_47607b3c-d1b6-4864-b9b0-00a2a8d2ac32 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.1_2026-09-12-17-33-30_patch_f6ea2c99-a715-4c4d-876a-23ed8c62a83d +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.20_2026-09-18-11-07-21_patch_5a336f91-63a6-4877-97ed-da89395cc8f5 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.21_2026-09-18-13-34-52_patch_c56462e1-63d9-498f-a912-c80c5f0171d1 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.22_2026-09-20-08-10-45_patch_6afa2c20-bcef-4b7d-8685-2c9815d8683f +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.23_2026-09-20-08-38-35_patch_a7f169e5-37ab-4ec0-bf89-d14770181303 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.24_2026-09-20-15-49-27_patch_c1222081-ff44-4f20-aea2-5840b6d76efd +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.25_2026-09-21-06-11-40_patch_6df46dd9-e7d3-4e71-bec4-e5c93b4ac863 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.26_2026-09-21-07-54-11_patch_cc52dd36-5eba-4094-a1b0-d6f79f103441 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.27_2026-09-21-08-36-49_patch_a25fa125-9bcd-4317-8da5-851c1c329631 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.2_2026-09-13-06-36-33_patch_176bb944-1c49-4dd3-a001-8fce908825a0 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.3_2026-09-13-12-32-46_patch_58e5ce3a-f2a7-4789-b2cd-5cc75c72b6ec +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.4_2026-09-13-12-45-14_patch_b6c79d55-8f85-4f47-a304-df52b122c38c +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.5_2026-09-13-14-11-18_patch_9b111116-5e62-472a-b72c-d1bbafa22c97 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.5_2026-09-13-17-01-11_patch_d4bf61c0-83ac-4dd3-b512-a65805d298c8 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.6_2026-09-14-11-31-04_patch_fd2ebda6-22cb-4e7d-9452-2f6f2640e828 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.7_2026-09-13-17-06-08_patch_428af161-0b27-43c2-8751-e5f7874df120 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/18.0.9_2026-09-14-12-37-42_patch_b2ca0529-4b67-4fa4-8fdc-5f5e9f5d5166 +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/__init__.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli/version/read_version.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli.egg-info/dependency_links.txt +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli.egg-info/entry_points.txt +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli.egg-info/not-zip-safe +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli.egg-info/requires.txt +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/ellf_cli.egg-info/top_level.txt +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/pyproject.toml +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/setup.cfg +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/setup.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_appdirs.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_auth.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_config.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_errors.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_files_cp.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_files_cp_helpers.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_helm.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_import_export.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_info.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_config_io.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_configure.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_deploy_node_labels.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_get_terraform.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_init.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_provision.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_start_fixes.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_start_validation.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_state_bucket.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_infra_terraform_init.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_invalid_secrets.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_job_list_project_filter.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_jobs_list_unmatched.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_key_pair.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_login.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_logout.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_main.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_org_select.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_plans.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_projects.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_publish_build_poll.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_publish_data.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_publish_import_root.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_publish_requirements.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_pypi_auth.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_query.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_recipe_file.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_recipes.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_state.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_support.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_todos.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_ty.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_ui.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_usage_counts.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_users_assign.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_users_list.py +0 -0
- {ellf_cli-18.0.28 → ellf_cli-18.0.30}/tests/test_util.py +0 -0
|
@@ -220,8 +220,10 @@ def info(
|
|
|
220
220
|
as_json=as_json,
|
|
221
221
|
select=select,
|
|
222
222
|
stats=_stats(res),
|
|
223
|
-
#
|
|
224
|
-
table_exclude=
|
|
223
|
+
# Noise in a table; --json and an explicit --select still carry them.
|
|
224
|
+
table_exclude=(
|
|
225
|
+
None if select else ["stats", "related_tasks", "created_by_user"]
|
|
226
|
+
),
|
|
225
227
|
)
|
|
226
228
|
return res
|
|
227
229
|
|
|
@@ -3,6 +3,7 @@ from collections.abc import Sequence
|
|
|
3
3
|
from typing import Any, Optional, Union
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
|
+
from pydantic import ValidationError
|
|
6
7
|
from radicli import Arg
|
|
7
8
|
from wasabi import msg
|
|
8
9
|
|
|
@@ -13,6 +14,7 @@ from ..errors import (
|
|
|
13
14
|
BrokerError,
|
|
14
15
|
CLIError,
|
|
15
16
|
EllfError,
|
|
17
|
+
EllfErrors,
|
|
16
18
|
HTTPXErrors,
|
|
17
19
|
JobStartRejectedError,
|
|
18
20
|
)
|
|
@@ -296,17 +298,25 @@ def info(
|
|
|
296
298
|
as_json=as_json,
|
|
297
299
|
select=select,
|
|
298
300
|
stats=_stats(res),
|
|
299
|
-
#
|
|
300
|
-
table_exclude=["related_tasks"],
|
|
301
|
+
# Noise in a table; --json and an explicit --select still carry them.
|
|
302
|
+
table_exclude=None if select else ["related_tasks", "created_by_user"],
|
|
301
303
|
)
|
|
302
304
|
return res
|
|
303
305
|
|
|
304
306
|
|
|
305
307
|
def _stats(agent: AgentDetail) -> dict[str, Any]:
|
|
306
|
-
|
|
308
|
+
stats = {
|
|
307
309
|
"tasks": len(agent.related_tasks),
|
|
308
310
|
"last_started": agent.last_execution_started,
|
|
309
311
|
}
|
|
312
|
+
try:
|
|
313
|
+
counts = get_auth_state().client.agent.annotation_counts(agent.id)
|
|
314
|
+
except (EllfErrors.EllfError, ValidationError, *HTTPXErrors):
|
|
315
|
+
# Stats are decorative; a mismatched PAM schema shouldn't fail `info`.
|
|
316
|
+
return stats
|
|
317
|
+
stats["annotations"] = counts.annotations
|
|
318
|
+
stats["sessions"] = counts.sessions
|
|
319
|
+
return stats
|
|
310
320
|
|
|
311
321
|
|
|
312
322
|
@cli.subcommand(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import builtins
|
|
2
2
|
import logging
|
|
3
|
+
import sys
|
|
3
4
|
from collections.abc import Sequence
|
|
4
5
|
from typing import Any, Optional, Union
|
|
5
6
|
from uuid import UUID
|
|
@@ -19,6 +20,7 @@ from ellf_pam_sdk.models import (
|
|
|
19
20
|
from ..auth import AuthState
|
|
20
21
|
from ..cli import cli
|
|
21
22
|
from ..errors import BrokerError, CLIError, EllfErrors, HTTPXErrors
|
|
23
|
+
from ..interactivity import isatty
|
|
22
24
|
from ..messages import Messages
|
|
23
25
|
from ..query import resolve_project, resolve_project_id
|
|
24
26
|
from ..ui import print_info_table, print_mutation_result, print_table_with_select
|
|
@@ -186,7 +188,9 @@ def update(
|
|
|
186
188
|
)
|
|
187
189
|
def delete(name_or_id: Union[str, UUID], as_json: bool = False) -> UUID:
|
|
188
190
|
"""Delete a project"""
|
|
189
|
-
|
|
191
|
+
project = resolve_project(name_or_id)
|
|
192
|
+
project_id = project.id
|
|
193
|
+
_confirm_delete(project)
|
|
190
194
|
auth = get_auth_state()
|
|
191
195
|
_stop_project_jobs(auth, project_id)
|
|
192
196
|
try:
|
|
@@ -205,6 +209,20 @@ def delete(name_or_id: Union[str, UUID], as_json: bool = False) -> UUID:
|
|
|
205
209
|
return project_id
|
|
206
210
|
|
|
207
211
|
|
|
212
|
+
def _confirm_delete(project: ProjectDetail) -> None:
|
|
213
|
+
"""Type the project name back to confirm.
|
|
214
|
+
|
|
215
|
+
Refused without a terminal, so a script or an agent fails instead of
|
|
216
|
+
hanging on a prompt nothing will answer.
|
|
217
|
+
"""
|
|
218
|
+
if not isatty(sys.stdin) or not isatty(sys.stdout):
|
|
219
|
+
raise CLIError(Messages.E193.format(name=project.name))
|
|
220
|
+
msg.warn(Messages.T071.format(name=project.name))
|
|
221
|
+
typed = input(Messages.project_delete_confirm).strip()
|
|
222
|
+
if typed != project.name:
|
|
223
|
+
raise CLIError(Messages.E194.format(name=project.name))
|
|
224
|
+
|
|
225
|
+
|
|
208
226
|
def _stop_project_jobs(auth: AuthState, project_id: UUID) -> None:
|
|
209
227
|
"""Stop all cluster jobs for a project's tasks, actions, and agents."""
|
|
210
228
|
job_ids: builtins.list[tuple[UUID, broker_models.JobType]] = []
|
|
@@ -212,7 +212,13 @@ def info(
|
|
|
212
212
|
except (BrokerError, *HTTPXErrors) as e:
|
|
213
213
|
msg.warn(f"Could not fetch live status from the cluster: {e}")
|
|
214
214
|
print_info_table(
|
|
215
|
-
res,
|
|
215
|
+
res,
|
|
216
|
+
exclude=["plan"],
|
|
217
|
+
as_json=as_json,
|
|
218
|
+
select=select,
|
|
219
|
+
stats=_stats(res),
|
|
220
|
+
# Noise in a table; --json and an explicit --select still carry it.
|
|
221
|
+
table_exclude=None if select else ["created_by_user"],
|
|
216
222
|
)
|
|
217
223
|
return res
|
|
218
224
|
|
|
@@ -29,7 +29,6 @@ from ..query import (
|
|
|
29
29
|
stop_job,
|
|
30
30
|
)
|
|
31
31
|
from ..ui import (
|
|
32
|
-
format_duration,
|
|
33
32
|
print_args_table,
|
|
34
33
|
print_info_table,
|
|
35
34
|
print_logs,
|
|
@@ -225,8 +224,8 @@ def info(
|
|
|
225
224
|
as_json=as_json,
|
|
226
225
|
select=select,
|
|
227
226
|
stats=_stats(res, as_json=as_json),
|
|
228
|
-
#
|
|
229
|
-
table_exclude=["related_actions"],
|
|
227
|
+
# Noise in a table; --json and an explicit --select still carry them.
|
|
228
|
+
table_exclude=None if select else ["related_actions", "created_by_user"],
|
|
230
229
|
)
|
|
231
230
|
return res
|
|
232
231
|
|
|
@@ -239,14 +238,13 @@ def _stats(task: TaskDetail, *, as_json: bool) -> dict[str, Any]:
|
|
|
239
238
|
# Stats are decorative. A PAM release whose stats schema differs from
|
|
240
239
|
# this CLI's (older, or newer) should not take down `info`.
|
|
241
240
|
return {"related_actions": len(task.related_actions)}
|
|
242
|
-
duration = stats.duration or 0
|
|
243
241
|
return {
|
|
242
|
+
"annotations": stats.annotations,
|
|
244
243
|
"annotators": stats.annotators,
|
|
245
244
|
"agents": stats.agents or 0,
|
|
246
245
|
"sessions": stats.sessions,
|
|
247
246
|
"live_sessions": stats.live_sessions,
|
|
248
|
-
|
|
249
|
-
"duration": duration if as_json else format_duration(duration),
|
|
247
|
+
"duration": stats.duration,
|
|
250
248
|
"related_actions": len(task.related_actions),
|
|
251
249
|
}
|
|
252
250
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import builtins
|
|
2
2
|
from collections.abc import Sequence
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
from typing import Literal, Optional, Union
|
|
4
|
+
from typing import Any, Literal, Optional, Union
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
+
from pydantic import ValidationError
|
|
7
8
|
from radicli import Arg
|
|
8
9
|
from wasabi import msg
|
|
9
10
|
|
|
@@ -25,7 +26,11 @@ from ..cli import cli
|
|
|
25
26
|
from ..errors import CLIError, EllfErrors, HTTPXErrors
|
|
26
27
|
from ..messages import Messages
|
|
27
28
|
from ..query import _try_uuid, resolve_task
|
|
28
|
-
from ..ui import
|
|
29
|
+
from ..ui import (
|
|
30
|
+
print_info_table,
|
|
31
|
+
print_mutation_result,
|
|
32
|
+
print_table_with_select,
|
|
33
|
+
)
|
|
29
34
|
from ._state import get_auth_state
|
|
30
35
|
|
|
31
36
|
# Mirrors the role model of the web app's members page: a user is an
|
|
@@ -177,7 +182,7 @@ def info(
|
|
|
177
182
|
) -> UserDetail:
|
|
178
183
|
"""Get detailed info for a user"""
|
|
179
184
|
res = _resolve_user(email_name_or_id)
|
|
180
|
-
|
|
185
|
+
_print_user(res, select=select, as_json=as_json)
|
|
181
186
|
return res
|
|
182
187
|
|
|
183
188
|
|
|
@@ -197,10 +202,43 @@ def whoami(
|
|
|
197
202
|
"""Show the currently authenticated user"""
|
|
198
203
|
auth = get_auth_state()
|
|
199
204
|
res = auth.client.user.read(id=auth.user_id)
|
|
200
|
-
|
|
205
|
+
_print_user(res, select=select, as_json=as_json)
|
|
201
206
|
return res
|
|
202
207
|
|
|
203
208
|
|
|
209
|
+
def _print_user(
|
|
210
|
+
user: UserDetail, *, select: Optional[builtins.list[str]], as_json: bool
|
|
211
|
+
) -> None:
|
|
212
|
+
"""The shared body of `users info` and `users whoami`."""
|
|
213
|
+
print_info_table(
|
|
214
|
+
user,
|
|
215
|
+
as_json=as_json,
|
|
216
|
+
select=select,
|
|
217
|
+
stats=_stats(user, as_json=as_json),
|
|
218
|
+
# Noise in a table; --json and an explicit --select still carry them.
|
|
219
|
+
table_exclude=None if select else ["groups", "tags"],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _stats(user: UserDetail, *, as_json: bool) -> dict[str, Any]:
|
|
224
|
+
"""This member's org-wide annotation stats, for the info block."""
|
|
225
|
+
client = get_auth_state().client
|
|
226
|
+
try:
|
|
227
|
+
stats = client.user.stats(user.id)
|
|
228
|
+
except (EllfErrors.EllfError, ValidationError, *HTTPXErrors):
|
|
229
|
+
# Stats are decorative; a mismatched PAM schema shouldn't fail `info`.
|
|
230
|
+
return {}
|
|
231
|
+
return {
|
|
232
|
+
"projects": stats.projects,
|
|
233
|
+
"tasks": stats.tasks,
|
|
234
|
+
"sessions": stats.sessions,
|
|
235
|
+
"annotations": stats.annotations,
|
|
236
|
+
"duration": stats.duration,
|
|
237
|
+
"last_annotated": stats.last_annotated,
|
|
238
|
+
"last_active": stats.last_active,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
204
242
|
@cli.subcommand(
|
|
205
243
|
"users",
|
|
206
244
|
"update",
|
|
@@ -68,8 +68,8 @@ def create_llm_client(api_key: str, model_name: str):
|
|
|
68
68
|
#
|
|
69
69
|
# The SDK reads poll_interval and idle_shutdown_seconds from the recipe
|
|
70
70
|
# parameters automatically — you don't need to handle them yourself. The
|
|
71
|
-
# same goes for icon: this is the avatar used in the app wherever the agent's
|
|
72
|
-
# annotations are attributed to it. Pick the default that suits your agent — the
|
|
71
|
+
# same goes for icon: this is the avatar used in the app wherever the agent's
|
|
72
|
+
# annotations are attributed to it. Pick the default that suits your agent — the
|
|
73
73
|
# model provider it calls, or what it does — from the set in props.icon.
|
|
74
74
|
#
|
|
75
75
|
# Agents are created and managed like tasks and actions:
|
|
@@ -142,6 +142,9 @@ ellf secret create my-credentials OPENAI_API_KEY="sk-..." CUSTOM_SECRET=-
|
|
|
142
142
|
T068 = "Select your plan here:\n{url}"
|
|
143
143
|
T069 = "Your free trial has ended"
|
|
144
144
|
T070 = "Today is the last day of your free trial"
|
|
145
|
+
T071 = "This deletes {name} and everything in it: tasks, actions, agents, services, project plans and annotations. Any annotated datasets stay on your cluster."
|
|
146
|
+
|
|
147
|
+
project_delete_confirm = "Type the project name to confirm: "
|
|
145
148
|
|
|
146
149
|
# CLI docstrings for placeholders (parent commands with no functions)
|
|
147
150
|
doc_todos = "Manage todos in your Ellf projects"
|
|
@@ -302,6 +305,8 @@ ellf secret create my-credentials OPENAI_API_KEY="sk-..." CUSTOM_SECRET=-
|
|
|
302
305
|
E190 = "No project set, so there is nothing to list {plural} for. Pass --project, or set a default with `ellf config project`"
|
|
303
306
|
E191 = "Could not create {noun} {name}: input validation failed"
|
|
304
307
|
E192 = "Annotators can't log in via the CLI. Ask an admin of your organization '{org}' to upgrade your role to developer, then run `{command}` again."
|
|
308
|
+
E193 = "Deleting a project has to be confirmed via a terminal. Run `ellf projects delete {name}` again in an interactive terminal session."
|
|
309
|
+
E194 = "Wrong project name, so {name} was not deleted"
|
|
305
310
|
|
|
306
311
|
|
|
307
312
|
# fmt: on
|
|
@@ -150,21 +150,6 @@ def _json_default(value: Any) -> Any:
|
|
|
150
150
|
return str(value)
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def format_duration(seconds: int) -> str:
|
|
154
|
-
"""A span of seconds as ``1h 30m``, ``45m`` or ``0m``.
|
|
155
|
-
|
|
156
|
-
Annotation time is reported in whole seconds, but nobody reads a session
|
|
157
|
-
to the second: minutes are the finest unit shown.
|
|
158
|
-
"""
|
|
159
|
-
minutes, _ = divmod(max(0, int(seconds)), 60)
|
|
160
|
-
hours, minutes = divmod(minutes, 60)
|
|
161
|
-
if hours and minutes:
|
|
162
|
-
return f"{hours}h {minutes}m"
|
|
163
|
-
if hours:
|
|
164
|
-
return f"{hours}h"
|
|
165
|
-
return f"{minutes}m"
|
|
166
|
-
|
|
167
|
-
|
|
168
153
|
def print_info_table(
|
|
169
154
|
item: BaseModel,
|
|
170
155
|
select: list[str] | None = None,
|
|
@@ -281,6 +281,8 @@ ellf_cli/version/18.0.24_2026-09-20-15-49-27_patch_c1222081-ff44-4f20-aea2-5840b
|
|
|
281
281
|
ellf_cli/version/18.0.25_2026-09-21-06-11-40_patch_6df46dd9-e7d3-4e71-bec4-e5c93b4ac863
|
|
282
282
|
ellf_cli/version/18.0.26_2026-09-21-07-54-11_patch_cc52dd36-5eba-4094-a1b0-d6f79f103441
|
|
283
283
|
ellf_cli/version/18.0.27_2026-09-21-08-36-49_patch_a25fa125-9bcd-4317-8da5-851c1c329631
|
|
284
|
+
ellf_cli/version/18.0.28_2026-09-21-09-44-01_patch_4430a721-0b3c-4f6a-b9bc-a53e667161bb
|
|
285
|
+
ellf_cli/version/18.0.29_2026-09-21-10-58-07_patch_83b2b942-60a4-4bef-afd1-99690ba52b69
|
|
284
286
|
ellf_cli/version/18.0.2_2026-09-13-06-36-33_patch_176bb944-1c49-4dd3-a001-8fce908825a0
|
|
285
287
|
ellf_cli/version/18.0.3_2026-09-13-12-32-46_patch_58e5ce3a-f2a7-4789-b2cd-5cc75c72b6ec
|
|
286
288
|
ellf_cli/version/18.0.4_2026-09-13-12-45-14_patch_b6c79d55-8f85-4f47-a304-df52b122c38c
|
|
@@ -291,6 +293,7 @@ ellf_cli/version/18.0.7_2026-09-13-17-06-08_patch_428af161-0b27-43c2-8751-e5f787
|
|
|
291
293
|
ellf_cli/version/18.0.9_2026-09-14-12-37-42_patch_b2ca0529-4b67-4fa4-8fdc-5f5e9f5d5166
|
|
292
294
|
ellf_cli/version/__init__.py
|
|
293
295
|
ellf_cli/version/read_version.py
|
|
296
|
+
tests/test_agents_info_stats.py
|
|
294
297
|
tests/test_appdirs.py
|
|
295
298
|
tests/test_auth.py
|
|
296
299
|
tests/test_config.py
|
|
@@ -312,6 +315,7 @@ tests/test_infra_state_bucket.py
|
|
|
312
315
|
tests/test_infra_terraform_init.py
|
|
313
316
|
tests/test_invalid_secrets.py
|
|
314
317
|
tests/test_job_list_project_filter.py
|
|
318
|
+
tests/test_jobs_info_hides_creator.py
|
|
315
319
|
tests/test_jobs_list_unmatched.py
|
|
316
320
|
tests/test_key_pair.py
|
|
317
321
|
tests/test_login.py
|
|
@@ -320,6 +324,7 @@ tests/test_main.py
|
|
|
320
324
|
tests/test_org_select.py
|
|
321
325
|
tests/test_plans.py
|
|
322
326
|
tests/test_projects.py
|
|
327
|
+
tests/test_projects_delete_confirm.py
|
|
323
328
|
tests/test_publish_build_poll.py
|
|
324
329
|
tests/test_publish_data.py
|
|
325
330
|
tests/test_publish_import_root.py
|
|
@@ -336,5 +341,6 @@ tests/test_ui.py
|
|
|
336
341
|
tests/test_ui_extras.py
|
|
337
342
|
tests/test_usage_counts.py
|
|
338
343
|
tests/test_users_assign.py
|
|
344
|
+
tests/test_users_info_stats.py
|
|
339
345
|
tests/test_users_list.py
|
|
340
346
|
tests/test_util.py
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""`agents info` reports what the agent has annotated, as the app's table does."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
from typing import Any, cast
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from ellf_cli.commands import agents as agents_cmd
|
|
11
|
+
from ellf_cli.errors import EllfErrors
|
|
12
|
+
from ellf_pam_sdk.models import AgentAnnotationStats, AgentDetail
|
|
13
|
+
|
|
14
|
+
STARTED = datetime(2026, 9, 20, 8, 46, tzinfo=timezone.utc)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def stats_of(monkeypatch):
|
|
19
|
+
"""Build an agent's stats block against a fixed annotation-counts reply."""
|
|
20
|
+
|
|
21
|
+
def _run(counts: Any, tasks: int = 2) -> dict[str, Any]:
|
|
22
|
+
agent_id = uuid4()
|
|
23
|
+
# _stats reads three attributes; a full AgentDetail would need the
|
|
24
|
+
# cluster and recipe plumbing none of them touch.
|
|
25
|
+
agent = cast(
|
|
26
|
+
AgentDetail,
|
|
27
|
+
SimpleNamespace(
|
|
28
|
+
id=agent_id,
|
|
29
|
+
related_tasks=[object()] * tasks,
|
|
30
|
+
last_execution_started=STARTED,
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _counts(_id):
|
|
35
|
+
if isinstance(counts, Exception):
|
|
36
|
+
raise counts
|
|
37
|
+
return counts
|
|
38
|
+
|
|
39
|
+
auth = SimpleNamespace(
|
|
40
|
+
client=SimpleNamespace(agent=SimpleNamespace(annotation_counts=_counts))
|
|
41
|
+
)
|
|
42
|
+
monkeypatch.setattr(agents_cmd, "get_auth_state", lambda: auth)
|
|
43
|
+
return agents_cmd._stats(agent)
|
|
44
|
+
|
|
45
|
+
return _run
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_annotations_and_sessions_join_the_block(stats_of):
|
|
49
|
+
counts = AgentAnnotationStats(agent_id=uuid4(), annotations=450, sessions=3)
|
|
50
|
+
assert stats_of(counts) == {
|
|
51
|
+
"tasks": 2,
|
|
52
|
+
"last_started": STARTED,
|
|
53
|
+
"annotations": 450,
|
|
54
|
+
"sessions": 3,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_unreported_annotations_pass_through_as_none(stats_of):
|
|
59
|
+
"""None means no cluster has reported, which is not a zero."""
|
|
60
|
+
counts = AgentAnnotationStats(agent_id=uuid4(), annotations=None, sessions=0)
|
|
61
|
+
assert stats_of(counts)["annotations"] is None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_counts_failure_leaves_the_rest_of_the_block(stats_of):
|
|
65
|
+
"""Stats are decorative: the counts are lost, not the command."""
|
|
66
|
+
assert stats_of(EllfErrors.EllfError("boom")) == {
|
|
67
|
+
"tasks": 2,
|
|
68
|
+
"last_started": STARTED,
|
|
69
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""`<job> info` keeps `created_by_user` out of the default table."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from ellf_cli.commands import actions as actions_cmd
|
|
9
|
+
from ellf_cli.commands import agents as agents_cmd
|
|
10
|
+
from ellf_cli.commands import services as services_cmd
|
|
11
|
+
from ellf_cli.commands import tasks as tasks_cmd
|
|
12
|
+
|
|
13
|
+
# module, the name it resolves the job with, and what it hides by default
|
|
14
|
+
JOBS = [
|
|
15
|
+
(tasks_cmd, "resolve_task", ["related_actions", "created_by_user"]),
|
|
16
|
+
(actions_cmd, "resolve_action", ["stats", "related_tasks", "created_by_user"]),
|
|
17
|
+
(agents_cmd, "resolve_agent", ["related_tasks", "created_by_user"]),
|
|
18
|
+
(services_cmd, "resolve_service", ["created_by_user"]),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def shown(monkeypatch):
|
|
24
|
+
"""Run one job's `info` and capture what it asks to be printed."""
|
|
25
|
+
|
|
26
|
+
def _run(module: Any, resolver: str, **kwargs: Any) -> dict[str, Any]:
|
|
27
|
+
captured: dict[str, Any] = {}
|
|
28
|
+
job = SimpleNamespace(id="job-id")
|
|
29
|
+
status = SimpleNamespace(state=SimpleNamespace(value="ready"))
|
|
30
|
+
auth = SimpleNamespace(
|
|
31
|
+
broker_client=SimpleNamespace(
|
|
32
|
+
jobs=SimpleNamespace(get_status=lambda _id: status)
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
monkeypatch.setattr(module, "get_auth_state", lambda: auth)
|
|
36
|
+
monkeypatch.setattr(module, resolver, lambda *a, **k: job)
|
|
37
|
+
monkeypatch.setattr(module, "_stats", lambda *a, **k: {})
|
|
38
|
+
monkeypatch.setattr(
|
|
39
|
+
module,
|
|
40
|
+
"print_info_table",
|
|
41
|
+
lambda item, **kw: captured.update(item=item, **kw),
|
|
42
|
+
)
|
|
43
|
+
module.info("whatever", **kwargs)
|
|
44
|
+
return captured
|
|
45
|
+
|
|
46
|
+
return _run
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.parametrize("module,resolver,hidden", JOBS)
|
|
50
|
+
def test_creator_is_hidden_from_the_default_table(shown, module, resolver, hidden):
|
|
51
|
+
assert shown(module, resolver)["table_exclude"] == hidden
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.parametrize("module,resolver,hidden", JOBS)
|
|
55
|
+
def test_naming_your_own_columns_keeps_them(shown, module, resolver, hidden):
|
|
56
|
+
"""table_exclude would otherwise beat the include and empty the table."""
|
|
57
|
+
res = shown(module, resolver, select=["created_by_user"])
|
|
58
|
+
assert res["table_exclude"] is None
|
|
59
|
+
assert res["select"] == ["created_by_user"]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""`projects delete` must be confirmed by hand, and must fail without a TTY."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from ellf_cli.commands import projects as projects_cmd
|
|
9
|
+
from ellf_cli.errors import CLIError
|
|
10
|
+
from ellf_pam_sdk.models import ProjectDetail
|
|
11
|
+
|
|
12
|
+
NAME = "my-project"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _project(name: str = NAME) -> ProjectDetail:
|
|
16
|
+
now = datetime.now(timezone.utc)
|
|
17
|
+
return ProjectDetail(
|
|
18
|
+
id=uuid4(),
|
|
19
|
+
created=now,
|
|
20
|
+
updated=now,
|
|
21
|
+
org_id=uuid4(),
|
|
22
|
+
name=name,
|
|
23
|
+
slug=name,
|
|
24
|
+
description="",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _Delete:
|
|
29
|
+
"""Calls `projects delete` under a chosen set of terminals, and carries
|
|
30
|
+
the record of how far it got -- which the tests that expect a refusal
|
|
31
|
+
have to read off the fixture, since the call raises before returning."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, monkeypatch, reached: list[str]):
|
|
34
|
+
self._monkeypatch = monkeypatch
|
|
35
|
+
self.reached = reached
|
|
36
|
+
|
|
37
|
+
def __call__(self, *, stdin_tty: bool, stdout_tty: bool, typed: str = NAME):
|
|
38
|
+
self._monkeypatch.setattr(
|
|
39
|
+
projects_cmd,
|
|
40
|
+
"isatty",
|
|
41
|
+
lambda stream: (
|
|
42
|
+
stdin_tty if stream is projects_cmd.sys.stdin else stdout_tty
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
self._monkeypatch.setattr("builtins.input", lambda _prompt="": typed)
|
|
46
|
+
return projects_cmd.delete(NAME), self.reached
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.fixture
|
|
50
|
+
def run(monkeypatch):
|
|
51
|
+
"""Call `projects delete`, recording whether it got past the prompt."""
|
|
52
|
+
project = _project()
|
|
53
|
+
reached: list[str] = []
|
|
54
|
+
|
|
55
|
+
monkeypatch.setattr(projects_cmd, "resolve_project", lambda _: project)
|
|
56
|
+
monkeypatch.setattr(
|
|
57
|
+
projects_cmd,
|
|
58
|
+
"_stop_project_jobs",
|
|
59
|
+
lambda *a, **k: reached.append("stopped"),
|
|
60
|
+
)
|
|
61
|
+
monkeypatch.setattr(projects_cmd, "get_auth_state", lambda: _FakeAuth(reached))
|
|
62
|
+
monkeypatch.setattr(projects_cmd, "print_mutation_result", lambda *a, **k: None)
|
|
63
|
+
return _Delete(monkeypatch, reached)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _FakeAuth:
|
|
67
|
+
def __init__(self, reached):
|
|
68
|
+
self.client = self
|
|
69
|
+
self.project = self
|
|
70
|
+
self._reached = reached
|
|
71
|
+
|
|
72
|
+
def delete(self, id): # noqa: A002 - matches the SDK's signature
|
|
73
|
+
self._reached.append("deleted")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_refuses_without_a_terminal(run):
|
|
77
|
+
with pytest.raises(CLIError, match="has to be confirmed via a terminal"):
|
|
78
|
+
run(stdin_tty=False, stdout_tty=True)
|
|
79
|
+
assert run.reached == []
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_refuses_when_stdout_is_piped(run):
|
|
83
|
+
with pytest.raises(CLIError, match="has to be confirmed via a terminal"):
|
|
84
|
+
run(stdin_tty=True, stdout_tty=False)
|
|
85
|
+
assert run.reached == []
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_refuses_a_name_that_does_not_match(run):
|
|
89
|
+
with pytest.raises(CLIError, match="Wrong project name"):
|
|
90
|
+
run(stdin_tty=True, stdout_tty=True, typed="My-Project")
|
|
91
|
+
assert run.reached == []
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_deletes_once_the_name_is_typed(run):
|
|
95
|
+
_, reached = run(stdin_tty=True, stdout_tty=True, typed=NAME)
|
|
96
|
+
assert reached == ["stopped", "deleted"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_accepts_the_name_with_stray_whitespace(run):
|
|
100
|
+
_, reached = run(stdin_tty=True, stdout_tty=True, typed=f" {NAME}\n")
|
|
101
|
+
assert reached == ["stopped", "deleted"]
|
|
@@ -14,7 +14,6 @@ from pydantic import BaseModel
|
|
|
14
14
|
|
|
15
15
|
from ellf_cli.ui import (
|
|
16
16
|
dicts_to_table,
|
|
17
|
-
format_duration,
|
|
18
17
|
is_silent,
|
|
19
18
|
isatty,
|
|
20
19
|
print_args_table,
|
|
@@ -215,21 +214,6 @@ class TestPrintInfoTable:
|
|
|
215
214
|
assert json.loads(capsys.readouterr().out)["age"] == 30
|
|
216
215
|
|
|
217
216
|
|
|
218
|
-
class TestFormatDuration:
|
|
219
|
-
def test_minutes_are_the_finest_unit(self):
|
|
220
|
-
assert format_duration(0) == "0m"
|
|
221
|
-
assert format_duration(59) == "0m"
|
|
222
|
-
assert format_duration(180) == "3m"
|
|
223
|
-
assert format_duration(5400) == "1h 30m"
|
|
224
|
-
|
|
225
|
-
def test_whole_hours_drop_the_minutes(self):
|
|
226
|
-
assert format_duration(7200) == "2h"
|
|
227
|
-
assert format_duration(3600 * 1824 + 24 * 60) == "1824h 24m"
|
|
228
|
-
|
|
229
|
-
def test_never_negative(self):
|
|
230
|
-
assert format_duration(-30) == "0m"
|
|
231
|
-
|
|
232
|
-
|
|
233
217
|
class TestPrintTableWithSelect:
|
|
234
218
|
def test_basic(self, capsys):
|
|
235
219
|
items = [_SampleModel(name="Alice", age=30)]
|