ellf-cli 17.0.207__tar.gz → 18.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. {ellf_cli-17.0.207/ellf_cli.egg-info → ellf_cli-18.0.2}/PKG-INFO +1 -1
  2. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/README.md +2 -2
  3. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/_recipe_subcommand.py +1 -1
  4. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/agents.py +14 -0
  5. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/import_export.py +110 -14
  6. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/projects.py +4 -1
  7. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/publish_code.py +104 -24
  8. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/tasks.py +7 -3
  9. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf.json +8 -6
  10. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/messages.py +7 -3
  11. ellf_cli-18.0.2/ellf_cli/version/17.0.207_2026-09-11-15-26-02_major_3f1b3ed7-eaac-41f5-b740-0af3331595d9 +3 -0
  12. ellf_cli-18.0.2/ellf_cli/version/18.0.0_2026-09-12-10-01-10_patch_8995783a-a9fa-4c4e-90b3-6386a4c481eb +3 -0
  13. ellf_cli-18.0.2/ellf_cli/version/18.0.1_2026-09-12-17-33-30_patch_f6ea2c99-a715-4c4d-876a-23ed8c62a83d +3 -0
  14. {ellf_cli-17.0.207 → ellf_cli-18.0.2/ellf_cli.egg-info}/PKG-INFO +1 -1
  15. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli.egg-info/SOURCES.txt +5 -0
  16. ellf_cli-18.0.2/tests/test_import_export.py +260 -0
  17. ellf_cli-18.0.2/tests/test_publish_import_root.py +120 -0
  18. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/LICENSE +0 -0
  19. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/MANIFEST.in +0 -0
  20. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/__init__.py +0 -0
  21. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/__main__.py +0 -0
  22. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/about.json +0 -0
  23. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/about.py +0 -0
  24. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/appdirs.py +0 -0
  25. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/auth.py +0 -0
  26. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/cli.py +0 -0
  27. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/cloud/__init__.py +0 -0
  28. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/cloud/gcp.py +0 -0
  29. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/cluster_config.py +0 -0
  30. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/__init__.py +0 -0
  31. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/_cluster_select.py +0 -0
  32. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/_org_select.py +0 -0
  33. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/_project_scope.py +0 -0
  34. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/_recipe_file.py +0 -0
  35. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/_state.py +0 -0
  36. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/_usage.py +0 -0
  37. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/actions.py +0 -0
  38. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/assets.py +0 -0
  39. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/auth.py +0 -0
  40. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/clusters.py +0 -0
  41. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/config.py +0 -0
  42. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/datasets.py +0 -0
  43. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/files/__init__.py +0 -0
  44. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/files/cp.py +0 -0
  45. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/files/ls.py +0 -0
  46. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/files/rm.py +0 -0
  47. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/files/rsync.py +0 -0
  48. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/files/stats.py +0 -0
  49. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/general.py +0 -0
  50. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/__init__.py +0 -0
  51. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/_config_io.py +0 -0
  52. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/_helpers.py +0 -0
  53. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/configure.py +0 -0
  54. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/deploy.py +0 -0
  55. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/init.py +0 -0
  56. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/init_values.py +0 -0
  57. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/provision.py +0 -0
  58. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/register.py +0 -0
  59. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/setup.py +0 -0
  60. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/start.py +0 -0
  61. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/terraform.py +0 -0
  62. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/infra/tls.py +0 -0
  63. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/jobs.py +0 -0
  64. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/packages.py +0 -0
  65. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/paths.py +0 -0
  66. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/plans.py +0 -0
  67. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/publish_data.py +0 -0
  68. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/recipes.py +0 -0
  69. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/secrets.py +0 -0
  70. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/services.py +0 -0
  71. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/support.py +0 -0
  72. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/todos.py +0 -0
  73. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/commands/users.py +0 -0
  74. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/config.py +0 -0
  75. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/.claude-plugin/plugin.json +0 -0
  76. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/.gitignore +0 -0
  77. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skill_variants.json +0 -0
  78. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-annotate.assistant/SKILL.md +0 -0
  79. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-annotate.assistant/references/annotation_audit.md +0 -0
  80. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-annotate.assistant/references/builtin_ellf_annotation_recipes.md +0 -0
  81. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/SKILL.md +0 -0
  82. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/references/annotation_audit.md +0 -0
  83. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/references/builtin_ellf_annotation_recipes.md +0 -0
  84. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-annotate.coding/references/builtin_prodigy_recipes.md +0 -0
  85. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-ask/SKILL.md +0 -0
  86. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-handoff/SKILL.md +0 -0
  87. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-monitor.assistant/SKILL.md +0 -0
  88. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-monitor.assistant/references/annotation_metrics.md +0 -0
  89. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-monitor.assistant/references/training_monitoring.md +0 -0
  90. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/SKILL.md +0 -0
  91. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/references/annotation_metrics.md +0 -0
  92. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/references/training_monitoring.md +0 -0
  93. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-monitor.coding/scripts/check_training.py +0 -0
  94. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-ops.assistant/SKILL.md +0 -0
  95. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-ops.coding/SKILL.md +0 -0
  96. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-ops.coding/references/data_infra_cli.md +0 -0
  97. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-ops.coding/scripts/run_job.py +0 -0
  98. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-patterns/SKILL.md +0 -0
  99. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-patterns/references/pattern_strategies.md +0 -0
  100. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/SKILL.md +0 -0
  101. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_action_recipe.py +0 -0
  102. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_agent_recipe.py +0 -0
  103. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_blocks_ui.py +0 -0
  104. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_correct.py +0 -0
  105. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_custom_ui.py +0 -0
  106. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_manual.py +0 -0
  107. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_pages_ui.py +0 -0
  108. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_routing.py +0 -0
  109. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_task_recipe.py +0 -0
  110. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/assets/templates/template_teach.py +0 -0
  111. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/builtin_recipes.md +0 -0
  112. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/ellf_recipe_sdk.md +0 -0
  113. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/lint_recipe.py +0 -0
  114. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/prodigy_recipe_api.md +0 -0
  115. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-prodigy/references/template_index.md +0 -0
  116. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.assistant/SKILL.md +0 -0
  117. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.assistant/references/consulting_patterns.md +0 -0
  118. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.assistant/references/explosion_strategy.md +0 -0
  119. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.assistant/references/prodigy_llm_bot.md +0 -0
  120. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.coding/SKILL.md +0 -0
  121. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.coding/references/consulting_patterns.md +0 -0
  122. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.coding/references/explosion_strategy.md +0 -0
  123. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-project.coding/references/prodigy_llm_bot.md +0 -0
  124. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-support.assistant/SKILL.md +0 -0
  125. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-support.coding/SKILL.md +0 -0
  126. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-todo/SKILL.md +0 -0
  127. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.assistant/SKILL.md +0 -0
  128. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/diagnostics.md +0 -0
  129. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/evaluation_guide.md +0 -0
  130. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/model_selection.md +0 -0
  131. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/training_paradigms.md +0 -0
  132. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.assistant/references/workflow.md +0 -0
  133. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/SKILL.md +0 -0
  134. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/config_advanced.md +0 -0
  135. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/config_architectures.md +0 -0
  136. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/config_training.md +0 -0
  137. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/diagnostics.md +0 -0
  138. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/evaluation_guide.md +0 -0
  139. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/experiment_patterns.md +0 -0
  140. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/model_selection.md +0 -0
  141. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/training_paradigms.md +0 -0
  142. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/training_troubleshooting.md +0 -0
  143. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/references/workflow.md +0 -0
  144. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ellf_skills/skills/ellf-train.coding/scripts/ellf_logger.py +0 -0
  145. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/errors.py +0 -0
  146. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/helm.py +0 -0
  147. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/interactivity.py +0 -0
  148. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/key_pair.py +0 -0
  149. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/main.py +0 -0
  150. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/pypi_auth.py +0 -0
  151. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/query.py +0 -0
  152. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/cookiecutter.json +0 -0
  153. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/.gitignore +0 -0
  154. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/README.md.tmpl +0 -0
  155. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/requirements-dev.in +0 -0
  156. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/requirements.in +0 -0
  157. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/setup.py.tmpl +0 -0
  158. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/__init__.py +0 -0
  159. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/about.py +0 -0
  160. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/recipes/__init__.py +0 -0
  161. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/recipes_cookiecutter/{{cookiecutter.package_dir}}/{{cookiecutter.package_name}}/recipes/example_task.py +0 -0
  162. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/testing/__init__.py +0 -0
  163. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ty.py +0 -0
  164. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/ui.py +0 -0
  165. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/url.py +0 -0
  166. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/util.py +0 -0
  167. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.114_2026-08-19-08-36-57__1a4e37c1-471e-4a81-b4d5-1ffe7dec3a61 +0 -0
  168. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.114_2026-08-19-09-25-21_patch_33ff1e26-0e5e-46d8-8052-ab1d032e8b6c +0 -0
  169. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.115_2026-08-19-09-27-13_patch_4ac728c3-0342-4a36-adbc-e28d2d99b1ba +0 -0
  170. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.116_2026-08-19-09-30-47_patch_c706fef6-6813-4183-b268-12e7b00d7d0b +0 -0
  171. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.117_2026-08-19-09-39-14_patch_c6b0495f-2f88-4aa8-9ebf-0a235b4c407e +0 -0
  172. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.118_2026-08-19-09-52-00_patch_b395655a-8d78-4344-9266-6901c20f6517 +0 -0
  173. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.119_2026-08-19-10-22-38_patch_8f95c49d-aedc-41b8-816d-ccb95f048831 +0 -0
  174. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.120_2026-08-19-10-26-58_patch_7fbf35e6-cc01-4b75-8c9a-f5d054bcaf93 +0 -0
  175. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.121_2026-08-19-10-42-02_patch_31a81b02-a886-4dea-84e0-67cc6ca412d5 +0 -0
  176. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.122_2026-08-19-11-16-49_patch_1c4d7307-48d4-493e-936d-80af799d644b +0 -0
  177. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.123_2026-08-19-11-47-32_patch_d3d78d04-9989-45b1-91b1-93b571d8814a +0 -0
  178. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.124_2026-08-20-08-03-17_patch_57f4c389-2d19-41ce-aca4-13b61c62f387 +0 -0
  179. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.125_2026-08-20-08-25-05_patch_7f368148-0d1c-4525-bbdf-c6d257ad861a +0 -0
  180. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.126_2026-08-20-14-51-36_patch_a99abcc8-3371-4c63-a759-bb7ea5b32432 +0 -0
  181. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.127_2026-08-21-05-48-58_patch_34b4ccee-7596-4f57-93e9-774973cca58e +0 -0
  182. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.128_2026-08-21-10-47-20_patch_693112b9-927c-4a8f-8553-b06eef86079d +0 -0
  183. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.129_2026-08-21-11-16-54_patch_7a981666-01d9-4d1b-9ba3-2129744e3bd5 +0 -0
  184. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.130_2026-08-21-13-09-36_patch_b7ad1a76-ce70-4d70-9d1a-7c414eba5bb4 +0 -0
  185. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.131_2026-08-21-14-31-10_patch_9128ef66-193e-4caf-8710-afd86128e638 +0 -0
  186. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.132_2026-08-24-08-59-34_patch_c600fc14-86a5-460f-9c4e-f0177056f756 +0 -0
  187. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.133_2026-08-24-10-28-29_patch_b6ce3e32-9d7b-48ac-981a-270c22c180dd +0 -0
  188. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.134_2026-08-24-11-00-51_patch_a4fc0484-4a64-46a2-94f8-aa805254c7b0 +0 -0
  189. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.135_2026-08-24-12-12-41_patch_a870a56f-9a65-49eb-92da-f572b26667d8 +0 -0
  190. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.136_2026-08-24-12-42-46_patch_9b399356-6482-4291-8da7-e748a99c4b53 +0 -0
  191. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.137_2026-08-24-12-53-57_patch_f7979348-1910-40cd-a535-90299eca84ee +0 -0
  192. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.138_2026-08-24-14-36-30_patch_ed0f8750-f663-498b-85e7-0d3f2213fcfe +0 -0
  193. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.139_2026-08-25-08-37-26_patch_3ba87e19-902b-4f76-b64c-90e5c0ca1fc6 +0 -0
  194. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.140_2026-08-25-09-52-45_patch_abc830f2-df15-4385-b797-cdaf72c10752 +0 -0
  195. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.141_2026-08-25-11-37-20_patch_1d9ec01e-06f6-4c8e-b095-af17d5b62b00 +0 -0
  196. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.142_2026-08-26-07-38-49_patch_3dd23e36-e7df-494c-986f-a412861a8d40 +0 -0
  197. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.143_2026-08-26-09-32-32_patch_7b7ebd34-de27-4b73-98a7-3e1501e16cd5 +0 -0
  198. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.144_2026-08-26-10-56-43_patch_166064f2-2208-4c9b-9e25-25dc4cd32084 +0 -0
  199. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.145_2026-08-26-11-37-29_patch_b997f3ee-d7d3-4408-9f2f-c8b6d08bdb3b +0 -0
  200. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.146_2026-08-26-12-15-20_patch_afbbb893-25db-468c-81db-2663ca373068 +0 -0
  201. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.147_2026-08-26-14-10-50_patch_fd753b1f-2fa1-4ddb-a9e7-de690da85254 +0 -0
  202. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.148_2026-08-27-09-08-35_patch_23814c0b-7f77-489c-b4a8-ceadd18146e3 +0 -0
  203. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.149_2026-08-27-09-40-17_patch_8879d72e-a3d8-4abc-a16a-d9dcf75f0fa1 +0 -0
  204. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.150_2026-08-27-10-57-25_patch_afbe60ed-3e23-4d88-be3c-f800d7002a7d +0 -0
  205. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.151_2026-08-28-09-02-46_patch_1d4ca23b-aeae-4ea8-96b1-8cf57371f29d +0 -0
  206. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.152_2026-08-28-15-37-24_patch_c9d0bdc1-a7e1-432b-92b9-92dc76fe0c78 +0 -0
  207. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.153_2026-08-29-09-03-27_patch_7531573a-1b33-4092-aa9c-0e65f47b201f +0 -0
  208. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.154_2026-08-29-11-56-35_patch_919f5787-39c8-4dc1-ae6a-f27c28380f21 +0 -0
  209. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.155_2026-08-29-15-15-59_patch_822f9ed8-c366-4e5f-9791-dee8467cf3ce +0 -0
  210. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.156_2026-08-29-16-19-01_patch_3ac2bf36-20f7-4c9f-9fdb-295d4466d59e +0 -0
  211. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.157_2026-08-30-17-32-17_patch_4302354c-c9c5-4be7-acd3-542872a748d8 +0 -0
  212. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.158_2026-08-31-09-28-33_patch_3523774f-c25d-4102-8c1a-92fff046a48d +0 -0
  213. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.159_2026-08-31-10-31-01_patch_fe3e6290-2553-4154-a0a0-8b8a418280ed +0 -0
  214. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.160_2026-08-31-11-23-39_patch_e701a085-3d23-4212-b0af-73b1d419a4c1 +0 -0
  215. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.161_2026-08-31-12-41-01_patch_074d7bde-c493-44db-bc15-e81fd91e2921 +0 -0
  216. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.162_2026-08-31-13-58-06_patch_84db1d60-4280-433b-bd3a-22e5611cd315 +0 -0
  217. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.163_2026-08-31-15-25-54_patch_1931c5ba-73df-48cb-8dcb-555ace0af267 +0 -0
  218. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.163_2026-08-31-15-44-37_patch_3962e805-2a92-494a-a87b-b6e6d66c40a0 +0 -0
  219. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.165_2026-08-31-19-48-59_patch_86e5b923-2b99-4b86-b542-469b197614be +0 -0
  220. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.166_2026-09-01-07-39-57_patch_c344892f-d542-4e7b-8f27-02365a411e15 +0 -0
  221. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.167_2026-09-01-08-21-53_patch_e199d419-282a-4dbb-8756-cf98b7037e2b +0 -0
  222. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.168_2026-09-01-09-12-31_patch_235c9fe0-da15-431d-bb56-01e2b9b15f4b +0 -0
  223. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.169_2026-09-01-11-54-19_patch_98b617ab-d130-42b5-93f2-19cae56d4c4c +0 -0
  224. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.170_2026-09-02-09-27-28_patch_e448d2e6-b7ec-4235-96bf-0173b3623c87 +0 -0
  225. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.171_2026-09-02-12-52-44_patch_e6a241c2-1d62-482c-a9eb-9e3373395c50 +0 -0
  226. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.172_2026-09-04-08-47-04_patch_daae079f-c104-4fcd-bb57-7bff68e80ad9 +0 -0
  227. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.173_2026-09-04-12-34-42_patch_399c4cbd-0c28-4dfd-a4ec-e569420a36e4 +0 -0
  228. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.174_2026-09-04-13-08-48_patch_f5cfe3d5-808e-45e4-bafc-00d0554838ee +0 -0
  229. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.175_2026-09-04-17-20-46_patch_1ef542fa-5d8e-4d90-aa15-9fc4cea3dfec +0 -0
  230. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.176_2026-09-05-08-25-05_patch_ced5521f-f9da-4640-a921-95fcb5cd2e6d +0 -0
  231. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.177_2026-09-05-09-00-16_patch_803c1ad4-a9af-4953-92c5-d882dec03ea2 +0 -0
  232. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.178_2026-09-05-09-30-34_patch_012f6259-2e3f-4591-9806-1920fd06cb1f +0 -0
  233. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.179_2026-09-05-09-33-43_patch_f4bdf28c-d584-47a5-b699-8c883330162c +0 -0
  234. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.180_2026-09-05-12-02-21_patch_34a14b01-d22d-44a8-ac63-be0370a61422 +0 -0
  235. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.181_2026-09-05-12-47-46_patch_56b18867-99f6-4d5c-8215-7b9d60d34010 +0 -0
  236. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.182_2026-09-05-13-35-18_patch_61b7d4d1-73c8-4603-9b0c-593e7405587f +0 -0
  237. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.183_2026-09-06-07-27-09_patch_d76331b2-e3a3-4c0b-8412-ecd131b6ac02 +0 -0
  238. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.184_2026-09-06-10-21-47_patch_7680b7a1-49f0-4eef-b12e-4c854407448c +0 -0
  239. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.185_2026-09-06-10-30-40_patch_e56c2183-77f8-44ef-a92e-7377ea6ce8c1 +0 -0
  240. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.186_2026-09-07-10-00-51_patch_67de5683-d208-4bc0-8fb6-7677377908e7 +0 -0
  241. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.187_2026-09-07-10-18-13_patch_2086cee2-276b-48f7-80ce-6a17397b9541 +0 -0
  242. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.188_2026-09-07-12-51-28_patch_d6590822-068f-4a19-814d-34bbf204ba6e +0 -0
  243. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.189_2026-09-07-12-55-23_patch_7e0fb04b-4d40-4490-974f-c74ba2ae55a1 +0 -0
  244. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.190_2026-09-08-08-58-03_patch_15e3ac1e-4475-4272-bdd8-d00bb0db8f17 +0 -0
  245. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.191_2026-09-08-09-55-58_patch_3d350e50-ee53-4282-bc75-d2251142c46e +0 -0
  246. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.192_2026-09-08-11-12-21_patch_0733223b-1ae2-4b5b-897f-dfc57574ff83 +0 -0
  247. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.193_2026-09-08-21-48-17_patch_8d07d4e6-3555-4720-85c9-9452570fae2d +0 -0
  248. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.194_2026-09-09-07-53-43_patch_69559429-aa42-4b62-9bd7-cdfded1c00d0 +0 -0
  249. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.195_2026-09-09-09-55-40_patch_bbf67f1d-3b9a-43a2-b1bc-22df3fed8a34 +0 -0
  250. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.196_2026-09-09-09-59-47_patch_70d76812-7b3c-4192-ad49-fcaccc29b0da +0 -0
  251. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.197_2026-09-09-13-06-06_patch_d455be0b-5421-492a-b952-fab0fd8110f8 +0 -0
  252. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.198_2026-09-09-13-14-34_patch_267f54e5-311e-435e-882f-8e19191325ea +0 -0
  253. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.199_2026-09-09-22-24-18_patch_81e3e453-ca0e-42fa-9c21-8f94d43c05f2 +0 -0
  254. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.200_2026-09-10-07-19-50_patch_911714fa-1fa3-4165-bfe6-d10c650bf5bf +0 -0
  255. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.201_2026-09-10-10-14-19_patch_864015f2-b0b0-4563-a1c8-155629cb9126 +0 -0
  256. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.202_2026-09-10-11-00-05_patch_18270ecf-e770-4e69-8bcf-e5143c39929b +0 -0
  257. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.203_2026-09-10-11-10-41_patch_ff273ab1-44f3-4bc0-b184-3396e943df04 +0 -0
  258. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.204_2026-09-10-13-02-02_patch_d4ee41e2-d13c-4941-8605-3f1a42b56b22 +0 -0
  259. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.205_2026-09-10-13-57-50_patch_284df67e-5957-4b0c-bc64-5581db6ddecc +0 -0
  260. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/17.0.206_2026-09-11-10-18-50_patch_1ac7e410-0e62-4eef-bbe4-ebf93c9d0869 +0 -0
  261. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/__init__.py +0 -0
  262. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli/version/read_version.py +0 -0
  263. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli.egg-info/dependency_links.txt +0 -0
  264. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli.egg-info/entry_points.txt +0 -0
  265. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli.egg-info/not-zip-safe +0 -0
  266. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli.egg-info/requires.txt +0 -0
  267. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/ellf_cli.egg-info/top_level.txt +0 -0
  268. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/pyproject.toml +0 -0
  269. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/setup.cfg +0 -0
  270. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/setup.py +0 -0
  271. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_appdirs.py +0 -0
  272. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_auth.py +0 -0
  273. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_config.py +0 -0
  274. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_errors.py +0 -0
  275. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_files_cp.py +0 -0
  276. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_files_cp_helpers.py +0 -0
  277. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_helm.py +0 -0
  278. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_info.py +0 -0
  279. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_config_io.py +0 -0
  280. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_configure.py +0 -0
  281. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_deploy_node_labels.py +0 -0
  282. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_get_terraform.py +0 -0
  283. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_init.py +0 -0
  284. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_provision.py +0 -0
  285. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_start_fixes.py +0 -0
  286. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_start_validation.py +0 -0
  287. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_state_bucket.py +0 -0
  288. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_infra_terraform_init.py +0 -0
  289. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_invalid_secrets.py +0 -0
  290. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_job_list_project_filter.py +0 -0
  291. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_jobs_list_unmatched.py +0 -0
  292. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_key_pair.py +0 -0
  293. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_login.py +0 -0
  294. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_logout.py +0 -0
  295. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_main.py +0 -0
  296. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_org_select.py +0 -0
  297. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_plans.py +0 -0
  298. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_projects.py +0 -0
  299. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_publish_build_poll.py +0 -0
  300. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_publish_data.py +0 -0
  301. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_publish_requirements.py +0 -0
  302. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_pypi_auth.py +0 -0
  303. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_query.py +0 -0
  304. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_recipe_file.py +0 -0
  305. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_recipes.py +0 -0
  306. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_state.py +0 -0
  307. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_support.py +0 -0
  308. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_todos.py +0 -0
  309. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_ty.py +0 -0
  310. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_ui.py +0 -0
  311. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_ui_extras.py +0 -0
  312. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_usage_counts.py +0 -0
  313. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_users_assign.py +0 -0
  314. {ellf_cli-17.0.207 → ellf_cli-18.0.2}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ellf-cli
3
- Version: 17.0.207
3
+ Version: 18.0.2
4
4
  Summary: Ellf Command Line Interface
5
5
  Home-page: https://prodi.gy
6
6
  Author: ExplosionAI GmbH
@@ -1631,11 +1631,11 @@ Save the state of the current app JSON file. If an assets directory is provided,
1631
1631
  | --- | --- | --- | --- |
1632
1632
  | `output` | `Path` | JSON output path for data | |
1633
1633
  | `assets_dir` | `Path` | Local directory to download assets to | `None` |
1634
- | `--include` | `str` | Comma-separated items to include | `['tasks', 'actions', 'assets', 'datasets', 'paths']` |
1634
+ | `--include` | `str` | Comma-separated items to include: tasks, actions, agents, services, assets, datasets, paths | `['tasks', 'actions', 'agents', 'services', 'assets', 'datasets', 'paths']` |
1635
1635
 
1636
1636
  ### `ellf import`
1637
1637
 
1638
- Populate Ellf with data for projects, tasks, actions, assets and paths.
1638
+ Populate Ellf with data for projects, tasks, actions, agents, services, assets, datasets and paths.
1639
1639
 
1640
1640
  | Argument | Type | Description | Default |
1641
1641
  | --- | --- | --- | --- |
@@ -133,7 +133,7 @@ def create_from_recipe(
133
133
  if object_validation.errors:
134
134
  errors = ["\n".join(err) for err in object_validation.errors.values()]
135
135
  noun = _get_noun(recipe)
136
- err = Messages.E044.format(noun=noun, name=task_or_action_name)
136
+ err = Messages.E191.format(noun=noun, name=task_or_action_name)
137
137
  raise CLIError(err, "\n".join(errors))
138
138
  _validate_plan(recipe, plan, object_validation)
139
139
  jt = getattr(recipe, "job_type", None)
@@ -154,6 +154,20 @@ def assign(
154
154
  auth = get_auth_state()
155
155
  agent = resolve_agent(name_or_id, cluster_id=cluster_id, project_id=project_id)
156
156
  task_obj = resolve_task(task, cluster_id=cluster_id, project_id=project_id)
157
+ # The API appends the agent to the task's annotator group without checking
158
+ # membership, so a repeat assignment fails on the database rather than
159
+ # being a no-op. Check first, so re-running a setup script is safe.
160
+ if task_obj.id in agent.related_tasks:
161
+ print_mutation_result(
162
+ {
163
+ "agent_id": str(agent.id),
164
+ "task_id": str(task_obj.id),
165
+ "status": "already_assigned",
166
+ },
167
+ Messages.T066.format(name=agent.name, task=task_obj.name),
168
+ as_json=as_json,
169
+ )
170
+ return
157
171
  auth.client.task.add_agent(task_id=task_obj.id, agent_id=agent.id)
158
172
  print_mutation_result(
159
173
  {"agent_id": str(agent.id), "task_id": str(task_obj.id), "status": "assigned"},
@@ -26,8 +26,15 @@ def uuid7() -> uuid.UUID:
26
26
 
27
27
 
28
28
  class ProjectData(models.ProjectSummary):
29
+ # Each job is the argument list of its `create` command, starting with the
30
+ # recipe name, e.g. ["ner", "--name", "my-task", "--dataset", "my-ds"].
29
31
  tasks: Sequence[list[str]] = tuple()
30
32
  actions: Sequence[list[str]] = tuple()
33
+ agents: Sequence[list[str]] = tuple()
34
+ services: Sequence[list[str]] = tuple()
35
+ # [agent name, task name] pairs. Agents only count as annotators on tasks
36
+ # they are assigned to, so this is part of the project's shape.
37
+ assignments: Sequence[list[str]] = tuple()
31
38
  # Setting some defaults for creation
32
39
  created: datetime = datetime.now()
33
40
  updated: datetime = datetime.now()
@@ -69,9 +76,23 @@ class RecipeData(BaseModel):
69
76
  class Data(BaseModel):
70
77
  timestamp: datetime = datetime.now()
71
78
  project: dict[str, ProjectData]
72
- asset: dict[str, AssetData]
73
- dataset: dict[str, DatasetData]
74
- path: dict[str, PathData]
79
+ asset: dict[str, AssetData] = {}
80
+ dataset: dict[str, DatasetData] = {}
81
+ path: dict[str, PathData] = {}
82
+
83
+
84
+ IncludeItem = Literal[
85
+ "tasks", "actions", "agents", "services", "assets", "datasets", "paths"
86
+ ]
87
+ ALL_INCLUDES: list[IncludeItem] = [
88
+ "tasks",
89
+ "actions",
90
+ "agents",
91
+ "services",
92
+ "assets",
93
+ "datasets",
94
+ "paths",
95
+ ]
75
96
 
76
97
 
77
98
  @cli.command(
@@ -84,13 +105,7 @@ def export_data(
84
105
  # fmt: off
85
106
  output: Path,
86
107
  assets_dir: Optional[Path] = None,
87
- include: list[Literal["tasks", "actions", "assets", "datasets", "paths"]] = [
88
- "tasks",
89
- "actions",
90
- "assets",
91
- "datasets",
92
- "paths",
93
- ],
108
+ include: list[IncludeItem] = ALL_INCLUDES,
94
109
  # fmt: on
95
110
  ) -> None:
96
111
  """
@@ -108,26 +123,50 @@ def export_data(
108
123
  datasets = ptc.datasets.list()
109
124
  tasks = ptc.tasks.list()
110
125
  actions = ptc.actions.list()
126
+ agents = ptc.agents.list()
127
+ services = ptc.services.list()
111
128
  paths = ptc.paths.list()
112
129
  if "paths" in include:
113
130
  for path in paths:
114
131
  result.path[path.name] = PathData(**path.model_dump())
115
132
  tasks_by_project = defaultdict(list)
116
133
  actions_by_project = defaultdict(list)
134
+ agents_by_project = defaultdict(list)
135
+ services_by_project = defaultdict(list)
117
136
  if "tasks" in include:
118
137
  for task in tasks:
119
138
  tasks_by_project[task.project_id].append(task)
120
139
  if "actions" in include:
121
140
  for action in actions:
122
141
  actions_by_project[action.project_id].append(action)
142
+ if "agents" in include:
143
+ for agent in agents:
144
+ agents_by_project[agent.project_id].append(agent)
145
+ if "services" in include:
146
+ for service in services:
147
+ services_by_project[service.project_id].append(service)
148
+ task_names = {task.id: task.name for task in tasks}
123
149
  for project in projects:
124
150
  info = ptc.projects.info(project.id)
125
151
  project_tasks = tasks_by_project.get(project.id, [])
126
152
  project_actions = actions_by_project.get(project.id, [])
153
+ project_agents = agents_by_project.get(project.id, [])
154
+ project_services = services_by_project.get(project.id, [])
155
+ assignments: list[list[str]] = []
156
+ # Assignments are only meaningful when both ends are exported.
157
+ if "tasks" in include:
158
+ for agent in project_agents:
159
+ detail = ptc.agents.info(agent.id)
160
+ for task_id in detail.related_tasks:
161
+ if task_id in task_names:
162
+ assignments.append([agent.name, task_names[task_id]])
127
163
  result.project[info.name] = ProjectData(
128
164
  **info.model_dump(),
129
165
  tasks=[_get_cmd(t.cli_command) for t in project_tasks],
130
166
  actions=[_get_cmd(a.cli_command) for a in project_actions],
167
+ agents=[_get_cmd(a.cli_command) for a in project_agents],
168
+ services=[_get_cmd(s.cli_command) for s in project_services],
169
+ assignments=assignments,
131
170
  )
132
171
  if "assets" in include:
133
172
  assets_map = {}
@@ -154,6 +193,8 @@ def export_data(
154
193
  n_projects=len(result.project),
155
194
  n_tasks=sum(len(t) for t in tasks_by_project.values()),
156
195
  n_actions=sum(len(a) for a in actions_by_project.values()),
196
+ n_agents=sum(len(a) for a in agents_by_project.values()),
197
+ n_services=sum(len(s) for s in services_by_project.values()),
157
198
  n_assets=len(result.asset),
158
199
  n_datasets=len(result.dataset),
159
200
  n_paths=len(result.path),
@@ -171,7 +212,8 @@ def export_data(
171
212
  )
172
213
  def import_data(data: Union[Path, Data], strict: bool = False) -> None:
173
214
  """
174
- Populate Ellf with data for projects, tasks, actions, assets and paths.
215
+ Populate Ellf with data for projects, tasks, actions, agents, services,
216
+ assets, datasets and paths.
175
217
  """
176
218
  _import_data(data, strict=strict)
177
219
 
@@ -208,7 +250,7 @@ def _import_data(
208
250
  msg.divider(Messages.T042)
209
251
  for asset in data.asset.values():
210
252
  ptc.files.cp(
211
- asset.path,
253
+ _asset_source(asset.path),
212
254
  asset.dest,
213
255
  recurse=True,
214
256
  make_dirs=True,
@@ -223,11 +265,17 @@ def _import_data(
223
265
  meta=json.dumps(asset.meta),
224
266
  exists_ok=not strict,
225
267
  )
268
+ # Datasets come before jobs: a task may read a dataset that no other job
269
+ # creates, and creating a job that names a missing input dataset fails.
270
+ if data.dataset:
271
+ msg.divider(Messages.T064)
272
+ for dataset in data.dataset.values():
273
+ ptc.datasets.create(dataset.name, dataset.kind, exists_ok=not strict)
226
274
 
227
275
  if data.project:
228
276
  msg.divider(Messages.T044)
229
277
  for project in data.project.values():
230
- if project.tasks or project.actions:
278
+ if project.tasks or project.actions or project.agents or project.services:
231
279
  ptc.config.project(project.name)
232
280
  for task in project.tasks:
233
281
  ptc.tasks.create(_extra=task, exists_ok=not strict, no_start=True)
@@ -235,7 +283,55 @@ def _import_data(
235
283
  ptc.actions.create(
236
284
  _extra=action, exists_ok=not strict, no_start=True
237
285
  )
286
+ for agent in project.agents:
287
+ ptc.agents.create(_extra=agent, exists_ok=not strict, no_start=True)
288
+ for service in project.services:
289
+ ptc.services.create(
290
+ _extra=service, exists_ok=not strict, no_start=True
291
+ )
292
+ if any(project.assignments for project in data.project.values()):
293
+ msg.divider(Messages.T065)
294
+ for project in data.project.values():
295
+ if project.assignments:
296
+ ptc.config.project(project.name)
297
+ for agent_name, task_name in project.assignments:
298
+ ptc.agents.assign(agent_name, task=task_name)
299
+
300
+
301
+ def _asset_source(path: str) -> str:
302
+ """Make a local directory source copy its contents, not itself.
303
+
304
+ ``files cp`` follows ``cp`` semantics: ``cp src dest`` nests ``src`` under
305
+ ``dest`` once ``dest`` exists, so the second run of an import would leave
306
+ ``dest/src/...`` beside the files from the first run. A trailing slash
307
+ means "the contents of src", which lands in ``dest`` on every run.
308
+ """
309
+ from ellf_cli.util import is_local
310
+
311
+ if is_local(path) and Path(path).is_dir() and not path.endswith("/"):
312
+ return path + "/"
313
+ return path
238
314
 
239
315
 
240
316
  def _get_cmd(command: str) -> list[str]:
241
- return shlex.split(command)[3:-2]
317
+ """Turn a job's ``ellf <noun> create ...`` string into import arguments.
318
+
319
+ Drops the ``ellf <noun> create`` prefix and the ``--project-id`` flag: the
320
+ import sets the default project before creating each project's jobs, and
321
+ a slug from the exporting deployment need not exist on the importing one.
322
+ Everything else, including ``--worker-class``, is kept.
323
+ """
324
+ tokens = shlex.split(command)[3:]
325
+ args: list[str] = []
326
+ skip_next = False
327
+ for token in tokens:
328
+ if skip_next:
329
+ skip_next = False
330
+ continue
331
+ if token == "--project-id":
332
+ skip_next = True
333
+ continue
334
+ if token.startswith("--project-id="):
335
+ continue
336
+ args.append(token)
337
+ return args
@@ -4,6 +4,7 @@ from collections.abc import Sequence
4
4
  from typing import Any, 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
 
@@ -75,7 +76,9 @@ def _stats(project_id: UUID) -> dict[str, Any] | None:
75
76
  client = get_auth_state().client
76
77
  try:
77
78
  stats = client.project.stats(project_id)
78
- except (EllfErrors.EllfError, *HTTPXErrors):
79
+ except (EllfErrors.EllfError, ValidationError, *HTTPXErrors):
80
+ # Stats are decorative. A PAM release whose stats schema differs from
81
+ # this CLI's (older, or newer) should not take down `info`.
79
82
  return None
80
83
  return {
81
84
  "tasks": stats.tasks_count,
@@ -6,12 +6,14 @@ import json
6
6
  import os
7
7
  import subprocess
8
8
  import tarfile
9
+ import tempfile
9
10
  import time
10
11
  import zipfile
12
+ from contextlib import contextmanager
11
13
  from dataclasses import dataclass
12
14
  from importlib.metadata import PackageNotFoundError, distribution
13
15
  from pathlib import Path
14
- from typing import Optional, Union
16
+ from typing import Iterator, Optional, Union
15
17
  from uuid import UUID
16
18
 
17
19
  import httpx
@@ -44,7 +46,7 @@ class DistPackage:
44
46
 
45
47
  def __init__(self, path: Path) -> None:
46
48
  if path.suffix == ".whl":
47
- name, version, _, _2 = parse_wheel_filename(path.name)
49
+ name, version, _, _ = parse_wheel_filename(path.name)
48
50
  else:
49
51
  name, version = parse_sdist_filename(path.name)
50
52
 
@@ -93,7 +95,7 @@ class ModulePackage:
93
95
  def __init__(self, name: str, version_arg: str | None = None) -> None:
94
96
  self.name = name
95
97
  if version_arg is None:
96
- utc_now = datetime.datetime.utcnow()
98
+ utc_now = datetime.datetime.now(datetime.timezone.utc)
97
99
  self.version = f"{utc_now.year}.{utc_now.month}.{utc_now.day}-{utc_now.second + 60 * utc_now.minute + 60 * 60 * utc_now.hour}"
98
100
  else:
99
101
  self.version = version_arg
@@ -127,7 +129,7 @@ class FilesPackage:
127
129
  self.path = path
128
130
 
129
131
  if version_arg is None:
130
- utc_now = datetime.datetime.utcnow()
132
+ utc_now = datetime.datetime.now(datetime.timezone.utc)
131
133
  self.version = f"{utc_now.year}.{utc_now.month}.{utc_now.day}-{utc_now.second + 60 * utc_now.minute + 60 * 60 * utc_now.hour}"
132
134
  else:
133
135
  self.version = version_arg
@@ -169,14 +171,62 @@ class FilesPackage:
169
171
  return output
170
172
 
171
173
 
174
+ def _unpack_stripped(tar_data: bytes, root: Path) -> None:
175
+ """Unpack a tar into *root* with its top-level directory removed.
176
+
177
+ This is the same re-rooting the broker applies when it lays the
178
+ package into the image, so what create-meta imports locally is what
179
+ the cluster will import at runtime.
180
+ """
181
+ with tarfile.open(fileobj=io.BytesIO(tar_data), mode="r:*") as tar:
182
+ for member in tar.getmembers():
183
+ parts = member.name.split("/", 1)
184
+ inner = parts[1] if len(parts) > 1 else parts[0]
185
+ if not inner or inner.startswith("/") or ".." in Path(inner).parts:
186
+ continue
187
+ dest = root / inner
188
+ if member.isdir():
189
+ dest.mkdir(parents=True, exist_ok=True)
190
+ elif member.isfile():
191
+ dest.parent.mkdir(parents=True, exist_ok=True)
192
+ fileobj = tar.extractfile(member)
193
+ assert fileobj is not None
194
+ dest.write_bytes(fileobj.read())
195
+
196
+
197
+ @contextmanager
198
+ def _import_root(
199
+ package: Union[DistPackage, ModulePackage, FilesPackage],
200
+ source_dir: Path | None,
201
+ contents: bytes,
202
+ ) -> Iterator[Path | None]:
203
+ """Directory to prepend to PYTHONPATH so create-meta can import the package.
204
+
205
+ The broker never installs the package: it strips the sdist's top-level
206
+ directory and puts the unpacked root on PYTHONPATH. Mirror that here so
207
+ publishing doesn't require the package to be installed locally.
208
+ """
209
+ if isinstance(package, ModulePackage):
210
+ yield None
211
+ elif isinstance(package, FilesPackage):
212
+ yield package.path.resolve().parent
213
+ elif source_dir is not None:
214
+ yield source_dir.resolve()
215
+ else:
216
+ with tempfile.TemporaryDirectory(prefix="ellf-publish-") as tmp:
217
+ root = Path(tmp)
218
+ _unpack_stripped(contents, root)
219
+ yield root
220
+
221
+
172
222
  def _run_create_meta(
173
223
  package_name: str, *, extra_python_path: Path | None = None
174
224
  ) -> str:
175
225
  """Run create-meta locally to produce recipe metadata JSON.
176
226
 
177
227
  If *extra_python_path* is given it is prepended to ``PYTHONPATH`` so
178
- that ``create-meta`` can import loose ``.py`` files that aren't
179
- installed as packages.
228
+ that ``create-meta`` can import the package without it being
229
+ installed.
180
230
  """
181
231
  env = None
182
232
  if extra_python_path is not None:
@@ -200,9 +250,16 @@ def _run_create_meta(
200
250
  env=env,
201
251
  )
202
252
  if result.returncode != 0:
203
- raise CLIError(
204
- f"create-meta failed (exit {result.returncode}):\n{result.stderr}"
205
- )
253
+ message = f"create-meta failed (exit {result.returncode}):\n{result.stderr}"
254
+ if "RecipePackageNotFound" in result.stderr:
255
+ where = f" from {extra_python_path}" if extra_python_path else ""
256
+ message += (
257
+ f"\nCould not import '{package_name}'{where}. The package is "
258
+ "imported by name rather than installed, so the source must "
259
+ f"contain a '{package_name}' package (a directory with "
260
+ f"__init__.py) or a '{package_name}.py' module at its top level."
261
+ )
262
+ raise CLIError(message)
206
263
  return result.stdout.strip()
207
264
 
208
265
 
@@ -425,10 +482,12 @@ def publish_code(
425
482
  if auth.cluster_id is None:
426
483
  raise CLIError(Messages.E036)
427
484
 
485
+ source_dir: Path | None = None
428
486
  if is_path:
429
487
  path = Path(name_or_path)
430
- if path.is_dir() and (path / "setup.py").exists():
488
+ if path.is_dir() and _is_package_dir(path):
431
489
  msg.info(f"Building sdist from {name_or_path}")
490
+ source_dir = path
432
491
  package = DistPackage(_make_sdist_if_dir(path))
433
492
  elif not path.is_file():
434
493
  msg.info(f"Interpreting {name_or_path} as directory of code")
@@ -447,15 +506,13 @@ def publish_code(
447
506
  if package_version is None:
448
507
  msg.info(f"No version specified. Defaulting to {package.version}")
449
508
 
450
- # Generate recipe metadata locally
451
- # For FilesPackage we need to tell the subprocess where to find the source
452
- # so that ``create-meta`` can actually import the module.
453
- extra_python_path: Path | None = None
454
- if isinstance(package, FilesPackage):
455
- src = package.path.resolve()
456
- extra_python_path = src.parent if src.is_file() else src.parent
457
- with loading("Generating recipe metadata"):
458
- meta_json = _run_create_meta(package.name, extra_python_path=extra_python_path)
509
+ # Generate recipe metadata locally, importing the package from its
510
+ # source rather than requiring it to be installed.
511
+ with _import_root(package, source_dir, contents) as extra_python_path:
512
+ with loading("Generating recipe metadata"):
513
+ meta_json = _run_create_meta(
514
+ package.name, extra_python_path=extra_python_path
515
+ )
459
516
 
460
517
  meta_list = json.loads(meta_json)
461
518
  if len(meta_list) == 0:
@@ -524,16 +581,39 @@ def _is_path(name_or_path: str) -> bool:
524
581
  return path.exists()
525
582
 
526
583
 
584
+ def _is_package_dir(path: Path) -> bool:
585
+ return (path / "setup.py").exists() or (path / "pyproject.toml").exists()
586
+
587
+
527
588
  def _make_sdist_if_dir(path: Path) -> Path:
528
589
  if not path.is_dir():
529
590
  return path
530
591
  env = dict(os.environ)
531
592
  env["SKIP_CYTHON"] = "1"
532
- r = subprocess.run(["python", "setup.py", "sdist"], cwd=path, text=True, env=env)
533
- r.check_returncode()
534
- dists = list((path / "dist").iterdir())
593
+ if (path / "setup.py").exists():
594
+ cmd = ["python", "setup.py", "sdist"]
595
+ else:
596
+ # pyproject.toml-only projects have no setup.py to run; `build`
597
+ # is the standard front-end but isn't a CLI dependency.
598
+ cmd = ["python", "-m", "build", "--sdist"]
599
+ probe = subprocess.run(
600
+ ["python", "-c", "import build"], capture_output=True, text=True
601
+ )
602
+ if probe.returncode != 0:
603
+ raise CLIError(
604
+ f"{path} has a pyproject.toml but no setup.py, so building "
605
+ "its sdist needs the 'build' package. Install it with "
606
+ "`pip install build` and re-run the publish command."
607
+ )
608
+ r = subprocess.run(cmd, cwd=path, text=True, env=env)
609
+ if r.returncode != 0:
610
+ raise CLIError(f"Building the sdist in {path} failed (exit {r.returncode})")
611
+ dist_dir = path / "dist"
612
+ if not dist_dir.is_dir():
613
+ raise CLIError(f"sdist not created after building in {path}")
614
+ dists = [p for p in dist_dir.iterdir() if p.is_file()]
535
615
  if len(dists) == 0:
536
- raise ValueError("sdist not created after subprocess in {path / 'dist'}")
616
+ raise CLIError(f"sdist not created after building in {dist_dir}")
537
617
  # Return last modified file
538
618
  return max(dists, key=lambda p: p.stat().st_mtime)
539
619
 
@@ -646,6 +726,6 @@ def _read_record(path: Path) -> list[str]:
646
726
  output = []
647
727
  with path.open("r", encoding="utf8") as file_:
648
728
  for line in file_.read().strip().split("\n"):
649
- subpath, sha, size = line.split(",")
729
+ subpath, _, _ = line.split(",")
650
730
  output.append(subpath)
651
731
  return output
@@ -5,6 +5,7 @@ from typing import Any, Optional, Union
5
5
  from uuid import UUID
6
6
 
7
7
  import httpx
8
+ from pydantic import ValidationError
8
9
  from radicli import Arg
9
10
  from wasabi import msg
10
11
 
@@ -234,15 +235,18 @@ def _stats(task: TaskDetail, *, as_json: bool) -> dict[str, Any]:
234
235
  client = get_auth_state().client
235
236
  try:
236
237
  stats = client.task.stats(task.id)
237
- except (EllfErrors.EllfError, *HTTPXErrors):
238
+ except (EllfErrors.EllfError, ValidationError, *HTTPXErrors):
239
+ # Stats are decorative. A PAM release whose stats schema differs from
240
+ # this CLI's (older, or newer) should not take down `info`.
238
241
  return {"related_actions": len(task.related_actions)}
242
+ duration = stats.duration or 0
239
243
  return {
240
244
  "annotators": stats.annotators,
241
- "agents": stats.agents,
245
+ "agents": stats.agents or 0,
242
246
  "sessions": stats.sessions,
243
247
  "live_sessions": stats.live_sessions,
244
248
  # Whole seconds for --json, as PAM reports it; readable in the table.
245
- "duration": stats.duration if as_json else format_duration(stats.duration),
249
+ "duration": duration if as_json else format_duration(duration),
246
250
  "related_actions": len(task.related_actions),
247
251
  }
248
252
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "prog": "ellf",
3
3
  "help": "Ellf Command Line Interface.",
4
- "version": "17.0.206",
4
+ "version": "18.0.1",
5
5
  "extra_key": "_extra",
6
6
  "commands": {
7
7
  "actions": {
@@ -432,20 +432,22 @@
432
432
  "id": "include",
433
433
  "option": "--include",
434
434
  "short": null,
435
- "orig_help": "Comma-separated items to include",
436
- "default": "['tasks', 'actions', 'assets', 'datasets', 'paths']",
437
- "help": "Comma-separated items to include (str)",
435
+ "orig_help": "Comma-separated items to include: tasks, actions, agents, services, assets, datasets, paths",
436
+ "default": "['tasks', 'actions', 'agents', 'services', 'assets', 'datasets', 'paths']",
437
+ "help": "Comma-separated items to include: tasks, actions, agents, services, assets, datasets, paths (str)",
438
438
  "action": "append",
439
439
  "choices": [
440
440
  "tasks",
441
441
  "actions",
442
+ "agents",
443
+ "services",
442
444
  "assets",
443
445
  "datasets",
444
446
  "paths"
445
447
  ],
446
448
  "has_converter": false,
447
449
  "type": "str",
448
- "orig_type": "list[Literal[tasks, actions, assets, datasets, paths]]"
450
+ "orig_type": "list[Literal[tasks, actions, agents, services, assets, datasets, paths]]"
449
451
  }
450
452
  ],
451
453
  "description": "\nSave the state of the current app JSON file. If an assets directory is\nprovided, assets will be downloaded and referenced in the JSON accordingly.\n",
@@ -483,7 +485,7 @@
483
485
  "orig_type": "bool"
484
486
  }
485
487
  ],
486
- "description": "\nPopulate Ellf with data for projects, tasks, actions, assets and paths.\n",
488
+ "description": "\nPopulate Ellf with data for projects, tasks, actions, agents, services,\nassets, datasets and paths.\n",
487
489
  "allow_extra": false,
488
490
  "parent": null,
489
491
  "is_placeholder": false
@@ -109,13 +109,13 @@ ellf secret create my-credentials OPENAI_API_KEY="sk-..." CUSTOM_SECRET=-
109
109
  T035 = "Downloading {count} assets"
110
110
  T036 = "Downloaded assets"
111
111
  T037 = "Not downloading any assets. Make sure to specify the source in the JSON manually if you're planning on using it to import later."
112
- T038 = "Generated {n_projects} projects, {n_tasks} tasks, {n_actions} actions, {n_assets} assets, {n_datasets} datasets, {n_paths} paths"
112
+ T038 = "Generated {n_projects} projects, {n_tasks} tasks, {n_actions} actions, {n_agents} agents, {n_services} services, {n_assets} assets, {n_datasets} datasets, {n_paths} paths"
113
113
  T039 = "Successfully saved data"
114
114
  T040 = "Create projects"
115
115
  T041 = "Configure cluster paths"
116
116
  T042 = "Transfer asset files"
117
117
  T043 = "Register assets"
118
- T044 = "Create tasks and actions"
118
+ T044 = "Create tasks, actions, agents and services"
119
119
  T045 = "Upload recipes"
120
120
  T046 = "Package {name} has no previous published version"
121
121
  T047 = "Found previous published versions for package {name}: {versions}"
@@ -135,6 +135,9 @@ ellf secret create my-credentials OPENAI_API_KEY="sk-..." CUSTOM_SECRET=-
135
135
  T061 = "Deactivated {noun} {name}. They no longer have access or take up a seat; their work stays in the organization and an admin can reactivate them with `ellf users reactivate`."
136
136
  T062 = "Successfully reactivated {noun} {name}"
137
137
  T063 = "Not reactivating {noun} {name}: not deactivated"
138
+ T064 = "Create datasets"
139
+ T065 = "Assign agents to tasks"
140
+ T066 = "Not assigning agent {name} to task {task}: already assigned"
138
141
 
139
142
  # CLI docstrings for placeholders (parent commands with no functions)
140
143
  doc_todos = "Manage todos for handoff between web assistant and local Claude Code"
@@ -220,7 +223,7 @@ ellf secret create my-credentials OPENAI_API_KEY="sk-..." CUSTOM_SECRET=-
220
223
  import_data = "JSON file to import"
221
224
  export_output = "JSON output path for data"
222
225
  assets_dir = "Local directory to download assets to"
223
- include = "Comma-separated items to include"
226
+ include = "Comma-separated items to include: tasks, actions, agents, services, assets, datasets, paths"
224
227
  import_strict = "Error if items already exist and don't skip or overwrite anything"
225
228
  files_expand_path_alias = "Expand path aliases when displaying remote paths"
226
229
  secrets_path = "/ separated key prefix to save secrets to"
@@ -293,6 +296,7 @@ ellf secret create my-credentials OPENAI_API_KEY="sk-..." CUSTOM_SECRET=-
293
296
  )
294
297
  E189 = "Agent {name} is not assigned to task {task}. Check which tasks it is assigned to with `ellf agents info {name}`"
295
298
  E190 = "No project set, so there is nothing to list {plural} for. Pass --project, or set a default with `ellf config project`"
299
+ E191 = "Could not create {noun} {name}: input validation failed"
296
300
 
297
301
 
298
302
  # fmt: on
@@ -0,0 +1,3 @@
1
+ 17.0.207
2
+ major
3
+ 3f1b3ed7-eaac-41f5-b740-0af3331595d9
@@ -0,0 +1,3 @@
1
+ 18.0.0
2
+ patch
3
+ 8995783a-a9fa-4c4e-90b3-6386a4c481eb
@@ -0,0 +1,3 @@
1
+ 18.0.1
2
+ patch
3
+ f6ea2c99-a715-4c4d-876a-23ed8c62a83d
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ellf-cli
3
- Version: 17.0.207
3
+ Version: 18.0.2
4
4
  Summary: Ellf Command Line Interface
5
5
  Home-page: https://prodi.gy
6
6
  Author: ExplosionAI GmbH