openrunner-sdk 2.41.6__tar.gz → 2.41.8__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 (233) hide show
  1. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/.gitignore +2 -0
  2. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/PKG-INFO +6 -6
  3. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/README.md +1 -1
  4. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/CLAUDE.md +42 -0
  5. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/__init__.py +26 -3
  6. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/artifact.py +49 -0
  7. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/cli.py +159 -37
  8. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/datasets.py +186 -4
  9. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/__init__.py +55 -18
  10. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/api_integration.py +29 -3
  11. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/runner.py +14 -2
  12. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/install_commands.py +405 -0
  13. openrunner_sdk-2.41.8/openrunner/llms-full.txt +258 -0
  14. openrunner_sdk-2.41.8/openrunner/llms.py +49 -0
  15. openrunner_sdk-2.41.8/openrunner/llms.txt +42 -0
  16. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/migrate.py +29 -7
  17. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/run.py +22 -3
  18. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/sweep.py +31 -27
  19. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/pyproject.toml +10 -4
  20. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/integration/test_artifact_round_trip.py +1 -1
  21. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_artifact.py +59 -0
  22. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_datasets_module.py +113 -1
  23. openrunner_sdk-2.41.8/tests/test_gpu/test_engine_serverside.py +62 -0
  24. openrunner_sdk-2.41.8/tests/test_gpu/test_engine_stream_keepalive.py +61 -0
  25. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_gpu_cluster.py +46 -8
  26. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_sdk_gpu_api.py +20 -3
  27. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_init_facade.py +8 -2
  28. openrunner_sdk-2.41.8/tests/test_llms.py +43 -0
  29. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_machine_migrate.py +47 -3
  30. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_run_module.py +40 -0
  31. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j3.py +5 -1
  32. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sweep.py +55 -2
  33. openrunner_sdk-2.41.6/tests/test_gpu/test_engine_serverside.py +0 -30
  34. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/=6.0 +0 -0
  35. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/=8.1 +0 -0
  36. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/_native.py +0 -0
  37. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/_retry_transport.py +0 -0
  38. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/_safepath.py +0 -0
  39. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/_secret_rules.py +0 -0
  40. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/api_client.py +0 -0
  41. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/autoresearch.py +0 -0
  42. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/buffer.py +0 -0
  43. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/cache.py +0 -0
  44. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/config.py +0 -0
  45. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/cost.py +0 -0
  46. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/dataset.py +0 -0
  47. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/environment.py +0 -0
  48. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/evaluation.py +0 -0
  49. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/feedback.py +0 -0
  50. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/git_info.py +0 -0
  51. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/cost_guard.py +0 -0
  52. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/gpu_types.py +0 -0
  53. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/price_compare.py +0 -0
  54. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/provider_base.py +0 -0
  55. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/__init__.py +0 -0
  56. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/forge_provider.py +0 -0
  57. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/lambda_cloud.py +0 -0
  58. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/modal_provider.py +0 -0
  59. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/ovhcloud.py +0 -0
  60. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/runpod.py +0 -0
  61. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/providers/vastai.py +0 -0
  62. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/gpu/workspace_sync.py +0 -0
  63. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/guardrails.py +0 -0
  64. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/handover.py +0 -0
  65. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/hooks/patent-lab-post-commit +0 -0
  66. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/__init__.py +0 -0
  67. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/accelerate.py +0 -0
  68. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/anthropic_tracer.py +0 -0
  69. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/catboost.py +0 -0
  70. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/diffusers.py +0 -0
  71. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/fastai.py +0 -0
  72. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/forced_alignment.py +0 -0
  73. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/gladia.py +0 -0
  74. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/gymnasium.py +0 -0
  75. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/huggingface.py +0 -0
  76. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/hydra.py +0 -0
  77. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/ignite.py +0 -0
  78. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/jax.py +0 -0
  79. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/keras.py +0 -0
  80. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/langchain.py +0 -0
  81. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/lightgbm.py +0 -0
  82. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/lightning.py +0 -0
  83. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/llamaindex.py +0 -0
  84. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/openai_finetune.py +0 -0
  85. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/openai_tracer.py +0 -0
  86. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/optuna.py +0 -0
  87. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/pytorch.py +0 -0
  88. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/sb3.py +0 -0
  89. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/sklearn.py +0 -0
  90. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/skypilot.py +0 -0
  91. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/tensorflow.py +0 -0
  92. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/trl.py +0 -0
  93. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/tts.py +0 -0
  94. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/ultralytics.py +0 -0
  95. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/voice_agent.py +0 -0
  96. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/whisper.py +0 -0
  97. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/integration/xgboost.py +0 -0
  98. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/launch.py +0 -0
  99. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/machine.py +0 -0
  100. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/mcp_server.py +0 -0
  101. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/media.py +0 -0
  102. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/model.py +0 -0
  103. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/model_card.py +0 -0
  104. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/offline.py +0 -0
  105. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/patent_scan.py +0 -0
  106. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/pii.py +0 -0
  107. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/plot.py +0 -0
  108. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/projects.py +0 -0
  109. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/prompt.py +0 -0
  110. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/query_api.py +0 -0
  111. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/redact.py +0 -0
  112. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/reload.py +0 -0
  113. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/research.py +0 -0
  114. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/research_policy.py +0 -0
  115. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/scorers.py +0 -0
  116. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/sender.py +0 -0
  117. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/session.py +0 -0
  118. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/settings.py +0 -0
  119. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/summary.py +0 -0
  120. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/sync.py +0 -0
  121. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/system_metrics.py +0 -0
  122. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/tensorboard.py +0 -0
  123. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/tools/__init__.py +0 -0
  124. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/tools/patent_report.py +0 -0
  125. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/trace.py +0 -0
  126. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/transcript_formatter.py +0 -0
  127. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/wal.py +0 -0
  128. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/wandb_compat/__init__.py +0 -0
  129. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/wandb_compat/_shim.py +0 -0
  130. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/openrunner/wer.py +0 -0
  131. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/scripts/gen_betterleaks_rules.py +0 -0
  132. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/__init__.py +0 -0
  133. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/conftest.py +0 -0
  134. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/config.json +0 -0
  135. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/metadata.json +0 -0
  136. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/metrics.jsonl +0 -0
  137. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-aaa/summary.json +0 -0
  138. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/manifest.json +0 -0
  139. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/model-checkpoint/weights.bin +0 -0
  140. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/manifest.json +0 -0
  141. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/artifacts/sample-image/preview.png +0 -0
  142. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/config.json +0 -0
  143. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/metadata.json +0 -0
  144. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/metrics.jsonl +0 -0
  145. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/fixtures/wandb-export-mini/run-bbb/summary.json +0 -0
  146. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/integration/__init__.py +0 -0
  147. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/integration/conftest.py +0 -0
  148. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_alert.py +0 -0
  149. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_aliases.py +0 -0
  150. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_api_client.py +0 -0
  151. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_api_client_methods.py +0 -0
  152. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_api_client_transport.py +0 -0
  153. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_buffer.py +0 -0
  154. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cache.py +0 -0
  155. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_class_scorers.py +0 -0
  156. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cli.py +0 -0
  157. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cli_artifact_sweep.py +0 -0
  158. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cli_datasets.py +0 -0
  159. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cli_gpu_registry_deploy.py +0 -0
  160. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cli_infra.py +0 -0
  161. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cli_remainder.py +0 -0
  162. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_cli_topup.py +0 -0
  163. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_config.py +0 -0
  164. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_evaluation.py +0 -0
  165. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_finish.py +0 -0
  166. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_git_info.py +0 -0
  167. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/__init__.py +0 -0
  168. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/conftest.py +0 -0
  169. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_cost_guard.py +0 -0
  170. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_forge.py +0 -0
  171. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_governed_jobs.py +0 -0
  172. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_gpu_types.py +0 -0
  173. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_lambda.py +0 -0
  174. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_modal.py +0 -0
  175. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_ovh_training.py +0 -0
  176. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_ovhcloud.py +0 -0
  177. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_price_compare.py +0 -0
  178. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_provider_base.py +0 -0
  179. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_runner.py +0 -0
  180. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_runpod.py +0 -0
  181. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_vastai.py +0 -0
  182. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu/test_workspace_sync.py +0 -0
  183. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_gpu_modal_creds.py +0 -0
  184. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_init.py +0 -0
  185. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_install_commands.py +0 -0
  186. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_fastai.py +0 -0
  187. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_huggingface.py +0 -0
  188. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_keras.py +0 -0
  189. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_langchain.py +0 -0
  190. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_lightning.py +0 -0
  191. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_pytorch.py +0 -0
  192. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_setA.py +0 -0
  193. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_setB.py +0 -0
  194. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_setC.py +0 -0
  195. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_sklearn.py +0 -0
  196. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_integration_xgboost.py +0 -0
  197. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_launch.py +0 -0
  198. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_log.py +0 -0
  199. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_log_code.py +0 -0
  200. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_mcp_decisions.py +0 -0
  201. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_mcp_monitor.py +0 -0
  202. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_mcp_predictions.py +0 -0
  203. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_mcp_server.py +0 -0
  204. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_media.py +0 -0
  205. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_media_extra.py +0 -0
  206. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_migrate.py +0 -0
  207. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_offline.py +0 -0
  208. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_offline_sync.py +0 -0
  209. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_patent_scan.py +0 -0
  210. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_pii.py +0 -0
  211. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_plot.py +0 -0
  212. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_plot_media_extra2.py +0 -0
  213. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_prompt_module.py +0 -0
  214. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_pure_modules.py +0 -0
  215. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_query_api.py +0 -0
  216. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_redact.py +0 -0
  217. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_reload_module.py +0 -0
  218. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_research_module.py +0 -0
  219. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_resume.py +0 -0
  220. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_retry_transport.py +0 -0
  221. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j1.py +0 -0
  222. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j2.py +0 -0
  223. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sdk_cluster_j4.py +0 -0
  224. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sdk_core_edges.py +0 -0
  225. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sdk_features.py +0 -0
  226. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_sender.py +0 -0
  227. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_session_module.py +0 -0
  228. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_session_sync.py +0 -0
  229. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_summary.py +0 -0
  230. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_system_metrics.py +0 -0
  231. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_topup_hi.py +0 -0
  232. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_trace.py +0 -0
  233. {openrunner_sdk-2.41.6 → openrunner_sdk-2.41.8}/tests/test_wandb_compat.py +0 -0
@@ -153,6 +153,8 @@ activemq-data/
153
153
  # Environments
154
154
  .env
155
155
  .envrc
156
+ .forge.env
157
+ .forge-venv/
156
158
 
157
159
  # Docker secrets (real values — only examples tracked)
158
160
  secrets/
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: openrunner-sdk
3
- Version: 2.41.6
3
+ Version: 2.41.8
4
4
  Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
5
- Project-URL: Homepage, https://github.com/jqueguiner/openrunner
6
- Project-URL: Repository, https://github.com/jqueguiner/openrunner
7
- Project-URL: Issues, https://github.com/jqueguiner/openrunner/issues
5
+ Project-URL: Homepage, https://github.com/gladiaio/openrunner
6
+ Project-URL: Repository, https://github.com/gladiaio/openrunner
7
+ Project-URL: Issues, https://github.com/gladiaio/openrunner/issues
8
8
  Author-email: JL Queguiner <jl@gladia.io>
9
9
  License: MIT
10
10
  Keywords: experiment-tracking,invention-disclosure,machine-learning,ml,mlops,patent,wandb
@@ -656,7 +656,7 @@ W&B env vars (`WANDB_API_KEY`, `WANDB_BASE_URL`) are also supported as fallback
656
656
 
657
657
  ## Self-Hosting
658
658
 
659
- OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/jqueguiner/openrunner) for server setup with Docker Compose.
659
+ OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/gladiaio/openrunner) for server setup with Docker Compose.
660
660
 
661
661
  ## License
662
662
 
@@ -541,7 +541,7 @@ W&B env vars (`WANDB_API_KEY`, `WANDB_BASE_URL`) are also supported as fallback
541
541
 
542
542
  ## Self-Hosting
543
543
 
544
- OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/jqueguiner/openrunner) for server setup with Docker Compose.
544
+ OpenRunner is designed to be self-hosted. See the [main repo](https://github.com/gladiaio/openrunner) for server setup with Docker Compose.
545
545
 
546
546
  ## License
547
547
 
@@ -176,3 +176,45 @@ When the user asks about papers, research, or sessions:
176
176
  - Use `mcp__openrunner__openrunner_search_research` for research plans
177
177
  - Use `mcp__openrunner__openrunner_search_sessions` for AI session history
178
178
  - Use `mcp__openrunner__openrunner_update_paper` to modify paper sections
179
+
180
+ ## Container registry & serverless deployment
181
+
182
+ Register a container registry (Docker Hub, GHCR, Google Artifact Registry, AWS
183
+ ECR, Azure ACR, OVH/Harbor) once; the upstream credential is stored encrypted
184
+ server-side and the OpenRunner **broker** proxies pull/push — the client never
185
+ holds the upstream key.
186
+
187
+ ```
188
+ openrunner registry add --name "Harbor ML" --url REG_URL --username U --token T --namespace NS
189
+ openrunner registry set-default <id> # broker fallback when the repo path is ambiguous
190
+ openrunner registry login # docker login the broker (short-lived token)
191
+ docker push <registry_host>/<namespace>/<project-slug>:<user>-<name>-<tag>
192
+ ```
193
+
194
+ Repo convention (`canonical_image_name`): `{namespace}/{project_slug}`, tag leads
195
+ with the user — e.g. `model-team/ner-email:jl-int8-v1`.
196
+
197
+ **Images link to a project = the deploy gate.** An image pushed **through the
198
+ broker** is **auto-linked** to its project on the manifest PUT (project resolved
199
+ from the repo's project-slug segment), so it appears under the project's
200
+ **Registry** tab (artifact page) and the project-overview **Registry** panel with
201
+ no manual step. Direct-to-upstream pushes (or registries that hide their catalog,
202
+ e.g. OVH Harbor robots can't list `_catalog`) must be linked explicitly:
203
+
204
+ ```
205
+ openrunner deploy link IMAGE -p ORG/NAME [--registry-id UUID]
206
+ openrunner deploy images -p ORG/NAME # list linked images
207
+ openrunner deploy gpus -p ORG/NAME # deployable GPU offers
208
+ openrunner deploy create ... ; openrunner deploy scale <id> N ; openrunner deploy call <id> ...
209
+ ```
210
+ API: `GET/POST /projects/{id}/images`, `.../deployments`. SDK:
211
+ `client.list_project_images(pid)`, `client.link_project_image(pid, image, registry_id=)`.
212
+ Slash: `/openrunner:registry`, `/openrunner:deploy`, `/openrunner:build-api`.
213
+
214
+ ## Full LLM map: llms.txt
215
+
216
+ The complete end-to-end, LLM-oriented map of OpenRunner (dataset → run
217
+ instrumentation → GPU training → sweep → registry → deploy → handover) lives at
218
+ **https://openrun.gladia.io/llms-full.txt** (source: `src/web/public/llms-full.txt`),
219
+ with a short index at **/llms.txt**. Read the full doc to ground yourself in the
220
+ real commands + conventions before driving OpenRunner end-to-end for a new project.
@@ -93,6 +93,7 @@ import openrunner.datasets as datasets
93
93
  from openrunner.datasets import (
94
94
  DatasetInfo,
95
95
  DatasetsClient,
96
+ DeriveIncomplete,
96
97
  create_dataset,
97
98
  dataset_info,
98
99
  index_dataset,
@@ -101,6 +102,7 @@ from openrunner.datasets import (
101
102
  link_project,
102
103
  load_dataset,
103
104
  add_derivation,
105
+ derive_by_ids,
104
106
  derive_by_query,
105
107
  derived_rows,
106
108
  keyword_expand,
@@ -112,11 +114,20 @@ from openrunner.datasets import (
112
114
  word_lookup,
113
115
  set_dataset_card,
114
116
  set_storage_policy,
117
+ update_dataset,
115
118
  upload_file,
116
119
  upload_sharded,
117
120
  upload_folder,
118
121
  )
119
122
  from openrunner.config import Config
123
+ from openrunner.llms import (
124
+ llms_txt,
125
+ llms_txt_path,
126
+ LLMS_TXT_URL,
127
+ LLMS_FULL_TXT_URL,
128
+ LLMS_TXT_PATH,
129
+ LLMS_FULL_TXT_PATH,
130
+ )
120
131
  from openrunner.model import Model
121
132
  from openrunner.prompt import MessagesPrompt, Prompt, StringPrompt
122
133
  from openrunner.launch import LaunchJob, launch, from_run as _launch_from_run
@@ -843,11 +854,21 @@ def alert(
843
854
  return None
844
855
 
845
856
 
846
- def log_artifact(artifact: Artifact) -> dict[str, Any] | None:
857
+ def log_artifact(
858
+ artifact: "Artifact | str | Path",
859
+ *,
860
+ name: str | None = None,
861
+ type: str | None = None,
862
+ aliases: list[str] | None = None,
863
+ ) -> dict[str, Any] | None:
847
864
  """Upload an artifact to the active run. Never raises.
848
865
 
849
866
  Args:
850
- artifact: The Artifact object with files added via add_file/add_dir.
867
+ artifact: An Artifact with files added via add_file/add_dir, OR a path
868
+ to a file or directory (W&B-style), built into an Artifact for you.
869
+ name: Artifact name when ``artifact`` is a path.
870
+ type: Advisory type when ``artifact`` is a path.
871
+ aliases: Aliases to set on the created version.
851
872
 
852
873
  Returns:
853
874
  Server response dict with version info, or None on failure.
@@ -856,7 +877,9 @@ def log_artifact(artifact: Artifact) -> dict[str, Any] | None:
856
877
  if _active_run is None:
857
878
  logger.warning("openrunner.log_artifact(): no active run")
858
879
  return None
859
- return _active_run.log_artifact(artifact)
880
+ return _active_run.log_artifact(
881
+ artifact, name=name, type=type, aliases=aliases
882
+ )
860
883
  except Exception as e:
861
884
  logger.warning("openrunner.log_artifact() failed: %s", e)
862
885
  return None
@@ -29,6 +29,55 @@ def _sha256_file(path: Path, chunk_size: int = 8192) -> str:
29
29
  return h.hexdigest()
30
30
 
31
31
 
32
+ def coerce_artifact(
33
+ source: "Artifact | str | Path",
34
+ *,
35
+ name: str | None = None,
36
+ type: str | None = None,
37
+ description: str | None = None,
38
+ metadata: dict[str, Any] | None = None,
39
+ ) -> "Artifact":
40
+ """Return an Artifact, building one from a path if that's what was given.
41
+
42
+ ``log_artifact`` historically took a fully built ``Artifact``. Users coming
43
+ from W&B -- which this repo actively courts, see
44
+ ``examples/migrate-from-wandb/`` -- write the path form instead::
45
+
46
+ run.log_artifact("out/final", name="my-model", type="model")
47
+
48
+ and used to get ``TypeError: unexpected keyword argument 'name'``. Accept
49
+ both. A directory becomes ``add_dir``, a file ``add_file``; ``name``
50
+ defaults to the path's own name so the common case needs no kwargs.
51
+ """
52
+ if isinstance(source, Artifact):
53
+ # Already built: kwargs only fill gaps, they never silently override
54
+ # what the caller put on the object.
55
+ if name and not source.name:
56
+ source.name = name
57
+ if type and source.type is None:
58
+ source.type = type
59
+ return source
60
+
61
+ path = Path(source)
62
+ if not path.exists():
63
+ raise FileNotFoundError(f"artifact path does not exist: {path}")
64
+ art = Artifact(
65
+ name or path.name or path.stem,
66
+ type,
67
+ description,
68
+ metadata,
69
+ )
70
+ if path.is_dir():
71
+ # Existing add_dir semantics: files are prefixed with the directory
72
+ # name. Kept as-is so a coerced artifact lays out identically to a
73
+ # hand-built one -- changing it would silently move every existing
74
+ # caller's files.
75
+ art.add_dir(str(path))
76
+ else:
77
+ art.add_file(str(path))
78
+ return art
79
+
80
+
32
81
  class Artifact:
33
82
  """An untyped, versioned bucket of files for experiment tracking.
34
83
 
@@ -145,6 +145,45 @@ def _save_settings(settings: dict[str, Any]) -> None:
145
145
  )
146
146
 
147
147
 
148
+ def _resolve_api_key(settings: dict[str, Any] | None = None) -> str:
149
+ """API key with the same precedence the SDK uses: env, then the
150
+ session-synced ~/.openrunner/session_config.json, then settings.json.
151
+
152
+ Historically the CLI read settings.json FIRST and most subcommands never
153
+ looked at session_config at all — so a stale key in settings.json made
154
+ `openrunner catalog ...` fail with "API key has expired" while
155
+ `openrunner datasets ...` (SDK-backed) worked with the fresh session key.
156
+ """
157
+ try:
158
+ from openrunner.settings import _global_creds
159
+ session = _global_creds()
160
+ except Exception:
161
+ session = {}
162
+ return (
163
+ os.environ.get("OPENRUNNER_API_KEY")
164
+ or os.environ.get("OPENRUN_API_KEY")
165
+ or session.get("api_key")
166
+ or (settings or _load_settings()).get("api_key")
167
+ or ""
168
+ )
169
+
170
+
171
+ def _resolve_base_url(settings: dict[str, Any] | None = None) -> str:
172
+ """Base URL with the same precedence chain as ``_resolve_api_key``."""
173
+ try:
174
+ from openrunner.settings import _global_creds
175
+ session = _global_creds()
176
+ except Exception:
177
+ session = {}
178
+ return (
179
+ os.environ.get("OPENRUNNER_BASE_URL")
180
+ or os.environ.get("OPENRUN_BASE_URL")
181
+ or session.get("base_url")
182
+ or (settings or _load_settings()).get("base_url")
183
+ or ""
184
+ )
185
+
186
+
148
187
  # ---------------------------------------------------------------------------
149
188
  # CLI group
150
189
  # ---------------------------------------------------------------------------
@@ -420,7 +459,7 @@ def sync(
420
459
  --sync-tensorboard Import TensorBoard event files as metrics
421
460
  """
422
461
  settings = _load_settings()
423
- api_key = settings.get("api_key")
462
+ api_key = _resolve_api_key(settings)
424
463
  base_url = settings.get("base_url", "http://localhost:8000")
425
464
 
426
465
  if not api_key:
@@ -545,7 +584,7 @@ def ls(project: str | None) -> None:
545
584
  in the specified project.
546
585
  """
547
586
  settings = _load_settings()
548
- api_key = settings.get("api_key")
587
+ api_key = _resolve_api_key(settings)
549
588
  base_url = settings.get("base_url", "http://localhost:8000")
550
589
 
551
590
  if not api_key:
@@ -607,7 +646,7 @@ def _get_client() -> "APIClient":
607
646
  import os
608
647
 
609
648
  settings = _load_settings()
610
- api_key = os.environ.get("OPENRUNNER_API_KEY") or settings.get("api_key")
649
+ api_key = _resolve_api_key(settings)
611
650
  base_url = os.environ.get("OPENRUNNER_BASE_URL") or settings.get(
612
651
  "base_url", "http://localhost:8000"
613
652
  )
@@ -863,8 +902,11 @@ def artifact_list(project: str | None, project_opt: str | None) -> None:
863
902
 
864
903
  @artifact.command("get")
865
904
  @click.argument("name")
866
- @click.option("--root", "-r", default=".", help="Download directory (default: .)")
867
- def artifact_get(name: str, root: str) -> None:
905
+ @click.option("--root", "-r", "--output", "-o", "root", default=".",
906
+ help="Download directory (default: .). --output/-o is an alias.")
907
+ @click.option("--project", "-p", default=None,
908
+ help="Scope the lookup to ORG/PROJECT (else searches all accessible projects)")
909
+ def artifact_get(name: str, root: str, project: str | None = None) -> None:
868
910
  """Download an artifact to a local directory.
869
911
 
870
912
  NAME can use colon syntax: my-dataset:latest or my-model:v3.
@@ -886,8 +928,16 @@ def artifact_get(name: str, root: str) -> None:
886
928
  else:
887
929
  alias = ref
888
930
 
889
- # Find artifact by listing all accessible projects
931
+ # Find artifact by listing accessible projects. When --project is given we
932
+ # only search that one (faster, and disambiguates a name shared across
933
+ # projects); otherwise fall back to scanning every accessible project.
890
934
  projects = client.list_projects()
935
+ if project:
936
+ projects = [
937
+ p for p in projects
938
+ if project in (p.get("id"), p.get("name"), p.get("slug"),
939
+ f"{p.get('org_slug')}/{p.get('slug')}")
940
+ ] or projects
891
941
  artifact_detail = None
892
942
  target_version_id = None
893
943
 
@@ -1135,9 +1185,12 @@ def cache_cleanup(max_size: float | None) -> None:
1135
1185
 
1136
1186
  @main.command("download-artifact")
1137
1187
  @click.argument("name")
1138
- @click.option("--root", "-r", default=".", help="Download directory (default: .)")
1188
+ @click.option("--root", "-r", "--output", "-o", "root", default=".",
1189
+ help="Download directory (default: .). --output/-o is an alias.")
1190
+ @click.option("--project", "-p", default=None,
1191
+ help="Scope the lookup to ORG/PROJECT (else searches all accessible projects)")
1139
1192
  @click.pass_context
1140
- def download_artifact_cmd(ctx: click.Context, name: str, root: str) -> None:
1193
+ def download_artifact_cmd(ctx: click.Context, name: str, root: str, project: str | None) -> None:
1141
1194
  """Download an artifact to a local directory.
1142
1195
 
1143
1196
  NAME can use colon syntax: my-dataset:latest or my-model:v3.
@@ -1145,7 +1198,7 @@ def download_artifact_cmd(ctx: click.Context, name: str, root: str) -> None:
1145
1198
 
1146
1199
  This is a top-level alias for `openrunner artifact get`.
1147
1200
  """
1148
- ctx.invoke(artifact_get, name=name, root=root)
1201
+ ctx.invoke(artifact_get, name=name, root=root, project=project)
1149
1202
 
1150
1203
 
1151
1204
  @main.command("upload-artifact")
@@ -1520,7 +1573,7 @@ def launch(
1520
1573
  from openrunner.launch import launch as launch_fn
1521
1574
 
1522
1575
  settings = _load_settings()
1523
- api_key = settings.get("api_key")
1576
+ api_key = _resolve_api_key(settings)
1524
1577
  if not api_key:
1525
1578
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
1526
1579
  raise SystemExit(1)
@@ -1586,7 +1639,7 @@ def launch(
1586
1639
  def jobs(project: str, state: str | None) -> None:
1587
1640
  """List jobs for a project."""
1588
1641
  settings = _load_settings()
1589
- api_key = settings.get("api_key")
1642
+ api_key = _resolve_api_key(settings)
1590
1643
  base_url = settings.get("base_url", "http://localhost:8000")
1591
1644
 
1592
1645
  if not api_key:
@@ -1642,7 +1695,7 @@ def job_logs(job_id: str) -> None:
1642
1695
  JOB_ID is the UUID of the job.
1643
1696
  """
1644
1697
  settings = _load_settings()
1645
- api_key = settings.get("api_key")
1698
+ api_key = _resolve_api_key(settings)
1646
1699
  base_url = settings.get("base_url", "http://localhost:8000")
1647
1700
 
1648
1701
  if not api_key:
@@ -2230,7 +2283,7 @@ def docker(
2230
2283
  import subprocess
2231
2284
 
2232
2285
  settings = _load_settings()
2233
- api_key = os.environ.get("OPENRUNNER_API_KEY") or settings.get("api_key")
2286
+ api_key = _resolve_api_key(settings)
2234
2287
  base_url = os.environ.get("OPENRUNNER_BASE_URL") or settings.get(
2235
2288
  "base_url", "http://localhost:8000"
2236
2289
  )
@@ -3221,6 +3274,24 @@ def version_cmd(check: bool) -> None:
3221
3274
  click.echo("Up to date.")
3222
3275
 
3223
3276
 
3277
+ @main.command("llms")
3278
+ @click.option("--full", is_flag=True, help="Print the complete guide (llms-full.txt) instead of the index")
3279
+ @click.option("--path", "show_path", is_flag=True, help="Print the bundled file path instead of its content")
3280
+ def llms_cmd(full: bool, show_path: bool) -> None:
3281
+ """Print the bundled LLM-oriented OpenRunner doc.
3282
+
3283
+ The short index by default, or the complete end-to-end guide with --full.
3284
+ Bundled in the package (works offline); also served at /llms.txt and
3285
+ /llms-full.txt on the instance.
3286
+ """
3287
+ from openrunner import llms as _llms
3288
+
3289
+ if show_path:
3290
+ click.echo(_llms.llms_txt_path(full=full))
3291
+ else:
3292
+ click.echo(_llms.llms_txt(full=full))
3293
+
3294
+
3224
3295
  @main.command("_update_nudge", hidden=True)
3225
3296
  def update_nudge_cmd() -> None:
3226
3297
  """Print ONE update-hint line if a newer SDK exists, else nothing. Cheap:
@@ -3547,8 +3618,8 @@ def catalog_tables(org: str):
3547
3618
  """List catalog tables and their row counts."""
3548
3619
  settings = _load_settings()
3549
3620
  client = APIClient(
3550
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3551
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3621
+ base_url=_resolve_base_url(settings),
3622
+ api_key=_resolve_api_key(settings),
3552
3623
  )
3553
3624
  resp = client._request("get", f"/organizations/{org}/catalog/status")
3554
3625
  if resp.status_code != 200:
@@ -3586,8 +3657,8 @@ def catalog_init(org: str):
3586
3657
  """Initialize catalog tables on the configured storage connection."""
3587
3658
  settings = _load_settings()
3588
3659
  client = APIClient(
3589
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3590
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3660
+ base_url=_resolve_base_url(settings),
3661
+ api_key=_resolve_api_key(settings),
3591
3662
  )
3592
3663
  resp = client._request("post", f"/organizations/{org}/catalog/init")
3593
3664
  if resp.status_code != 200:
@@ -3608,8 +3679,8 @@ def catalog_query(org: str, table: str, where: str, limit: int, offset: int, fmt
3608
3679
  """Query rows from a catalog table."""
3609
3680
  settings = _load_settings()
3610
3681
  client = APIClient(
3611
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3612
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3682
+ base_url=_resolve_base_url(settings),
3683
+ api_key=_resolve_api_key(settings),
3613
3684
  )
3614
3685
  params = {"limit": limit, "offset": offset}
3615
3686
  if where:
@@ -3655,8 +3726,8 @@ def catalog_export(org: str, table: str, fmt: str, where: str, limit: int, outpu
3655
3726
  """Export catalog table data to file or stdout."""
3656
3727
  settings = _load_settings()
3657
3728
  client = APIClient(
3658
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3659
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3729
+ base_url=_resolve_base_url(settings),
3730
+ api_key=_resolve_api_key(settings),
3660
3731
  )
3661
3732
  params: dict[str, Any] = {"format": fmt}
3662
3733
  if where:
@@ -3698,8 +3769,8 @@ def catalog_upload(org: str, table: str, path: str, validate: bool):
3698
3769
 
3699
3770
  settings = _load_settings()
3700
3771
  client = APIClient(
3701
- base_url=settings.get("base_url", os.environ.get("OPENRUNNER_BASE_URL", "")),
3702
- api_key=settings.get("api_key", os.environ.get("OPENRUNNER_API_KEY", "")),
3772
+ base_url=_resolve_base_url(settings),
3773
+ api_key=_resolve_api_key(settings),
3703
3774
  )
3704
3775
 
3705
3776
  # Collect files
@@ -3760,6 +3831,56 @@ def catalog_upload(org: str, table: str, path: str, validate: bool):
3760
3831
  click.echo(f"\nDone. {total_rows} total rows uploaded to '{table}'.")
3761
3832
 
3762
3833
 
3834
+ @catalog.command("merge")
3835
+ @click.option("--org", required=True, help="Organization ID or slug")
3836
+ @click.option("--table", required=True, help="Table name (e.g. transcriptions)")
3837
+ @click.option("--key", default="request_id", show_default=True,
3838
+ help="Match column for the upsert")
3839
+ @click.option("--insert", is_flag=True, default=False,
3840
+ help="Also insert rows whose key has no match (default: skip them)")
3841
+ @click.option("--batch", default=500, show_default=True, help="Rows per request")
3842
+ @click.argument("path", type=click.Path(exists=True))
3843
+ def catalog_merge(org: str, table: str, key: str, insert: bool, batch: int, path: str):
3844
+ """Upsert JSONL rows into a catalog table by key (enrichment merge).
3845
+
3846
+ Unlike `catalog upload` (append-only), matched rows get ONLY the uploaded
3847
+ columns updated; new columns are created automatically (nullable, typed
3848
+ from the first non-null value). Nested values must be pre-serialized to
3849
+ JSON strings.
3850
+
3851
+ Example:
3852
+ openrunner catalog merge --org my-org --table transcriptions \\
3853
+ --key request_id enriched.jsonl
3854
+ """
3855
+ settings = _load_settings()
3856
+ client = APIClient(
3857
+ base_url=_resolve_base_url(settings),
3858
+ api_key=_resolve_api_key(settings),
3859
+ )
3860
+ lines = [ln for ln in Path(path).read_text(encoding="utf-8").splitlines() if ln.strip()]
3861
+ total = 0
3862
+ new_cols: set = set()
3863
+ for start in range(0, len(lines), max(1, batch)):
3864
+ chunk = lines[start:start + max(1, batch)]
3865
+ resp = client._request(
3866
+ "POST",
3867
+ f"/organizations/{org}/catalog/tables/{table}/merge?key={key}"
3868
+ + ("&insert=true" if insert else ""),
3869
+ content=("\n".join(chunk)).encode("utf-8"),
3870
+ headers={"Content-Type": "application/x-ndjson"},
3871
+ )
3872
+ if resp.status_code not in (200, 201):
3873
+ click.echo(f"Merge failed at row {start + 1}: {resp.text}", err=True)
3874
+ sys.exit(1)
3875
+ result = resp.json()
3876
+ total += result.get("rows_merged", 0)
3877
+ new_cols.update(result.get("new_columns", []))
3878
+ click.echo(f" merged {total}/{len(lines)}")
3879
+ if new_cols:
3880
+ click.echo(f"New columns created: {', '.join(sorted(new_cols))}")
3881
+ click.echo(f"Done. {total} rows merged into '{table}'.")
3882
+
3883
+
3763
3884
  # ---------------------------------------------------------------------------
3764
3885
  # patent-scan command
3765
3886
  # ---------------------------------------------------------------------------
@@ -3805,7 +3926,7 @@ def patent_scan(
3805
3926
  from openrunner.patent_scan import scan_commit, _run_git
3806
3927
 
3807
3928
  settings = _load_settings()
3808
- api_key = settings.get("api_key")
3929
+ api_key = _resolve_api_key(settings)
3809
3930
  base_url = settings.get("base_url", "https://api.openrun.gladia.io")
3810
3931
  project = project or settings.get("project")
3811
3932
 
@@ -3922,7 +4043,7 @@ def patent_lab(project: str | None, min_score: int, status: str | None, limit: i
3922
4043
  openrunner patent-lab --min-score 70 --status detected
3923
4044
  """
3924
4045
  settings = _load_settings()
3925
- api_key = settings.get("api_key")
4046
+ api_key = _resolve_api_key(settings)
3926
4047
  base_url = settings.get("base_url", "https://api.openrun.gladia.io")
3927
4048
  project = project or settings.get("project")
3928
4049
 
@@ -4340,7 +4461,7 @@ def gpu_credentials_add(org_id: str, provider: str, provider_key: str | None,
4340
4461
  from openrunner.gpu.api_integration import GpuAPIClient
4341
4462
 
4342
4463
  settings = _load_settings()
4343
- api_key = settings.get("api_key")
4464
+ api_key = _resolve_api_key(settings)
4344
4465
  base_url = settings.get("base_url", "http://localhost:8000")
4345
4466
  if not api_key:
4346
4467
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -4361,7 +4482,7 @@ def gpu_credentials_ls() -> None:
4361
4482
  from openrunner.gpu.api_integration import GpuAPIClient
4362
4483
 
4363
4484
  settings = _load_settings()
4364
- api_key = settings.get("api_key")
4485
+ api_key = _resolve_api_key(settings)
4365
4486
  base_url = settings.get("base_url", "http://localhost:8000")
4366
4487
  if not api_key:
4367
4488
  click.echo("Error: Not logged in.", err=True)
@@ -4398,9 +4519,10 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
4398
4519
 
4399
4520
  Forge is serverless (no pod / no SSH): COMMAND is the App script and it runs
4400
4521
  through the forge client, streaming the fleet's logs. The GPU is declared in
4401
- the script, so --gpu is not used.
4522
+ the script, so --gpu is not used. -e KEY=VALUE reaches the App's
4523
+ local_entrypoint as an environment variable.
4402
4524
 
4403
- openrunner gpu run --provider forge get_started.py
4525
+ openrunner gpu run --provider forge -e EPOCHS=3 get_started.py
4404
4526
 
4405
4527
  Example:
4406
4528
 
@@ -4415,7 +4537,7 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
4415
4537
  raise SystemExit(1)
4416
4538
 
4417
4539
  settings = _load_settings()
4418
- api_key = settings.get("api_key")
4540
+ api_key = _resolve_api_key(settings)
4419
4541
  base_url = settings.get("base_url")
4420
4542
  if not api_key:
4421
4543
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -4495,7 +4617,7 @@ def gpu_run(command, provider, gpu, gpu_count, image, disk_gb, env, ssh_keys, pr
4495
4617
  from openrunner.gpu.runner import run_engine_serverside_sync
4496
4618
  result = run_engine_serverside_sync(
4497
4619
  provider=provider, command=command, project=project,
4498
- emit=lambda m: click.echo(m),
4620
+ env=env_dict, emit=lambda m: click.echo(m),
4499
4621
  )
4500
4622
  except Exception as exc: # noqa: BLE001
4501
4623
  click.echo(f"gpu run failed: {exc}", err=True)
@@ -4585,7 +4707,7 @@ def _registry_http():
4585
4707
  import httpx
4586
4708
 
4587
4709
  settings = _load_settings()
4588
- api_key = settings.get("api_key")
4710
+ api_key = _resolve_api_key(settings)
4589
4711
  base_url = settings.get("base_url", "http://localhost:8000")
4590
4712
  if not api_key:
4591
4713
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -4783,7 +4905,7 @@ def registry_logout() -> None:
4783
4905
  def _drift_client_project(project: str | None):
4784
4906
  """Return (APIClient, project_id) resolving project name/slug -> UUID."""
4785
4907
  settings = _load_settings()
4786
- api_key = settings.get("api_key")
4908
+ api_key = _resolve_api_key(settings)
4787
4909
  base_url = settings.get("base_url", "http://localhost:8000")
4788
4910
  if not api_key:
4789
4911
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -5120,7 +5242,7 @@ def deploy_call(deployment_id, path, project, method, data, headers) -> None:
5120
5242
  openrunner deploy call <id> predict -p ORG/NAME -d '{"text":"hi"}'
5121
5243
  """
5122
5244
  settings = _load_settings()
5123
- api_key = settings.get("api_key")
5245
+ api_key = _resolve_api_key(settings)
5124
5246
  base_url = settings.get("base_url", "http://localhost:8000").rstrip("/")
5125
5247
  if not api_key:
5126
5248
  click.echo("Error: Not logged in.", err=True); raise SystemExit(1)
@@ -5164,7 +5286,7 @@ def deploy_call(deployment_id, path, project, method, data, headers) -> None:
5164
5286
  def _plain_client():
5165
5287
  """Build an APIClient from settings, or exit if not logged in."""
5166
5288
  settings = _load_settings()
5167
- api_key = settings.get("api_key")
5289
+ api_key = _resolve_api_key(settings)
5168
5290
  base_url = settings.get("base_url", "http://localhost:8000")
5169
5291
  if not api_key:
5170
5292
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)
@@ -5388,7 +5510,7 @@ def runner_start(org, name, labels, gpu, gpu_count, poll_interval, once, token,
5388
5510
  if not org or not name:
5389
5511
  click.echo("Error: pass --org and --name to register, or --token to run with a pre-minted token.", err=True)
5390
5512
  raise SystemExit(1)
5391
- api_key = settings.get("api_key")
5513
+ api_key = _resolve_api_key(settings)
5392
5514
  if not api_key:
5393
5515
  click.echo("Error: Not logged in. Run 'openrunner login' first (or pass --token).", err=True)
5394
5516
  raise SystemExit(1)
@@ -5645,7 +5767,7 @@ def runner_launch(org, name, provider, gpu, gpu_count, labels, image, web, poll_
5645
5767
  from openrunner.gpu.api_integration import GpuAPIClient
5646
5768
 
5647
5769
  settings = _load_settings()
5648
- api_key = settings.get("api_key")
5770
+ api_key = _resolve_api_key(settings)
5649
5771
  base_url = settings.get("base_url", "http://localhost:8000").rstrip("/")
5650
5772
  if not api_key:
5651
5773
  click.echo("Error: Not logged in. Run 'openrunner login' first.", err=True)