runlayer 0.22.2__tar.gz → 0.22.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. {runlayer-0.22.2 → runlayer-0.22.3}/PKG-INFO +3 -1
  2. {runlayer-0.22.2 → runlayer-0.22.3}/README.md +2 -0
  3. {runlayer-0.22.2 → runlayer-0.22.3}/pyproject.toml +1 -1
  4. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/deploy/docker_builder.py +33 -10
  5. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/deploy/service.py +9 -2
  6. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/env_substitution.py +1 -1
  7. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_deploy_service.py +32 -1
  8. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_docker_builder.py +105 -0
  9. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_env_substitution.py +14 -0
  10. {runlayer-0.22.2 → runlayer-0.22.3}/.gitignore +0 -0
  11. {runlayer-0.22.2 → runlayer-0.22.3}/.python-version +0 -0
  12. {runlayer-0.22.2 → runlayer-0.22.3}/AGENTS.md +0 -0
  13. {runlayer-0.22.2 → runlayer-0.22.3}/CLAUDE.md +0 -0
  14. {runlayer-0.22.2 → runlayer-0.22.3}/LICENSE +0 -0
  15. {runlayer-0.22.2 → runlayer-0.22.3}/Makefile +0 -0
  16. {runlayer-0.22.2 → runlayer-0.22.3}/development.md +0 -0
  17. {runlayer-0.22.2 → runlayer-0.22.3}/hooks/README.md +0 -0
  18. {runlayer-0.22.2 → runlayer-0.22.3}/hooks/__init__.py +0 -0
  19. {runlayer-0.22.2 → runlayer-0.22.3}/hooks/runlayer-hook.sh +0 -0
  20. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/__init__.py +0 -0
  21. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/api.py +0 -0
  22. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/__init__.py +0 -0
  23. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/auth.py +0 -0
  24. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/cache.py +0 -0
  25. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/deploy.py +0 -0
  26. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/hooks.py +0 -0
  27. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/logs.py +0 -0
  28. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/org_api_key.py +0 -0
  29. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/plugins.py +0 -0
  30. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/scan.py +0 -0
  31. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/setup.py +0 -0
  32. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/commands/skills.py +0 -0
  33. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/config.py +0 -0
  34. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/console.py +0 -0
  35. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/credential_store.py +0 -0
  36. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/deploy/__init__.py +0 -0
  37. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/deploy/config.py +0 -0
  38. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/index.html +0 -0
  39. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/logging.py +0 -0
  40. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/main.py +0 -0
  41. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/metrics.py +0 -0
  42. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/middleware.py +0 -0
  43. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/models.py +0 -0
  44. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/oauth.py +0 -0
  45. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/oauth_callback.py +0 -0
  46. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/paths.py +0 -0
  47. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/plugins/__init__.py +0 -0
  48. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/plugins/claude_manifest.py +0 -0
  49. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/plugins/discovery.py +0 -0
  50. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/plugins/installer.py +0 -0
  51. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/plugins/models.py +0 -0
  52. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/plugins/sync_engine.py +0 -0
  53. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/__init__.py +0 -0
  54. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/claude_code_plugins.py +0 -0
  55. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/clients.py +0 -0
  56. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/codex_plugins.py +0 -0
  57. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/config_parser.py +0 -0
  58. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/cursor_plugins.py +0 -0
  59. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/device.py +0 -0
  60. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/file_collector.py +0 -0
  61. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/openclaw_detector.py +0 -0
  62. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/opencode_plugins.py +0 -0
  63. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/plugin_scanner.py +0 -0
  64. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/project_scanner.py +0 -0
  65. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/service.py +0 -0
  66. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/scan/skill_scanner.py +0 -0
  67. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/AGENTS.md +0 -0
  68. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/CLAUDE.md +0 -0
  69. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/README.md +0 -0
  70. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/__init__.py +0 -0
  71. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/hashing.py +0 -0
  72. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/merkle.py +0 -0
  73. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/skill_id.py +0 -0
  74. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skill_identifier/types.py +0 -0
  75. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skills/__init__.py +0 -0
  76. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skills/discovery.py +0 -0
  77. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skills/installer.py +0 -0
  78. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skills/models.py +0 -0
  79. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/skills/sync_engine.py +0 -0
  80. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/symbols.py +0 -0
  81. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/sync.py +0 -0
  82. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/README.md +0 -0
  83. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/__init__.py +0 -0
  84. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/config.py +0 -0
  85. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/exceptions.py +0 -0
  86. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/proxy.py +0 -0
  87. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/verification/__init__.py +0 -0
  88. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/verification/base.py +0 -0
  89. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/verification/macos.py +0 -0
  90. {runlayer-0.22.2 → runlayer-0.22.3}/runlayer_cli/verified_local_proxy/verification/windows.py +0 -0
  91. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/__init__.py +0 -0
  92. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/cache/__init__.py +0 -0
  93. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/cache/test_cache.py +0 -0
  94. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/conftest.py +0 -0
  95. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/deploy/__init__.py +0 -0
  96. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/deploy/test_lifecycle.py +0 -0
  97. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/deploy/test_validate.py +0 -0
  98. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/claude_code.md +0 -0
  99. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/claude_desktop.md +0 -0
  100. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/codex.md +0 -0
  101. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/cursor.md +0 -0
  102. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/goose.md +0 -0
  103. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/opencode.md +0 -0
  104. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/vscode.md +0 -0
  105. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/windsurf.md +0 -0
  106. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/client_specs/zed.md +0 -0
  107. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/.claude-plugin/marketplace.json +0 -0
  108. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/.claude-plugin/plugin.json +0 -0
  109. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/.lsp.json +0 -0
  110. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/.mcp.json +0 -0
  111. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/README.md +0 -0
  112. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/agents/README.md +0 -0
  113. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/agents/code-reviewer.md +0 -0
  114. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/commands/review.md +0 -0
  115. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/config.json +0 -0
  116. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/hooks/hooks.json +0 -0
  117. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/hooks/validate.sh +0 -0
  118. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/notes.txt +0 -0
  119. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/scripts/deploy.sh +0 -0
  120. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/settings.json +0 -0
  121. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/skills/code-review/SKILL.md +0 -0
  122. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/skills/code-review/prompts.md +0 -0
  123. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/skills/ticket-triage/SKILL.md +0 -0
  124. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/skills/ticket-triage/helper.py +0 -0
  125. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/skills-v2/notes.md +0 -0
  126. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/skillsets/reference.md +0 -0
  127. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/fixtures/full_plugin/tool.ts +0 -0
  128. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/org_api_key/__init__.py +0 -0
  129. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/org_api_key/test_org_api_key.py +0 -0
  130. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/plugins/__init__.py +0 -0
  131. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/plugins/test_add.py +0 -0
  132. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/plugins/test_mcp_fallback.py +0 -0
  133. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/plugins/test_push.py +0 -0
  134. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/run/__init__.py +0 -0
  135. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/run/test_run.py +0 -0
  136. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/scan/__init__.py +0 -0
  137. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/scan/test_scan.py +0 -0
  138. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/setup/test_install_opencode.py +0 -0
  139. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/skills/__init__.py +0 -0
  140. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/skills/test_lifecycle.py +0 -0
  141. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/test_help_matrix.py +0 -0
  142. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/version/__init__.py +0 -0
  143. {runlayer-0.22.2 → runlayer-0.22.3}/tests/e2e/version/test_version.py +0 -0
  144. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/claude_code_config.json +0 -0
  145. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/claude_desktop_config.json +0 -0
  146. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/cursor_config.json +0 -0
  147. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/empty_config.json +0 -0
  148. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/goose_config.yaml +0 -0
  149. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/invalid_json.txt +0 -0
  150. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/sse_server_config.json +0 -0
  151. {runlayer-0.22.2 → runlayer-0.22.3}/tests/fixtures/vscode_config.json +0 -0
  152. {runlayer-0.22.2 → runlayer-0.22.3}/tests/skill_identifier/__init__.py +0 -0
  153. {runlayer-0.22.2 → runlayer-0.22.3}/tests/skill_identifier/test_merkle.py +0 -0
  154. {runlayer-0.22.2 → runlayer-0.22.3}/tests/skill_identifier/test_skill_id.py +0 -0
  155. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_api.py +0 -0
  156. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_auth.py +0 -0
  157. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_backwards_compatibility.py +0 -0
  158. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_cache.py +0 -0
  159. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_claude_code_plugins.py +0 -0
  160. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_claude_json_integration.py +0 -0
  161. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_cli.py +0 -0
  162. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_cli_backwards_compat.py +0 -0
  163. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_config.py +0 -0
  164. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_credential_store.py +0 -0
  165. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_cursor_plugins.py +0 -0
  166. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_hook_script.py +0 -0
  167. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_hooks_relay.py +0 -0
  168. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_logging.py +0 -0
  169. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_logs.py +0 -0
  170. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_metrics.py +0 -0
  171. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_middleware.py +0 -0
  172. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_oauth_browser_lock.py +0 -0
  173. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_oauth_token_storage.py +0 -0
  174. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_opencode_plugins.py +0 -0
  175. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_org_api_key_commands.py +0 -0
  176. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_plugin_scanner.py +0 -0
  177. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_plugins_commands.py +0 -0
  178. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_plugins_discovery.py +0 -0
  179. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_plugins_installer.py +0 -0
  180. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_plugins_sync.py +0 -0
  181. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_scan_clients.py +0 -0
  182. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_scan_device.py +0 -0
  183. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_scan_openclaw.py +0 -0
  184. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_scan_parser.py +0 -0
  185. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_scan_project.py +0 -0
  186. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_scan_service.py +0 -0
  187. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_scan_skills.py +0 -0
  188. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_setup_hooks.py +0 -0
  189. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_setup_install_claude_desktop.py +0 -0
  190. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_setup_install_config_formats.py +0 -0
  191. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_setup_install_local_servers.py +0 -0
  192. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_skills_commands.py +0 -0
  193. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_skills_discovery.py +0 -0
  194. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_skills_installer.py +0 -0
  195. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_skills_sync.py +0 -0
  196. {runlayer-0.22.2 → runlayer-0.22.3}/tests/test_symbols.py +0 -0
  197. {runlayer-0.22.2 → runlayer-0.22.3}/tests/verified_local_proxy/__init__.py +0 -0
  198. {runlayer-0.22.2 → runlayer-0.22.3}/tests/verified_local_proxy/test_config.py +0 -0
  199. {runlayer-0.22.2 → runlayer-0.22.3}/tests/verified_local_proxy/test_proxy.py +0 -0
  200. {runlayer-0.22.2 → runlayer-0.22.3}/tests/verified_local_proxy/test_verification/__init__.py +0 -0
  201. {runlayer-0.22.2 → runlayer-0.22.3}/tests/verified_local_proxy/test_verification/test_base.py +0 -0
  202. {runlayer-0.22.2 → runlayer-0.22.3}/tests/verified_local_proxy/test_verification/test_macos.py +0 -0
  203. {runlayer-0.22.2 → runlayer-0.22.3}/tests/verified_local_proxy/test_verification/test_windows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runlayer
3
- Version: 0.22.2
3
+ Version: 0.22.3
4
4
  Summary: A command-line interface for running MCP servers via HTTP transport
5
5
  Project-URL: Homepage, https://runlayer.com
6
6
  Project-URL: Documentation, https://docs.runlayer.com/
@@ -421,6 +421,8 @@ DATABASE_URL=postgres://localhost/db
421
421
  LOG_LEVEL=debug
422
422
  ```
423
423
 
424
+ **Limits:** Each `env` value in `runlayer.yaml` must be 2000 chars or less. If a value is too long, deploy validation fails and names the offending env var.
425
+
424
426
  **Note:** Variables from `.env` files override values from `os.environ`. The `$$VAR` syntax (double dollar sign) is reserved for backend variable substitution and will not be replaced by the CLI.
425
427
 
426
428
  ### `deploy validate` - Validate Configuration
@@ -186,6 +186,8 @@ DATABASE_URL=postgres://localhost/db
186
186
  LOG_LEVEL=debug
187
187
  ```
188
188
 
189
+ **Limits:** Each `env` value in `runlayer.yaml` must be 2000 chars or less. If a value is too long, deploy validation fails and names the offending env var.
190
+
189
191
  **Note:** Variables from `.env` files override values from `os.environ`. The `$$VAR` syntax (double dollar sign) is reserved for backend variable substitution and will not be replaced by the CLI.
190
192
 
191
193
  ### `deploy validate` - Validate Configuration
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "runlayer"
3
- version = "0.22.2"
3
+ version = "0.22.3"
4
4
  description = "A command-line interface for running MCP servers via HTTP transport"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -45,13 +45,31 @@ def check_docker_available() -> bool:
45
45
  return False
46
46
 
47
47
 
48
- def authenticate_ecr(credentials: ECRCredentials) -> None:
48
+ def get_registry_hostname(registry_url: str) -> str:
49
+ """Normalize registry URLs to the hostname Docker expects."""
50
+ return registry_url.replace("https://", "").replace("http://", "")
51
+
52
+
53
+ def get_registry_auth_config(credentials: ECRCredentials) -> dict[str, str]:
54
+ """Build request-scoped registry auth for Docker SDK calls."""
55
+ registry = get_registry_hostname(credentials.registry_url)
56
+ return {
57
+ "username": credentials.username,
58
+ "password": credentials.password,
59
+ "serveraddress": registry,
60
+ }
61
+
62
+
63
+ def authenticate_ecr(credentials: ECRCredentials) -> dict[str, str]:
49
64
  """
50
65
  Authenticate Docker with ECR using provided credentials.
51
66
 
52
67
  Args:
53
68
  credentials: ECR credentials from backend
54
69
 
70
+ Returns:
71
+ Auth config that can be reused by later Docker SDK calls
72
+
55
73
  Raises:
56
74
  DockerException: If authentication fails
57
75
  """
@@ -73,11 +91,8 @@ def authenticate_ecr(credentials: ECRCredentials) -> None:
73
91
  "Please get fresh credentials and try again."
74
92
  )
75
93
 
76
- client = docker.from_env()
77
-
78
- registry = credentials.registry_url.replace("https://", "").replace(
79
- "http://", ""
80
- )
94
+ registry = get_registry_hostname(credentials.registry_url)
95
+ auth_config = get_registry_auth_config(credentials)
81
96
 
82
97
  with Progress(
83
98
  SpinnerColumn(),
@@ -121,6 +136,7 @@ def authenticate_ecr(credentials: ECRCredentials) -> None:
121
136
  console.print("[dim]Retrying with Docker SDK instead...[/dim]")
122
137
 
123
138
  try:
139
+ client = docker.from_env()
124
140
  client.login(
125
141
  username=credentials.username,
126
142
  password=credentials.password,
@@ -133,6 +149,8 @@ def authenticate_ecr(credentials: ECRCredentials) -> None:
133
149
  except Exception as sdk_error:
134
150
  raise DockerException(f"Failed to authenticate: {sdk_error}")
135
151
 
152
+ return auth_config
153
+
136
154
  except APIError as e:
137
155
  raise DockerException(f"Failed to authenticate with ECR: {e}")
138
156
 
@@ -274,12 +292,13 @@ def tag_image(image_id: str, repository: str, tag: str) -> str:
274
292
  raise DockerException(f"Failed to tag image: {e}")
275
293
 
276
294
 
277
- def push_image(image_tag: str) -> str:
295
+ def push_image(image_tag: str, auth_config: Optional[dict[str, str]] = None) -> str:
278
296
  """
279
297
  Push a Docker image to a registry and get its digest.
280
298
 
281
299
  Args:
282
300
  image_tag: Full image tag (e.g., "registry/repo:tag")
301
+ auth_config: Optional request-scoped auth for the registry
283
302
 
284
303
  Returns:
285
304
  Image digest (SHA256 hash) of the pushed image
@@ -302,7 +321,11 @@ def push_image(image_tag: str) -> str:
302
321
  ) as progress:
303
322
  task = progress.add_task(description="Starting push...", total=None)
304
323
 
305
- for line in client.images.push(image_tag, stream=True, decode=True):
324
+ push_kwargs: dict[str, Any] = {"stream": True, "decode": True}
325
+ if auth_config:
326
+ push_kwargs["auth_config"] = auth_config
327
+
328
+ for line in client.images.push(image_tag, **push_kwargs):
306
329
  if "error" in line:
307
330
  error_msg = line.get("error", "Unknown error")
308
331
  console.print(f"\n[red]ERROR: {error_msg}[/red]\n")
@@ -387,7 +410,7 @@ def build_and_push(
387
410
  DockerPushError: If push fails
388
411
  """
389
412
  # Authenticate with ECR
390
- authenticate_ecr(credentials)
413
+ auth_config = authenticate_ecr(credentials)
391
414
 
392
415
  # Build the image with a local tag first
393
416
  local_tag = f"runlayer-build:{tag}"
@@ -404,6 +427,6 @@ def build_and_push(
404
427
  full_image_uri = tag_image(image_id, repository, tag)
405
428
 
406
429
  # Push to ECR and get digest
407
- image_digest = push_image(full_image_uri)
430
+ image_digest = push_image(full_image_uri, auth_config=auth_config)
408
431
 
409
432
  return full_image_uri, image_digest
@@ -293,11 +293,18 @@ def deploy_service(
293
293
  else:
294
294
  time_str = f"{seconds}s"
295
295
 
296
+ deployment_url = f"{host}/deploy/{deployment_id}"
297
+
296
298
  typer.secho(
297
299
  f"\n⏱️ Total deployment time: {time_str}",
298
300
  fg=typer.colors.CYAN,
299
301
  bold=True,
300
302
  )
303
+ typer.secho(
304
+ f"🔗 View deployment: {deployment_url}",
305
+ fg=typer.colors.CYAN,
306
+ bold=True,
307
+ )
301
308
 
302
309
 
303
310
  def _get_or_create_deployment(
@@ -485,9 +492,9 @@ def _push_to_ecr(
485
492
 
486
493
  typer.echo("Pushing image to ECR...\n")
487
494
  try:
488
- authenticate_ecr(ecr_creds)
495
+ auth_config = authenticate_ecr(ecr_creds)
489
496
  full_image_uri = tag_image(image_id, repository, deployment_id)
490
- image_digest = push_image(full_image_uri)
497
+ image_digest = push_image(full_image_uri, auth_config=auth_config)
491
498
 
492
499
  # Use digest-based reference for immutable deployments
493
500
  # This ensures ECS detects changes even with the same tag
@@ -57,7 +57,7 @@ def load_env_vars(
57
57
  # Load .env file if found (overrides os.environ)
58
58
  if env_path:
59
59
  # Load .env file into a dict (doesn't modify os.environ)
60
- dotenv_vars = dotenv_values(env_path)
60
+ dotenv_vars = dotenv_values(env_path, encoding="utf-8-sig")
61
61
 
62
62
  # Merge dotenv_vars into env_vars (dotenv file overrides os.environ)
63
63
  # Filter out None values (unset variables in .env file)
@@ -225,6 +225,11 @@ def test_push_to_ecr_success(mock_api_client):
225
225
  patch("runlayer_cli.deploy.service.push_image") as mock_push,
226
226
  patch("runlayer_cli.deploy.service.typer"),
227
227
  ):
228
+ mock_auth.return_value = {
229
+ "username": "AWS",
230
+ "password": "test-password",
231
+ "serveraddress": "123456789.dkr.ecr.us-east-1.amazonaws.com",
232
+ }
228
233
  mock_tag.return_value = (
229
234
  "123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo:test-deployment-id"
230
235
  )
@@ -238,7 +243,10 @@ def test_push_to_ecr_success(mock_api_client):
238
243
  assert result == expected_uri
239
244
  mock_auth.assert_called_once()
240
245
  mock_tag.assert_called_once()
241
- mock_push.assert_called_once()
246
+ mock_push.assert_called_once_with(
247
+ "123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo:test-deployment-id",
248
+ auth_config=mock_auth.return_value,
249
+ )
242
250
 
243
251
 
244
252
  def test_update_deployment_config_success(mock_api_client):
@@ -504,6 +512,29 @@ def test_validate_runlayer_yaml_config_http_error(mock_api_client):
504
512
  assert exc_info.value.exit_code == 1
505
513
 
506
514
 
515
+ def test_validate_runlayer_yaml_config_shows_backend_env_limit_error(mock_api_client):
516
+ """Test backend env limit error is surfaced unchanged."""
517
+ yaml_content = "name: test-service\nruntime: docker\nservice:\n port: 8000\n"
518
+
519
+ mock_response = ValidateYAMLResponse(
520
+ valid=False,
521
+ error="Configuration error: env.API_KEY is too long (2001 chars, max 2000)",
522
+ parsed_config=None,
523
+ )
524
+ mock_api_client.validate_yaml.return_value = mock_response
525
+
526
+ with (
527
+ patch("runlayer_cli.deploy.service.typer.echo") as _mock_echo,
528
+ patch("runlayer_cli.deploy.service.typer.secho") as mock_secho,
529
+ ):
530
+ with pytest.raises(typer.Exit) as exc_info:
531
+ _validate_runlayer_yaml_config(mock_api_client, yaml_content)
532
+
533
+ assert exc_info.value.exit_code == 1
534
+ rendered_calls = [str(call) for call in mock_secho.call_args_list]
535
+ assert any("env.API_KEY is too long" in call for call in rendered_calls)
536
+
537
+
507
538
  def test_validate_service_success():
508
539
  """Test successful validation service call."""
509
540
  with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
@@ -11,6 +11,7 @@ from runlayer_cli.deploy.docker_builder import (
11
11
  push_image,
12
12
  authenticate_ecr,
13
13
  DockerBuildError,
14
+ get_registry_auth_config,
14
15
  )
15
16
  from runlayer_cli.api import ECRCredentials
16
17
  import datetime
@@ -196,6 +197,38 @@ def test_push_image_success():
196
197
  mock_client.images.push.assert_called_once()
197
198
 
198
199
 
200
+ def test_push_image_uses_request_scoped_auth():
201
+ """Push should forward explicit auth to a fresh Docker client."""
202
+ auth_config = {
203
+ "username": "AWS",
204
+ "password": "test-password",
205
+ "serveraddress": "123456789.dkr.ecr.us-east-1.amazonaws.com",
206
+ }
207
+
208
+ with (
209
+ patch("runlayer_cli.deploy.docker_builder.docker") as mock_docker,
210
+ patch("runlayer_cli.deploy.docker_builder.console"),
211
+ ):
212
+ mock_client = MagicMock()
213
+ mock_docker.from_env.return_value = mock_client
214
+ mock_client.images.push.return_value = iter(
215
+ [{"aux": {"Digest": "sha256:def789ghi012"}}]
216
+ )
217
+
218
+ result = push_image(
219
+ "123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo:v1.0.0",
220
+ auth_config=auth_config,
221
+ )
222
+
223
+ assert result == "sha256:def789ghi012"
224
+ mock_client.images.push.assert_called_once_with(
225
+ "123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo:v1.0.0",
226
+ stream=True,
227
+ decode=True,
228
+ auth_config=auth_config,
229
+ )
230
+
231
+
199
232
  def test_authenticate_ecr_success():
200
233
  """Test successful ECR authentication."""
201
234
  # Create valid credentials
@@ -241,3 +274,75 @@ def test_authenticate_ecr_success():
241
274
  call_args = mock_subprocess_run.call_args
242
275
  assert "docker" in call_args[0][0]
243
276
  assert "login" in call_args[0][0]
277
+
278
+
279
+ def test_get_registry_auth_config_normalizes_registry_url():
280
+ """Auth config should use the hostname form Docker expects."""
281
+ expires_at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
282
+ hours=1
283
+ )
284
+ credentials = ECRCredentials(
285
+ username="AWS",
286
+ password="test-password",
287
+ registry_url="https://123456789.dkr.ecr.us-east-1.amazonaws.com",
288
+ repository_url="123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo",
289
+ expires_at=expires_at,
290
+ )
291
+
292
+ assert get_registry_auth_config(credentials) == {
293
+ "username": "AWS",
294
+ "password": "test-password",
295
+ "serveraddress": "123456789.dkr.ecr.us-east-1.amazonaws.com",
296
+ }
297
+
298
+
299
+ def test_authenticate_ecr_sdk_fallback_returns_auth_for_later_push():
300
+ """SDK fallback should still return request-scoped auth for fresh clients."""
301
+ expires_at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
302
+ hours=1
303
+ )
304
+ credentials = ECRCredentials(
305
+ username="AWS",
306
+ password="test-password",
307
+ registry_url="https://123456789.dkr.ecr.us-east-1.amazonaws.com",
308
+ repository_url="123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo",
309
+ expires_at=expires_at,
310
+ )
311
+
312
+ with (
313
+ patch("subprocess.run") as mock_subprocess_run,
314
+ patch("runlayer_cli.deploy.docker_builder.docker") as mock_docker,
315
+ patch("runlayer_cli.deploy.docker_builder.console"),
316
+ patch("runlayer_cli.deploy.docker_builder.Progress") as mock_progress,
317
+ ):
318
+ mock_client = MagicMock()
319
+ mock_docker.from_env.return_value = mock_client
320
+
321
+ mock_result = MagicMock()
322
+ mock_result.returncode = 1
323
+ mock_result.stderr = b"Error saving credentials: The stub received bad data"
324
+ mock_result.stdout = b""
325
+ mock_subprocess_run.return_value = mock_result
326
+
327
+ mock_progress_instance = MagicMock()
328
+ mock_progress_instance.__enter__ = MagicMock(
329
+ return_value=mock_progress_instance
330
+ )
331
+ mock_progress_instance.__exit__ = MagicMock(return_value=False)
332
+ mock_progress_instance.add_task = MagicMock(return_value="task-id")
333
+ mock_progress_instance.update = MagicMock()
334
+ mock_progress.return_value = mock_progress_instance
335
+
336
+ auth_config = authenticate_ecr(credentials)
337
+
338
+ assert auth_config == {
339
+ "username": "AWS",
340
+ "password": "test-password",
341
+ "serveraddress": "123456789.dkr.ecr.us-east-1.amazonaws.com",
342
+ }
343
+ mock_client.login.assert_called_once_with(
344
+ username="AWS",
345
+ password="test-password",
346
+ registry="123456789.dkr.ecr.us-east-1.amazonaws.com",
347
+ reauth=True,
348
+ )
@@ -260,6 +260,20 @@ with_dash_default: ${ANOTHER_VAR-another_default}
260
260
  assert "with_dash_default: another_default" in result # Uses default
261
261
 
262
262
 
263
+ def test_load_env_vars_with_utf8_bom():
264
+ """Test that UTF-8 BOM is stripped from .env files (PowerShell default)."""
265
+ with tempfile.NamedTemporaryFile(mode="wb", suffix=".env", delete=False) as f:
266
+ f.write(b"\xef\xbb\xbfFIRST_VAR=first_value\nSECOND_VAR=second_value\n")
267
+ env_file_path = f.name
268
+
269
+ try:
270
+ env_vars = load_env_vars(env_file_path)
271
+ assert env_vars["FIRST_VAR"] == "first_value"
272
+ assert env_vars["SECOND_VAR"] == "second_value"
273
+ finally:
274
+ Path(env_file_path).unlink()
275
+
276
+
263
277
  def test_substitute_env_vars_no_substitution_needed():
264
278
  """Test YAML with no variable references."""
265
279
  yaml_content = "name: my-service\nversion: 1.0.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes