mcli-framework 8.0.48__tar.gz → 8.0.50__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 (241) hide show
  1. {mcli_framework-8.0.48/src/mcli_framework.egg-info → mcli_framework-8.0.50}/PKG-INFO +2 -1
  2. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/pyproject.toml +2 -1
  3. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/main.py +9 -0
  4. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/ipfs_sync.py +14 -2
  5. mcli_framework-8.0.50/src/mcli/workflow/ci/__init__.py +1 -0
  6. mcli_framework-8.0.50/src/mcli/workflow/ci/act_runner.py +61 -0
  7. mcli_framework-8.0.50/src/mcli/workflow/ci/ci.py +172 -0
  8. mcli_framework-8.0.50/src/mcli/workflow/ci/runner_status.py +26 -0
  9. mcli_framework-8.0.50/src/mcli/workflow/ci/workflow_transform.py +151 -0
  10. {mcli_framework-8.0.48 → mcli_framework-8.0.50/src/mcli_framework.egg-info}/PKG-INFO +2 -1
  11. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli_framework.egg-info/SOURCES.txt +5 -0
  12. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli_framework.egg-info/requires.txt +1 -0
  13. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/LICENSE +0 -0
  14. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/MANIFEST.in +0 -0
  15. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/README.md +0 -0
  16. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/llms-full.txt +0 -0
  17. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/llms.txt +0 -0
  18. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/mcli_rust/Cargo.toml +0 -0
  19. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/mcli_rust/src/command_parser.rs +0 -0
  20. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/mcli_rust/src/file_watcher.rs +0 -0
  21. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/mcli_rust/src/lib.rs +0 -0
  22. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/mcli_rust/src/process_manager.rs +0 -0
  23. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/mcli_rust/src/tfidf.rs +0 -0
  24. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/setup.cfg +0 -0
  25. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/__init__.py +0 -0
  26. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/__init__.py +0 -0
  27. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/commands_cmd.py +0 -0
  28. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/completion_helpers.py +0 -0
  29. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/context_cmd.py +0 -0
  30. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/create_cmd.py +0 -0
  31. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/delete_cmd.py +0 -0
  32. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/edit_cmd.py +0 -0
  33. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/import_cmd.py +0 -0
  34. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/init_cmd.py +0 -0
  35. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/list_cmd.py +0 -0
  36. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/migrate_cmd.py +0 -0
  37. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/model/__init__.py +0 -0
  38. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/model/model.py +0 -0
  39. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/model_cmd.py +0 -0
  40. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/mv_cmd.py +0 -0
  41. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/new_cmd.py +0 -0
  42. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/remove_cmd.py +0 -0
  43. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/rm_cmd.py +0 -0
  44. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/search_cmd.py +0 -0
  45. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/services_cmd.py +0 -0
  46. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/setup_cmd.py +0 -0
  47. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/source_sync_cmd.py +0 -0
  48. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/sync_cmd.py +0 -0
  49. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/video/__init__.py +0 -0
  50. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/app/video/video.py +0 -0
  51. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/config.toml +0 -0
  52. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/__init__.py +0 -0
  53. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/api/__init__.py +0 -0
  54. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/api/api.py +0 -0
  55. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/api/daemon_client.py +0 -0
  56. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/api/daemon_client_local.py +0 -0
  57. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/api/daemon_decorator.py +0 -0
  58. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/api/mcli_decorators.py +0 -0
  59. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/__init__.py +0 -0
  60. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/auth.py +0 -0
  61. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/aws_manager.py +0 -0
  62. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/azure_manager.py +0 -0
  63. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/credential_manager.py +0 -0
  64. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/gcp_manager.py +0 -0
  65. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/key_manager.py +0 -0
  66. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/mcli_manager.py +0 -0
  67. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/token_manager.py +0 -0
  68. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/auth/token_util.py +0 -0
  69. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/config/__init__.py +0 -0
  70. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/config/config.py +0 -0
  71. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/config/settings.py +0 -0
  72. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/__init__.py +0 -0
  73. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/commands.py +0 -0
  74. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/defaults.py +0 -0
  75. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/env.py +0 -0
  76. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/messages.py +0 -0
  77. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/paths.py +0 -0
  78. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/scripts.py +0 -0
  79. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/constants/storage.py +0 -0
  80. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/custom_commands.py +0 -0
  81. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/discovery/__init__.py +0 -0
  82. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/discovery/command_discovery.py +0 -0
  83. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/erd/__init__.py +0 -0
  84. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/erd/erd.py +0 -0
  85. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/erd/generate_graph.py +0 -0
  86. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/errors.py +0 -0
  87. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/feature_detection.py +0 -0
  88. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/files/__init__.py +0 -0
  89. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/files/files.py +0 -0
  90. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/folder_workflows.py +0 -0
  91. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/fs/__init__.py +0 -0
  92. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/fs/fs.py +0 -0
  93. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/ipfs_utils.py +0 -0
  94. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/ipns_manager.py +0 -0
  95. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/lib.py +0 -0
  96. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/logger/__init__.py +0 -0
  97. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/logger/correlation.py +0 -0
  98. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/logger/logger.py +0 -0
  99. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/logger/structured.py +0 -0
  100. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/optional_deps.py +0 -0
  101. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/paths.py +0 -0
  102. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/performance/__init__.py +0 -0
  103. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/performance/optimizer.py +0 -0
  104. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/performance/rust_bridge.py +0 -0
  105. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/performance/uvloop_config.py +0 -0
  106. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/pickles/__init__.py +0 -0
  107. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/pickles/pickles.py +0 -0
  108. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/pyenv/__init__.py +0 -0
  109. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/pyenv/deps.py +0 -0
  110. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/pyenv/manager.py +0 -0
  111. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/pyenv/venv.py +0 -0
  112. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/script_loader.py +0 -0
  113. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/script_sync.py +0 -0
  114. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/script_watcher.py +0 -0
  115. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/search/cached_vectorizer.py +0 -0
  116. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/secrets/__init__.py +0 -0
  117. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/secrets/commands.py +0 -0
  118. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/secrets/manager.py +0 -0
  119. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/secrets/repl.py +0 -0
  120. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/secrets/store.py +0 -0
  121. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/__init__.py +0 -0
  122. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/config.py +0 -0
  123. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/data_pipeline.py +0 -0
  124. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/health.py +0 -0
  125. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/lsh_client.py +0 -0
  126. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/manager.py +0 -0
  127. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/redis_service.py +0 -0
  128. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/registry.py +0 -0
  129. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/state.py +0 -0
  130. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/services/supervisor.py +0 -0
  131. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/shell/__init__.py +0 -0
  132. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/shell/exceptions.py +0 -0
  133. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/shell/shell.py +0 -0
  134. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/sync_key_store.py +0 -0
  135. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/templates/__init__.py +0 -0
  136. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/templates/command_templates.py +0 -0
  137. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/toml/__init__.py +0 -0
  138. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/toml/toml.py +0 -0
  139. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/types.py +0 -0
  140. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/ui/styling.py +0 -0
  141. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/ui/visual_effects.py +0 -0
  142. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/watcher/__init__.py +0 -0
  143. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/watcher/watcher.py +0 -0
  144. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/workflow_models.py +0 -0
  145. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/lib/workspace_registry.py +0 -0
  146. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/mygroup/__init__.py +0 -0
  147. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/mygroup/test_cmd.py +0 -0
  148. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/public/__init__.py +0 -0
  149. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/public/commands/__init__.py +0 -0
  150. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/public/oi/oi.py +0 -0
  151. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/public/public.py +0 -0
  152. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/__init__.py +0 -0
  153. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/completion_cmd.py +0 -0
  154. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/env_cmd.py +0 -0
  155. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/health_cmd.py +0 -0
  156. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/ipfs_cmd.py +0 -0
  157. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/logs_cmd.py +0 -0
  158. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/migrate_cmd.py +0 -0
  159. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/release_notes_cmd.py +0 -0
  160. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/self_cmd.py +0 -0
  161. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/store_cmd.py +0 -0
  162. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/test_cmd.py +0 -0
  163. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/self/workflows_cmd.py +0 -0
  164. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/__init__.py +0 -0
  165. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/backends/__init__.py +0 -0
  166. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/backends/ipfs_backend.py +0 -0
  167. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/base.py +0 -0
  168. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/cache.py +0 -0
  169. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/encryption.py +0 -0
  170. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/factory.py +0 -0
  171. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/registry.py +0 -0
  172. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/storage/storacha_cli.py +0 -0
  173. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/__init__.py +0 -0
  174. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/__init__.py +0 -0
  175. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/async_command_database.py +0 -0
  176. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/async_process_manager.py +0 -0
  177. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/client.py +0 -0
  178. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/daemon.py +0 -0
  179. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/daemon_api.py +0 -0
  180. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/enhanced_daemon.py +0 -0
  181. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/process_cli.py +0 -0
  182. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/process_manager.py +0 -0
  183. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/daemon/test_daemon.py +0 -0
  184. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/doc_convert.py +0 -0
  185. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/docker/__init__.py +0 -0
  186. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/docker/docker.py +0 -0
  187. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/file/__init__.py +0 -0
  188. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/gcloud/__init__.py +0 -0
  189. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/gcloud/config.toml +0 -0
  190. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/gcloud/gcloud.py +0 -0
  191. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/git_commit/__init__.py +0 -0
  192. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/git_commit/ai_service.py +0 -0
  193. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/interview/__init__.py +0 -0
  194. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/lsh_integration.py +0 -0
  195. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/client.py +0 -0
  196. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/download_and_run_efficient_models.py +0 -0
  197. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/lightweight_embedder.py +0 -0
  198. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/lightweight_model_server.py +0 -0
  199. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/lightweight_test.py +0 -0
  200. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/model_service.py +0 -0
  201. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/ollama_efficient_runner.py +0 -0
  202. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/openai_adapter.py +0 -0
  203. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/pdf_processor.py +0 -0
  204. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/test_efficient_runner.py +0 -0
  205. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/test_example.py +0 -0
  206. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/test_integration.py +0 -0
  207. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/model_service/test_new_features.py +0 -0
  208. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/notebook/__init__.py +0 -0
  209. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/notebook/command_loader.py +0 -0
  210. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/notebook/converter.py +0 -0
  211. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/notebook/executor.py +0 -0
  212. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/notebook/notebook_cmd.py +0 -0
  213. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/notebook/schema.py +0 -0
  214. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/notebook/validator.py +0 -0
  215. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/openai/openai.py +0 -0
  216. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/registry/__init__.py +0 -0
  217. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/registry/registry.py +0 -0
  218. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/repo/__init__.py +0 -0
  219. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/repo/repo.py +0 -0
  220. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/__init__.py +0 -0
  221. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/cron_parser.py +0 -0
  222. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/job.py +0 -0
  223. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/models.py +0 -0
  224. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/monitor.py +0 -0
  225. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/persistence.py +0 -0
  226. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/scheduler.py +0 -0
  227. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/scheduler/validation.py +0 -0
  228. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/search/__init__.py +0 -0
  229. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/secrets/__init__.py +0 -0
  230. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/secrets/secrets_cmd.py +0 -0
  231. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/storage/__init__.py +0 -0
  232. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/storage/storage_cmd.py +0 -0
  233. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/sync/__init__.py +0 -0
  234. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/sync/test_cmd.py +0 -0
  235. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/videos/__init__.py +0 -0
  236. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/wakatime/__init__.py +0 -0
  237. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/wakatime/wakatime.py +0 -0
  238. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli/workflow/workflow.py +0 -0
  239. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli_framework.egg-info/dependency_links.txt +0 -0
  240. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli_framework.egg-info/entry_points.txt +0 -0
  241. {mcli_framework-8.0.48 → mcli_framework-8.0.50}/src/mcli_framework.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcli-framework
3
- Version: 8.0.48
3
+ Version: 8.0.50
4
4
  Summary: Portable workflow framework - transform any script into a versioned, schedulable command. Store in ~/.mcli/workflows/, version with lockfile, run as daemon or cron job.
5
5
  Author-email: Luis Fernandez de la Vara <luis@lefv.io>
6
6
  Maintainer-email: Luis Fernandez de la Vara <luis@lefv.io>
@@ -37,6 +37,7 @@ Requires-Python: >=3.10
37
37
  Description-Content-Type: text/markdown
38
38
  License-File: LICENSE
39
39
  Requires-Dist: click<9.0.0,>=8.1.7
40
+ Requires-Dist: ruamel.yaml<0.19,>=0.18
40
41
  Requires-Dist: rich<15.0.0,>=14.0.0
41
42
  Requires-Dist: requests<3.0.0,>=2.31.0
42
43
  Requires-Dist: tomli<3.0.0,>=2.2.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcli-framework"
3
- version = "8.0.48"
3
+ version = "8.0.50"
4
4
  description = "Portable workflow framework - transform any script into a versioned, schedulable command. Store in ~/.mcli/workflows/, version with lockfile, run as daemon or cron job."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -37,6 +37,7 @@ classifiers = [
37
37
  dependencies = [
38
38
  # Core CLI dependencies
39
39
  "click>=8.1.7,<9.0.0",
40
+ "ruamel.yaml>=0.18,<0.19",
40
41
  "rich>=14.0.0,<15.0.0",
41
42
  "requests>=2.31.0,<3.0.0",
42
43
  "tomli>=2.2.1,<3.0.0",
@@ -447,6 +447,15 @@ def _add_lazy_commands(app: click.Group):
447
447
  except ImportError as e:
448
448
  logger.debug(f"Could not load sync group: {e}")
449
449
 
450
+ # mcli ci - act-first CI gate + hosted-trigger migration
451
+ try:
452
+ from mcli.workflow.ci.ci import ci
453
+
454
+ app.add_command(ci, name="ci")
455
+ logger.debug("Added ci group")
456
+ except ImportError as e:
457
+ logger.debug(f"Could not load ci group: {e}")
458
+
450
459
  # mcli setup - Onboarding wizard for new users
451
460
  try:
452
461
  from mcli.app.setup_cmd import setup
@@ -28,7 +28,7 @@ import hashlib
28
28
  import json
29
29
  import time
30
30
  from datetime import datetime
31
- from pathlib import Path
31
+ from pathlib import Path, PurePosixPath
32
32
  from typing import Optional
33
33
 
34
34
  import requests
@@ -535,7 +535,19 @@ class IPFSSync:
535
535
  f"hash mismatch for '{name}': expected {expected_value}, got {actual}"
536
536
  )
537
537
 
538
- target = workflows_dir / script_filename
538
+ # SECURITY: `script_filename` comes from a remote, untrusted manifest.
539
+ # Contain it inside workflows_dir so a crafted manifest cannot write
540
+ # outside the directory via '..' segments or an absolute path.
541
+ rel = PurePosixPath(script_filename)
542
+ if rel.is_absolute() or any(part == ".." for part in rel.parts):
543
+ raise ValueError(f"unsafe script path in manifest: {script_filename}")
544
+ base = Path(workflows_dir).resolve()
545
+ target = (base / Path(*rel.parts)).resolve()
546
+ if base != target and base not in target.parents:
547
+ raise ValueError(f"script path escapes workflows dir: {script_filename}")
548
+
549
+ # Recreate group subdirectories (e.g. "demo/hello.py") on pull.
550
+ target.parent.mkdir(parents=True, exist_ok=True)
539
551
  target.write_bytes(payload)
540
552
  written.append(target)
541
553
 
@@ -0,0 +1 @@
1
+ """act-first CI tooling: local act gate + hosted-trigger stripping for private repos."""
@@ -0,0 +1,61 @@
1
+ """Run `act` locally and classify the outcome for the PR gate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ import subprocess
7
+ from enum import Enum
8
+ from pathlib import Path
9
+
10
+
11
+ class PreflightResult(Enum):
12
+ PASS = "pass"
13
+ FAIL = "fail"
14
+ UNREACHABLE = "unreachable"
15
+
16
+
17
+ def act_available() -> bool:
18
+ return shutil.which("act") is not None
19
+
20
+
21
+ def docker_running() -> bool:
22
+ try:
23
+ proc = subprocess.run(["docker", "info"], capture_output=True, timeout=30)
24
+ return proc.returncode == 0
25
+ except (FileNotFoundError, subprocess.TimeoutExpired):
26
+ return False
27
+
28
+
29
+ def probe() -> bool:
30
+ """Can act actually run here? Needs the binary, a live docker daemon, and `act -l`."""
31
+ if not act_available() or not docker_running():
32
+ return False
33
+ try:
34
+ proc = subprocess.run(["act", "-l"], capture_output=True, text=True, timeout=60)
35
+ except (FileNotFoundError, subprocess.TimeoutExpired):
36
+ return False
37
+ return proc.returncode == 0
38
+
39
+
40
+ def build_act_command(event: str) -> list[str]:
41
+ cmd = ["act", event]
42
+ if Path(".secrets").exists():
43
+ cmd += ["--secret-file", ".secrets"]
44
+ return cmd
45
+
46
+
47
+ def run_act(event: str = "pull_request") -> PreflightResult:
48
+ """Run act for `event`. PASS on exit 0, else FAIL. (Probe gates UNREACHABLE upstream.)"""
49
+ proc = subprocess.run(build_act_command(event))
50
+ return PreflightResult.PASS if proc.returncode == 0 else PreflightResult.FAIL
51
+
52
+
53
+ def preflight(repo_slug: str, event: str = "pull_request") -> PreflightResult:
54
+ """Primary gate. PASS/FAIL if act can run; UNREACHABLE if act can't start here.
55
+
56
+ `repo_slug` is accepted for symmetry and future use; the runner fallback is
57
+ orchestrated by the CLI layer based on runner_status.has_online_runner.
58
+ """
59
+ if not probe():
60
+ return PreflightResult.UNREACHABLE
61
+ return run_act(event)
@@ -0,0 +1,172 @@
1
+ """`mcli ci` — act-first CI gate and hosted-trigger migration for private repos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import stat
7
+ import subprocess
8
+ from pathlib import Path
9
+
10
+ import click
11
+
12
+ from mcli.workflow.ci.act_runner import PreflightResult, act_available, docker_running
13
+ from mcli.workflow.ci.act_runner import preflight as preflight_fn
14
+ from mcli.workflow.ci.runner_status import has_online_runner
15
+ from mcli.workflow.ci.workflow_transform import transform_file, write_self_hosted_workflow
16
+
17
+ _GITHUB_REMOTE_RE = re.compile(
18
+ r"(?:git@github\.com:|https://github\.com/)([^/]+/[^/]+?)(?:\.git)?/?$"
19
+ )
20
+
21
+
22
+ def current_repo_slug() -> str | None:
23
+ """owner/name from a github.com origin remote, or None (non-GitHub or no remote)."""
24
+ try:
25
+ url = subprocess.run(
26
+ ["git", "remote", "get-url", "origin"],
27
+ capture_output=True,
28
+ text=True,
29
+ timeout=10,
30
+ ).stdout.strip()
31
+ except (FileNotFoundError, subprocess.TimeoutExpired):
32
+ return None
33
+ if not url:
34
+ return None
35
+ match = _GITHUB_REMOTE_RE.match(url)
36
+ return match.group(1) if match else None
37
+
38
+
39
+ def detect_test_command() -> str:
40
+ """Best-effort test command for the self-hosted fallback workflow."""
41
+ makefile = Path("Makefile")
42
+ if makefile.exists():
43
+ txt = makefile.read_text()
44
+ if "\ntest:" in txt or txt.startswith("test:"):
45
+ return "make test"
46
+ if Path("pyproject.toml").exists() or Path("pytest.ini").exists():
47
+ return "uv run pytest -v || pytest -v"
48
+ if Path("package.json").exists():
49
+ return "npm test"
50
+ if Path("mix.exs").exists():
51
+ return "mix test"
52
+ return "echo 'TODO: set test command' && exit 1"
53
+
54
+
55
+ def workflows_dir() -> Path:
56
+ return Path(".github") / "workflows"
57
+
58
+
59
+ @click.group()
60
+ def ci():
61
+ """act-first CI: local act gate + stop billed hosted runners on private repos."""
62
+
63
+
64
+ @ci.command()
65
+ @click.option("--dry-run", is_flag=True, help="Show what would change without writing.")
66
+ def migrate(dry_run):
67
+ """Strip hosted triggers from this repo's workflows + add self-hosted fallback."""
68
+ wfdir = workflows_dir()
69
+ if not wfdir.exists():
70
+ click.echo("No .github/workflows directory; nothing to migrate.")
71
+ return
72
+ slug = current_repo_slug()
73
+ has_runner = has_online_runner(slug) if slug else False
74
+ test_cmd = detect_test_command()
75
+
76
+ files = sorted(p for p in wfdir.glob("*.y*ml") if p.name != "self-hosted-ci.yml")
77
+ if dry_run:
78
+ from mcli.workflow.ci.workflow_transform import MARKER, _yaml, workflow_has_hosted_job
79
+
80
+ for f in files:
81
+ text = f.read_text()
82
+ if MARKER in text:
83
+ click.echo(f" skip (already migrated): {f.name}")
84
+ continue
85
+ hosted = workflow_has_hosted_job(_yaml().load(text))
86
+ click.echo(f" {'STRIP' if hosted else 'keep '}: {f.name}")
87
+ click.echo(f" fallback self-hosted-ci.yml (pull_request={has_runner}, test='{test_cmd}')")
88
+ return
89
+
90
+ changed = [f.name for f in files if transform_file(f)]
91
+ created = write_self_hosted_workflow(wfdir, test_cmd, with_pull_request=has_runner)
92
+ for name in changed:
93
+ click.echo(f" stripped: {name}")
94
+ if created:
95
+ click.echo(f" created: self-hosted-ci.yml (pull_request={has_runner})")
96
+ click.echo(f"Done. {len(changed)} workflow(s) migrated.")
97
+
98
+
99
+ @ci.command()
100
+ @click.option("--event", default="pull_request", show_default=True, help="act event to simulate.")
101
+ def preflight(event):
102
+ """Run act as the PR gate. Exit 0=pass, 1=fail, 2=cannot validate, 3=use runner."""
103
+ slug = current_repo_slug()
104
+ result = preflight_fn(slug, event)
105
+ if result == PreflightResult.PASS:
106
+ click.echo("✅ act passed — OK to open PR.")
107
+ raise SystemExit(0)
108
+ if result == PreflightResult.FAIL:
109
+ click.echo("❌ act failed — fix before opening PR.")
110
+ raise SystemExit(1)
111
+ # UNREACHABLE
112
+ if slug and has_online_runner(slug):
113
+ click.echo(
114
+ "⚠️ act unreachable here; an online runner exists — "
115
+ "push and let the self-hosted runner validate."
116
+ )
117
+ raise SystemExit(3)
118
+ click.echo("⚠️ act unreachable and no online runner — cannot validate this PR.")
119
+ raise SystemExit(2)
120
+
121
+
122
+ @ci.command()
123
+ def pr():
124
+ """preflight, then `gh pr create --fill --base main` if it passed."""
125
+ slug = current_repo_slug()
126
+ result = preflight_fn(slug)
127
+ if result == PreflightResult.PASS:
128
+ # check=False on purpose: let gh/git stream their own errors to the terminal
129
+ # rather than raising CalledProcessError and hiding their output.
130
+ subprocess.run(["gh", "pr", "create", "--fill", "--base", "main"], check=False)
131
+ return
132
+ if result == PreflightResult.FAIL:
133
+ click.echo("act failed; not opening PR.")
134
+ raise SystemExit(1)
135
+ if slug and has_online_runner(slug):
136
+ click.echo("act unreachable; pushing so the runner can validate.")
137
+ subprocess.run(["git", "push", "-u", "origin", "HEAD"], check=False)
138
+ subprocess.run(["gh", "pr", "create", "--fill", "--base", "main"], check=False)
139
+ return
140
+ click.echo("act unreachable and no runner; refusing to open an unvalidated PR.")
141
+ raise SystemExit(2)
142
+
143
+
144
+ PRE_PUSH_HOOK = """#!/usr/bin/env bash
145
+ # mcli-ci pre-push gate: validate with act before pushing.
146
+ exec mcli ci preflight
147
+ """
148
+
149
+
150
+ @ci.command()
151
+ def doctor():
152
+ """Show act/docker/runner status for this repo."""
153
+ click.echo(f"act installed: {act_available()}")
154
+ click.echo(f"docker running: {docker_running()}")
155
+ slug = current_repo_slug()
156
+ click.echo(f"repo: {slug or '(no origin)'}")
157
+ if slug:
158
+ click.echo(f"online runner: {has_online_runner(slug)}")
159
+
160
+
161
+ @ci.command(name="install-hook")
162
+ def install_hook():
163
+ """Install an opt-in pre-push hook that runs `mcli ci preflight`."""
164
+ hooks = Path(".git") / "hooks"
165
+ if not hooks.exists():
166
+ click.echo("Not a git repo (.git/hooks missing).")
167
+ raise SystemExit(1)
168
+ hook = hooks / "pre-push"
169
+ hook.write_text(PRE_PUSH_HOOK)
170
+ mode = hook.stat().st_mode
171
+ hook.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
172
+ click.echo(f"Installed pre-push hook at {hook}")
@@ -0,0 +1,26 @@
1
+ """Query GitHub for self-hosted runner availability via the gh CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+
8
+
9
+ def has_online_runner(repo_slug: str) -> bool:
10
+ """True if `repo_slug` (owner/name) has at least one online self-hosted runner."""
11
+ try:
12
+ proc = subprocess.run(
13
+ ["gh", "api", f"repos/{repo_slug}/actions/runners"],
14
+ capture_output=True,
15
+ text=True,
16
+ timeout=30,
17
+ )
18
+ except (FileNotFoundError, subprocess.TimeoutExpired):
19
+ return False
20
+ if proc.returncode != 0:
21
+ return False
22
+ try:
23
+ data = json.loads(proc.stdout or "{}")
24
+ except json.JSONDecodeError:
25
+ return False
26
+ return any(r.get("status") == "online" for r in data.get("runners", []))
@@ -0,0 +1,151 @@
1
+ """Transform GitHub Actions workflows for act-first CI on private repos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from io import StringIO
6
+ from pathlib import Path
7
+
8
+ from ruamel.yaml import YAML
9
+
10
+ MARKER = "mcli-ci: hosted-triggers-stripped"
11
+ SELF_HOSTED_FILENAME = "self-hosted-ci.yml"
12
+ HOSTED_PREFIXES = ("ubuntu", "macos", "windows")
13
+
14
+
15
+ def is_hosted_label(label: str) -> bool:
16
+ """True if a runs-on label names a GitHub-hosted runner image."""
17
+ return str(label).lower().startswith(HOSTED_PREFIXES)
18
+
19
+
20
+ def runs_on_is_hosted(runs_on) -> bool:
21
+ """Classify a job's runs-on. Conservative: unknown expressions count as hosted."""
22
+ if runs_on is None:
23
+ return False
24
+ if isinstance(runs_on, (list, tuple)):
25
+ labels = [str(x) for x in runs_on]
26
+ if any("self-hosted" in lbl for lbl in labels):
27
+ return False
28
+ return any(is_hosted_label(lbl) for lbl in labels)
29
+ text = str(runs_on)
30
+ if "self-hosted" in text:
31
+ return False
32
+ if "${{" in text:
33
+ return True # unknown matrix/expression -> assume hosted to stop cost
34
+ return is_hosted_label(text)
35
+
36
+
37
+ # NOTE: ruamel.yaml round-trip (YAML(), typ='rt') is the SAFE, correct loader here.
38
+ # It does NOT execute `!!python/object` tags like PyYAML's yaml.load(). Do not
39
+ # "fix" this to PyYAML safe_load — that would strip comments/formatting and break
40
+ # round-tripping. Round-trip preservation is a hard requirement of this transform.
41
+ def _yaml() -> YAML:
42
+ y = YAML()
43
+ y.version = (1, 2) # critical: keeps bare `on:` a string, not boolean True
44
+ y.preserve_quotes = True
45
+ y.width = 4096 # avoid reflowing long lines
46
+ y.indent(mapping=2, sequence=4, offset=2)
47
+ return y
48
+
49
+
50
+ def workflow_has_hosted_job(doc) -> bool:
51
+ """True if any job in the parsed workflow targets a GitHub-hosted runner."""
52
+ if not isinstance(doc, dict):
53
+ return False
54
+ jobs = doc.get("jobs") or {}
55
+ for job in jobs.values():
56
+ if isinstance(job, dict) and runs_on_is_hosted(job.get("runs-on")):
57
+ return True
58
+ return False
59
+
60
+
61
+ def strip_hosted_triggers(doc) -> bool:
62
+ """Remove push/pull_request from `on:`, ensure workflow_dispatch. Returns changed."""
63
+ on = doc.get("on")
64
+ if on is None:
65
+ return False
66
+ changed = False
67
+ if isinstance(on, dict):
68
+ for key in ("push", "pull_request"):
69
+ if key in on:
70
+ del on[key]
71
+ changed = True
72
+ if "workflow_dispatch" not in on:
73
+ on["workflow_dispatch"] = None
74
+ changed = True
75
+ elif isinstance(on, list):
76
+ for key in ("push", "pull_request"):
77
+ while key in on:
78
+ on.remove(key)
79
+ changed = True
80
+ if "workflow_dispatch" not in on:
81
+ on.append("workflow_dispatch")
82
+ changed = True
83
+ elif isinstance(on, str):
84
+ if on in ("push", "pull_request"):
85
+ doc["on"] = "workflow_dispatch"
86
+ changed = True
87
+ return changed
88
+
89
+
90
+ def transform_file(path: Path) -> bool:
91
+ """Strip hosted triggers in-place if the workflow has a hosted job. Idempotent."""
92
+ path = Path(path)
93
+ text = path.read_text()
94
+ if MARKER in text:
95
+ return False
96
+ yaml = _yaml()
97
+ doc = yaml.load(text)
98
+ if not workflow_has_hosted_job(doc):
99
+ return False
100
+ # `on:` is workflow-level. If a workflow mixes hosted and self-hosted jobs,
101
+ # stripping push/pull_request also removes the self-hosted job's auto-trigger.
102
+ # Acceptable here: the separate self-hosted-ci.yml provides the runner PR path
103
+ # when a runner exists. Re-add triggers by hand if a single workflow needs both.
104
+ strip_hosted_triggers(doc)
105
+ doc.yaml_set_start_comment(f"{MARKER}\n")
106
+ # Pinning version=(1,2) at load keeps `on` a string key (not YAML 1.1 bool True).
107
+ # Reset it before dumping so ruamel does not prepend a `%YAML 1.2` / `---` directive
108
+ # to the workflow file (the key is already a plain string in the loaded document).
109
+ yaml.version = None
110
+ buf = StringIO()
111
+ yaml.dump(doc, buf)
112
+ path.write_text(buf.getvalue())
113
+ return True
114
+
115
+
116
+ def render_self_hosted_workflow(test_command: str, with_pull_request: bool) -> str:
117
+ """Render the dormant self-hosted fallback workflow as YAML text."""
118
+ triggers = " workflow_dispatch:\n"
119
+ if with_pull_request:
120
+ triggers += " pull_request:\n"
121
+ ref = "${{ github.ref }}"
122
+ return (
123
+ f"# {MARKER}\n"
124
+ "name: self-hosted-ci\n"
125
+ "on:\n"
126
+ f"{triggers}"
127
+ "concurrency:\n"
128
+ f" group: self-hosted-ci-{ref}\n"
129
+ " cancel-in-progress: true\n"
130
+ "jobs:\n"
131
+ " test:\n"
132
+ " runs-on: [self-hosted, Linux, X64]\n"
133
+ " timeout-minutes: 30\n"
134
+ " steps:\n"
135
+ " - uses: actions/checkout@v4\n"
136
+ " - name: Run tests\n"
137
+ f" run: {test_command}\n"
138
+ )
139
+
140
+
141
+ def write_self_hosted_workflow(
142
+ workflows_dir: Path, test_command: str, with_pull_request: bool
143
+ ) -> bool:
144
+ """Write self-hosted-ci.yml if absent. Returns True if created."""
145
+ workflows_dir = Path(workflows_dir)
146
+ target = workflows_dir / SELF_HOSTED_FILENAME
147
+ if target.exists():
148
+ return False
149
+ workflows_dir.mkdir(parents=True, exist_ok=True)
150
+ target.write_text(render_self_hosted_workflow(test_command, with_pull_request))
151
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcli-framework
3
- Version: 8.0.48
3
+ Version: 8.0.50
4
4
  Summary: Portable workflow framework - transform any script into a versioned, schedulable command. Store in ~/.mcli/workflows/, version with lockfile, run as daemon or cron job.
5
5
  Author-email: Luis Fernandez de la Vara <luis@lefv.io>
6
6
  Maintainer-email: Luis Fernandez de la Vara <luis@lefv.io>
@@ -37,6 +37,7 @@ Requires-Python: >=3.10
37
37
  Description-Content-Type: text/markdown
38
38
  License-File: LICENSE
39
39
  Requires-Dist: click<9.0.0,>=8.1.7
40
+ Requires-Dist: ruamel.yaml<0.19,>=0.18
40
41
  Requires-Dist: rich<15.0.0,>=14.0.0
41
42
  Requires-Dist: requests<3.0.0,>=2.31.0
42
43
  Requires-Dist: tomli<3.0.0,>=2.2.1
@@ -165,6 +165,11 @@ src/mcli/workflow/__init__.py
165
165
  src/mcli/workflow/doc_convert.py
166
166
  src/mcli/workflow/lsh_integration.py
167
167
  src/mcli/workflow/workflow.py
168
+ src/mcli/workflow/ci/__init__.py
169
+ src/mcli/workflow/ci/act_runner.py
170
+ src/mcli/workflow/ci/ci.py
171
+ src/mcli/workflow/ci/runner_status.py
172
+ src/mcli/workflow/ci/workflow_transform.py
168
173
  src/mcli/workflow/daemon/__init__.py
169
174
  src/mcli/workflow/daemon/async_command_database.py
170
175
  src/mcli/workflow/daemon/async_process_manager.py
@@ -1,4 +1,5 @@
1
1
  click<9.0.0,>=8.1.7
2
+ ruamel.yaml<0.19,>=0.18
2
3
  rich<15.0.0,>=14.0.0
3
4
  requests<3.0.0,>=2.31.0
4
5
  tomli<3.0.0,>=2.2.1
File without changes