benchkit-cli 0.1.0__tar.gz → 0.1.1__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 (188) hide show
  1. {benchkit_cli-0.1.0/src/benchkit_cli.egg-info → benchkit_cli-0.1.1}/PKG-INFO +7 -3
  2. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/README.md +6 -2
  3. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/pyproject.toml +1 -1
  4. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/evalplus.py +23 -1
  5. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/cli.py +52 -9
  6. benchkit_cli-0.1.1/src/benchkit/config.py +251 -0
  7. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/executor.py +48 -21
  8. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/connect.py +28 -0
  9. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1/src/benchkit_cli.egg-info}/PKG-INFO +7 -3
  10. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit_cli.egg-info/SOURCES.txt +2 -0
  11. benchkit_cli-0.1.1/tests/test_config.py +198 -0
  12. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/LICENSE +0 -0
  13. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/setup.cfg +0 -0
  14. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/__init__.py +0 -0
  15. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/_pi_proxy.py +0 -0
  16. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/answer_key_guard.ts +0 -0
  17. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/artifacts.py +0 -0
  18. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/__init__.py +0 -0
  19. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/aider_polyglot.py +0 -0
  20. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/arc.py +0 -0
  21. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/banking77.py +0 -0
  22. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/base.py +0 -0
  23. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/boolq.py +0 -0
  24. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/chartqa.py +0 -0
  25. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/code_verifier.py +0 -0
  26. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/downloads.py +0 -0
  27. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/git_surgery.py +0 -0
  28. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/gpqa.py +0 -0
  29. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/gsm8k.py +0 -0
  30. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/hellaswag.py +0 -0
  31. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/humaneval.py +0 -0
  32. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/ifeval.py +0 -0
  33. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/ifeval_instructions.py +0 -0
  34. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/mbpp.py +0 -0
  35. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/mc_arena.py +0 -0
  36. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/mcq.py +0 -0
  37. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/medfailbench.py +0 -0
  38. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/mmlu.py +0 -0
  39. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/mmlu_pro.py +0 -0
  40. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/mmvp.py +0 -0
  41. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/ocrbench.py +0 -0
  42. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/openbookqa.py +0 -0
  43. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/patcheval.py +0 -0
  44. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/piqa.py +0 -0
  45. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/ruler.py +0 -0
  46. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/ruler_generator.py +0 -0
  47. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/sanity.py +0 -0
  48. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/treejs_arena.py +0 -0
  49. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/truthfulqa.py +0 -0
  50. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/utils.py +0 -0
  51. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/vision.py +0 -0
  52. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/vstar.py +0 -0
  53. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/winogrande.py +0 -0
  54. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/benchmarks/xstest.py +0 -0
  55. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/browser.py +0 -0
  56. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/catalog.py +0 -0
  57. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/client.py +0 -0
  58. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/controls.py +0 -0
  59. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/MMLU_LICENSE.txt +0 -0
  60. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/README.md +0 -0
  61. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/arc_challenge.jsonl +0 -0
  62. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/banking77.jsonl +0 -0
  63. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/banking77_intents.json +0 -0
  64. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/boolq.jsonl +0 -0
  65. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/code_verifier.jsonl +0 -0
  66. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/gpqa_diamond.jsonl +0 -0
  67. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/gsm8k.jsonl +0 -0
  68. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/hellaswag.jsonl +0 -0
  69. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/humaneval.jsonl +0 -0
  70. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/ifeval.jsonl +0 -0
  71. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/mbpp.jsonl +0 -0
  72. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/mc_blocks_1_20_1.jsonl +0 -0
  73. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/medfailbench_hard30.jsonl +0 -0
  74. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/medfailbench_hard30.md +0 -0
  75. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/openbookqa.jsonl +0 -0
  76. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/piqa.jsonl +0 -0
  77. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/sanity_code.jsonl +0 -0
  78. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/truthfulqa.jsonl +0 -0
  79. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/winogrande.jsonl +0 -0
  80. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/datasets/xstest.csv +0 -0
  81. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/decision.py +0 -0
  82. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/demo.py +0 -0
  83. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/dockerfiles.py +0 -0
  84. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/engine.py +0 -0
  85. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/evaluation.py +0 -0
  86. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/events.py +0 -0
  87. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/backport-release-stack/setup.sh +0 -0
  88. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/backport-release-stack/verify.sh +0 -0
  89. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/bisect-the-regression/setup.sh +0 -0
  90. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/bisect-the-regression/verify.sh +0 -0
  91. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/rebase-conflict-chain/setup.sh +0 -0
  92. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/rebase-conflict-chain/verify.sh +0 -0
  93. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/recover-complex-stash/setup.sh +0 -0
  94. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/recover-complex-stash/verify.sh +0 -0
  95. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/recover-lost-work/setup.sh +0 -0
  96. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/recover-lost-work/verify.sh +0 -0
  97. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/repair-force-pushed-remote/setup.sh +0 -0
  98. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/repair-force-pushed-remote/verify.sh +0 -0
  99. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/revert-merge-with-followups/setup.sh +0 -0
  100. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/revert-merge-with-followups/verify.sh +0 -0
  101. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/secret-in-history/README.md +0 -0
  102. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/secret-in-history/setup.sh +0 -0
  103. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/secret-in-history/verify.sh +0 -0
  104. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/split-the-mega-commit/setup.sh +0 -0
  105. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/split-the-mega-commit/verify.sh +0 -0
  106. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/untangle-nested-submodules/setup.sh +0 -0
  107. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/git_surgery/untangle-nested-submodules/verify.sh +0 -0
  108. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/history.py +0 -0
  109. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/jobs.py +0 -0
  110. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/leakage.py +0 -0
  111. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/looping.py +0 -0
  112. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_render.py +0 -0
  113. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/atlas.png +0 -0
  114. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
  115. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/version.json +0 -0
  116. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/viewer.html +0 -0
  117. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/viewer.js +0 -0
  118. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +0 -0
  119. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/worker.js +0 -0
  120. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/mc_viewer/dist/worker.js.LICENSE.txt +0 -0
  121. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/metrics.py +0 -0
  122. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/ollama.py +0 -0
  123. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/perf.py +0 -0
  124. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/perf_report.py +0 -0
  125. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/perturbations.py +0 -0
  126. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/pi_agent.py +0 -0
  127. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/pi_package/package-lock.json +0 -0
  128. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/pi_package/package.json +0 -0
  129. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/report.py +0 -0
  130. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/report_markdown.py +0 -0
  131. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/results.py +0 -0
  132. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/retry.py +0 -0
  133. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/runner.py +0 -0
  134. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/sandbox.py +0 -0
  135. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/template_check.py +0 -0
  136. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/templates/arena.html +0 -0
  137. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/templates/history.html +0 -0
  138. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/templates/perf.html +0 -0
  139. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/templates/report.html +0 -0
  140. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/__init__.py +0 -0
  141. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/app.py +0 -0
  142. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/app.tcss +0 -0
  143. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/formatting.py +0 -0
  144. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/__init__.py +0 -0
  145. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/detail.py +0 -0
  146. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/help.py +0 -0
  147. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/modals.py +0 -0
  148. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/results.py +0 -0
  149. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/run.py +0 -0
  150. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/screens/setup.py +0 -0
  151. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/theme.py +0 -0
  152. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit/tui/widgets.py +0 -0
  153. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit_cli.egg-info/dependency_links.txt +0 -0
  154. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit_cli.egg-info/entry_points.txt +0 -0
  155. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit_cli.egg-info/requires.txt +0 -0
  156. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/src/benchkit_cli.egg-info/top_level.txt +0 -0
  157. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_aider_polyglot.py +0 -0
  158. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_banking77.py +0 -0
  159. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_code_verifier.py +0 -0
  160. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_concurrency.py +0 -0
  161. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_datasets.py +0 -0
  162. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_decision.py +0 -0
  163. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_evalplus.py +0 -0
  164. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_git_surgery.py +0 -0
  165. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_gsm8k.py +0 -0
  166. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_history.py +0 -0
  167. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_humaneval.py +0 -0
  168. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_looping.py +0 -0
  169. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_mc_arena.py +0 -0
  170. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_mcq.py +0 -0
  171. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_medfailbench.py +0 -0
  172. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_medfailbench_real_outputs.py +0 -0
  173. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_medfailbench_real_outputs_escalation.py +0 -0
  174. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_medfailbench_scoring_distortion.py +0 -0
  175. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_patcheval.py +0 -0
  176. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_perturbations.py +0 -0
  177. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_pi_harness.py +0 -0
  178. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_repair.py +0 -0
  179. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_report.py +0 -0
  180. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_retry.py +0 -0
  181. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_ruler.py +0 -0
  182. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_runner.py +0 -0
  183. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_sanity.py +0 -0
  184. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_treejs_arena.py +0 -0
  185. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_unload.py +0 -0
  186. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_utils.py +0 -0
  187. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_vision.py +0 -0
  188. {benchkit_cli-0.1.0 → benchkit_cli-0.1.1}/tests/test_xstest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: benchkit-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Benchmark your local LLMs with real eval suites
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -69,8 +69,10 @@ cp .env.example .env
69
69
  uv run benchkit
70
70
  ```
71
71
 
72
- Set your server in a `.env` in the directory you run `benchkit` from (parent
73
- directories are not searched), or enter it on the Connect screen:
72
+ Set your server in `~/.config/benchkit/.env` to use it from any folder, in a
73
+ `.env` in the directory you run `benchkit` from (parent directories are not
74
+ searched), or on the Connect screen, where "Save as default" writes the user
75
+ config for you:
74
76
 
75
77
  ```env
76
78
  BENCHKIT_PROVIDER=openai
@@ -85,6 +87,8 @@ BENCHKIT_PROVIDER=ollama
85
87
  OLLAMA_HOST=http://localhost:11434
86
88
  ```
87
89
 
90
+ `benchkit config` shows every setting and where it came from.
91
+
88
92
  No inference server available? Explore the complete UI offline:
89
93
 
90
94
  ```bash
@@ -51,8 +51,10 @@ cp .env.example .env
51
51
  uv run benchkit
52
52
  ```
53
53
 
54
- Set your server in a `.env` in the directory you run `benchkit` from (parent
55
- directories are not searched), or enter it on the Connect screen:
54
+ Set your server in `~/.config/benchkit/.env` to use it from any folder, in a
55
+ `.env` in the directory you run `benchkit` from (parent directories are not
56
+ searched), or on the Connect screen, where "Save as default" writes the user
57
+ config for you:
56
58
 
57
59
  ```env
58
60
  BENCHKIT_PROVIDER=openai
@@ -67,6 +69,8 @@ BENCHKIT_PROVIDER=ollama
67
69
  OLLAMA_HOST=http://localhost:11434
68
70
  ```
69
71
 
72
+ `benchkit config` shows every setting and where it came from.
73
+
70
74
  No inference server available? Explore the complete UI offline:
71
75
 
72
76
  ```bash
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "benchkit-cli"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "Benchmark your local LLMs with real eval suites"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -6,6 +6,8 @@ import contextlib
6
6
  import multiprocessing
7
7
  import os
8
8
  import platform
9
+ import shutil
10
+ import tempfile
9
11
  import textwrap
10
12
  from functools import cache, lru_cache
11
13
  from pathlib import Path
@@ -14,6 +16,7 @@ from typing import Any
14
16
  from benchkit.benchmarks.base import Task
15
17
  from benchkit.benchmarks.downloads import Dataset
16
18
  from benchkit.benchmarks.utils import strip_think_tags
19
+ from benchkit.executor import sandbox_env
17
20
 
18
21
  HUMANEVAL_SYSTEM = (
19
22
  "Complete the following Python function. "
@@ -43,10 +46,29 @@ def _fork_multiprocessing():
43
46
 
44
47
  EvalPlus's ``evalplus.eval`` does ``from multiprocessing import Array, Value``
45
48
  so those names are bound on that module and must be patched there as well.
49
+
50
+ The forked child runs model-written code, so it swaps its inherited
51
+ environment for a minimal one before running: API keys stay with the
52
+ parent. ``EVALPLUS_*`` is kept because the child reads its memory limit.
46
53
  """
47
54
  import evalplus.eval as _ev
48
55
 
49
56
  fork_ctx = multiprocessing.get_context("fork")
57
+
58
+ class _ScrubbedProcess(fork_ctx.Process):
59
+ def run(self) -> None:
60
+ # EvalPlus's reliability guard disables shutil.rmtree in the child.
61
+ rmtree = shutil.rmtree
62
+ home = tempfile.mkdtemp(prefix="benchkit-evalplus-")
63
+ env = sandbox_env(home, keep_prefixes=("EVALPLUS_",))
64
+ os.environ.clear()
65
+ os.environ.update(env)
66
+ tempfile.tempdir = None
67
+ try:
68
+ super().run()
69
+ finally:
70
+ rmtree(home, ignore_errors=True)
71
+
50
72
  mp_originals = (
51
73
  multiprocessing.Process,
52
74
  multiprocessing.Value,
@@ -54,7 +76,7 @@ def _fork_multiprocessing():
54
76
  )
55
77
  ev_originals = (_ev.Value, _ev.Array)
56
78
  try:
57
- multiprocessing.Process = fork_ctx.Process
79
+ multiprocessing.Process = _ScrubbedProcess
58
80
  multiprocessing.Value = fork_ctx.Value
59
81
  multiprocessing.Array = fork_ctx.Array
60
82
  _ev.Value = fork_ctx.Value
@@ -9,13 +9,13 @@ from __future__ import annotations
9
9
  import argparse
10
10
  import sys
11
11
 
12
- from dotenv import load_dotenv
13
12
  from rich import box
14
13
  from rich.console import Console
14
+ from rich.markup import escape
15
15
  from rich.table import Table
16
16
  from rich.text import Text
17
17
 
18
- from benchkit import catalog
18
+ from benchkit import catalog, config
19
19
  from benchkit.benchmarks import DESCRIPTIONS, REGISTRY
20
20
  from benchkit.client import InferenceClient
21
21
  from benchkit.engine import (
@@ -60,11 +60,11 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
60
60
  parser.add_argument(
61
61
  "command",
62
62
  nargs="?",
63
- choices=("perf", "history", "render-check", "datasets"),
63
+ choices=("perf", "history", "render-check", "datasets", "config"),
64
64
  help=(
65
65
  "Run a performance profile, browse historical results, check "
66
- "that headless rendering works on this machine, or list and "
67
- "install benchmark datasets"
66
+ "that headless rendering works on this machine, list and "
67
+ "install benchmark datasets, or show the effective configuration"
68
68
  ),
69
69
  )
70
70
  parser.add_argument(
@@ -73,7 +73,8 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
73
73
  metavar="MODEL",
74
74
  help=(
75
75
  "Model name for `benchkit perf MODEL`; `install` for "
76
- "`benchkit datasets install [BENCHMARK ...]`"
76
+ "`benchkit datasets install [BENCHMARK ...]`; `path` for "
77
+ "`benchkit config path`"
77
78
  ),
78
79
  )
79
80
  parser.add_argument(
@@ -83,6 +84,15 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
83
84
  help="Datasets: benchmarks to install (default: every missing one)",
84
85
  )
85
86
  parser.add_argument("--host", help="Inference host, overrides BENCHKIT_HOST")
87
+ parser.add_argument(
88
+ "--env-file",
89
+ metavar="PATH",
90
+ help=(
91
+ "Read settings from PATH instead of ./.env (also BENCHKIT_ENV_FILE). "
92
+ "Precedence: flags > environment > --env-file or ./.env > "
93
+ "~/.config/benchkit/.env ($XDG_CONFIG_HOME) > defaults"
94
+ ),
95
+ )
86
96
  parser.add_argument(
87
97
  "--demo",
88
98
  action="store_true",
@@ -956,11 +966,40 @@ def _perf(args: argparse.Namespace) -> None:
956
966
  )
957
967
 
958
968
 
969
+ def _config(args: argparse.Namespace) -> None:
970
+ if args.perf_model == "path":
971
+ for entry in config.loaded().files:
972
+ if entry.skipped:
973
+ state = f"[dim]{entry.skipped}[/dim]"
974
+ elif entry.exists:
975
+ state = "[green]found[/green]"
976
+ else:
977
+ state = "[dim]missing[/dim]"
978
+ console.print(f"{entry.role:<18} {escape(str(entry.path))} {state}")
979
+ return
980
+ if args.perf_model is not None:
981
+ console.print(
982
+ f"[red]Unknown config action {escape(repr(args.perf_model))}.[/red]"
983
+ )
984
+ sys.exit(1)
985
+
986
+ table = Table(box=box.SIMPLE_HEAD, header_style="bold")
987
+ table.add_column("Setting")
988
+ table.add_column("Value")
989
+ table.add_column("Origin", style="dim")
990
+ for key, value, origin in config.resolve({"BENCHKIT_HOST": args.host}):
991
+ shown = Text(value) if value else Text("-", style="dim")
992
+ table.add_row(key, shown, Text(origin))
993
+ console.print(table)
994
+
995
+
959
996
  def main(argv: list[str] | None = None) -> None:
960
- # Only the working directory's .env: walking up to parent directories would
961
- # let a file in a shared parent such as /tmp redirect the host and API key.
962
- load_dotenv(".env")
963
997
  args = _parse_args(sys.argv[1:] if argv is None else argv)
998
+ try:
999
+ config.load(args.env_file)
1000
+ except config.ConfigError as exc:
1001
+ console.print(f"[red]{exc}[/red]")
1002
+ sys.exit(2)
964
1003
 
965
1004
  if args.list:
966
1005
  _list_benchmarks()
@@ -974,6 +1013,10 @@ def main(argv: list[str] | None = None) -> None:
974
1013
  _datasets(args)
975
1014
  return
976
1015
 
1016
+ if args.command == "config":
1017
+ _config(args)
1018
+ return
1019
+
977
1020
  if args.command == "perf":
978
1021
  _perf(args)
979
1022
  return
@@ -0,0 +1,251 @@
1
+ """Layered configuration: which settings apply, and where each one came from.
2
+
3
+ Precedence, highest first: CLI flags, real environment variables,
4
+ ``--env-file PATH`` (or ``BENCHKIT_ENV_FILE``), ``./.env`` in the working
5
+ directory, the user config ``$XDG_CONFIG_HOME/benchkit/.env`` (default
6
+ ``~/.config/benchkit/.env``), then built-in defaults. An explicit env file
7
+ replaces ``./.env`` rather than stacking on it.
8
+
9
+ Only BenchKit's own keys are applied from any file, so running inside another
10
+ project never imports that project's secrets. Parent directories are never
11
+ searched: a ``.env`` in a shared parent such as ``/tmp`` could otherwise
12
+ redirect the host and API key.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ from collections.abc import MutableMapping
19
+ from dataclasses import dataclass, field
20
+ from pathlib import Path
21
+
22
+ from dotenv import dotenv_values, set_key, unset_key
23
+
24
+ from benchkit.client import DEFAULT_HOST
25
+
26
+ ENV_FILE_VAR = "BENCHKIT_ENV_FILE"
27
+ _PREFIXES = ("BENCHKIT_", "EVALPLUS_")
28
+ _NAMES = frozenset({"OLLAMA_HOST", "OPENAI_API_KEY", "OPENAI_BASE_URL"})
29
+ _SECRET_SUFFIXES = ("_API_KEY", "_TOKEN", "_SECRET", "_PASSWORD")
30
+
31
+ # Settings `benchkit config` always lists, with the default each falls back to
32
+ # ("" when unset simply means "not used"). Internal variables the harness sets
33
+ # for its own child processes are left out.
34
+ # fmt: off
35
+ SETTINGS: tuple[tuple[str, str], ...] = (
36
+ ("BENCHKIT_PROVIDER", "auto"),
37
+ ("BENCHKIT_HOST", DEFAULT_HOST),
38
+ ("OPENAI_BASE_URL", ""),
39
+ ("OLLAMA_HOST", ""),
40
+ ("BENCHKIT_API_KEY", ""),
41
+ ("OPENAI_API_KEY", ""),
42
+ ("BENCHKIT_TIMEOUT", "300"),
43
+ ("BENCHKIT_CONCURRENCY", ""),
44
+ ("BENCHKIT_HARDWARE", ""),
45
+ ("BENCHKIT_PERF_CONFIG", ""),
46
+ ("BENCHKIT_RETRIES", "3"),
47
+ ("BENCHKIT_CONNECT_RETRIES", "6"),
48
+ ("BENCHKIT_RETRY_BASE_DELAY", "0.5"),
49
+ ("BENCHKIT_RETRY_MAX_DELAY", "8"),
50
+ ("BENCHKIT_RETRY_MAX_WAIT", "60"),
51
+ ("BENCHKIT_LOOP_KILL", "true"),
52
+ ("BENCHKIT_LOOP_KILL_PERCENT", "80"),
53
+ ("BENCHKIT_LOOP_KILL_SECONDS", "10"),
54
+ ("BENCHKIT_PI_TIMEOUT", "900"),
55
+ ("BENCHKIT_DOCKER", "docker"),
56
+ ("BENCHKIT_SANDBOX_MEMORY", "2g"),
57
+ ("BENCHKIT_SANDBOX_CPUS", "2"),
58
+ ("BENCHKIT_SANDBOX_PIDS", ""),
59
+ ("BENCHKIT_MC_ARENA_MEMORY", "512m"),
60
+ ("BENCHKIT_MC_ARENA_CPUS", "1"),
61
+ ("BENCHKIT_MC_ARENA_PIDS", "64"),
62
+ ("BENCHKIT_MC_ARENA_BASE_IMAGE", ""),
63
+ ("BENCHKIT_MC_ARENA_IMAGE", ""),
64
+ ("BENCHKIT_RENDER_SETTLE", "4"),
65
+ ("BENCHKIT_RENDER_TIMEOUT", "30"),
66
+ ("BENCHKIT_RENDER_ALLOWED_HOSTS", ""),
67
+ ("BENCHKIT_RENDER_OFFLINE", "false"),
68
+ ("BENCHKIT_RENDER_CHROMIUM", ""),
69
+ ("BENCHKIT_RENDER_NO_SANDBOX", "false"),
70
+ ("BENCHKIT_DATASET_CACHE", ""),
71
+ ("BENCHKIT_VISION_CACHE", ""),
72
+ ("BENCHKIT_PATCHEVAL_DATASET", ""),
73
+ ("BENCHKIT_AIDER_DATASET", ""),
74
+ )
75
+ # fmt: on
76
+
77
+
78
+ class ConfigError(Exception):
79
+ """A configuration source was named but cannot be used."""
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class EnvFile:
84
+ """One file BenchKit considered, in precedence order."""
85
+
86
+ path: Path
87
+ role: str
88
+ exists: bool
89
+ skipped: str = ""
90
+
91
+
92
+ @dataclass
93
+ class Loaded:
94
+ """The files considered and the origin of every BenchKit key applied."""
95
+
96
+ files: list[EnvFile] = field(default_factory=list)
97
+ origins: dict[str, str] = field(default_factory=dict)
98
+
99
+
100
+ _loaded = Loaded()
101
+
102
+
103
+ def is_benchkit_key(key: str) -> bool:
104
+ return key.startswith(_PREFIXES) or key in _NAMES
105
+
106
+
107
+ def is_secret(key: str) -> bool:
108
+ return key.endswith(_SECRET_SUFFIXES)
109
+
110
+
111
+ def mask(value: str) -> str:
112
+ """Hide a secret, keeping the last four characters of a long one."""
113
+ if not value:
114
+ return ""
115
+ if len(value) <= 8:
116
+ return "****"
117
+ return "****" + value[-4:]
118
+
119
+
120
+ def user_config_dir(environ: MutableMapping[str, str] | None = None) -> Path:
121
+ environ = os.environ if environ is None else environ
122
+ base = environ.get("XDG_CONFIG_HOME") or ""
123
+ root = Path(base).expanduser() if base else Path("~/.config").expanduser()
124
+ return root / "benchkit"
125
+
126
+
127
+ def user_env_path(environ: MutableMapping[str, str] | None = None) -> Path:
128
+ return user_config_dir(environ) / ".env"
129
+
130
+
131
+ def load(
132
+ env_file: str | None = None,
133
+ *,
134
+ environ: MutableMapping[str, str] | None = None,
135
+ cwd: Path | None = None,
136
+ ) -> Loaded:
137
+ """Apply BenchKit keys from the config files into ``environ``.
138
+
139
+ A key already in the environment is never overwritten, and a file only
140
+ fills keys that no higher file set. Raises ``ConfigError`` when an
141
+ explicit env file does not exist.
142
+ """
143
+ global _loaded
144
+ environ = os.environ if environ is None else environ
145
+ cwd = Path.cwd() if cwd is None else cwd
146
+
147
+ files: list[EnvFile] = []
148
+ project = cwd / ".env"
149
+ explicit, role = env_file, "--env-file"
150
+ if not explicit and environ.get(ENV_FILE_VAR):
151
+ explicit, role = environ[ENV_FILE_VAR], ENV_FILE_VAR
152
+ if explicit:
153
+ path = Path(explicit).expanduser()
154
+ if not path.is_absolute():
155
+ path = cwd / path
156
+ if not path.is_file():
157
+ raise ConfigError(f"{role}: no such file: {path}")
158
+ files.append(EnvFile(path, role, True))
159
+ files.append(
160
+ EnvFile(
161
+ project, "project", project.is_file(), skipped=f"replaced by {role}"
162
+ )
163
+ )
164
+ else:
165
+ files.append(EnvFile(project, "project", project.is_file()))
166
+ user = user_env_path(environ)
167
+ files.append(EnvFile(user, "user", user.is_file()))
168
+
169
+ origins = {key: "env" for key in environ if is_benchkit_key(key)}
170
+ for entry in files:
171
+ if not entry.exists or entry.skipped:
172
+ continue
173
+ for key, value in dotenv_values(entry.path).items():
174
+ if value is None or key == ENV_FILE_VAR or not is_benchkit_key(key):
175
+ continue
176
+ if key in origins:
177
+ continue
178
+ environ[key] = value
179
+ origins[key] = str(entry.path)
180
+
181
+ _loaded = Loaded(files, origins)
182
+ return _loaded
183
+
184
+
185
+ def loaded() -> Loaded:
186
+ """What the last ``load`` applied (empty before the first call)."""
187
+ return _loaded
188
+
189
+
190
+ def resolve(
191
+ flags: dict[str, str | None] | None = None,
192
+ *,
193
+ environ: MutableMapping[str, str] | None = None,
194
+ ) -> list[tuple[str, str, str]]:
195
+ """Every known or set BenchKit key as ``(key, value, origin)``.
196
+
197
+ ``value`` is masked for secrets. ``origin`` is ``flag``, ``env``, the file
198
+ path the value came from, or ``default``.
199
+ """
200
+ environ = os.environ if environ is None else environ
201
+ flags = {key: value for key, value in (flags or {}).items() if value}
202
+ defaults = dict(SETTINGS)
203
+ extra = sorted(
204
+ key
205
+ for key in environ
206
+ if is_benchkit_key(key) and key not in defaults and key != ENV_FILE_VAR
207
+ )
208
+ rows = []
209
+ for key in [*defaults, *extra]:
210
+ if key in flags:
211
+ value, origin = flags[key], "flag"
212
+ elif key in environ:
213
+ value, origin = environ[key], _loaded.origins.get(key, "env")
214
+ else:
215
+ value, origin = defaults.get(key, ""), "default"
216
+ rows.append((key, mask(value) if is_secret(key) else value, origin))
217
+ return rows
218
+
219
+
220
+ def save_user(
221
+ values: dict[str, str | None],
222
+ *,
223
+ environ: MutableMapping[str, str] | None = None,
224
+ ) -> Path:
225
+ """Write ``values`` to the user config; ``None`` or ``""`` removes a key.
226
+
227
+ The file can hold an API key, so it is created readable by its owner only.
228
+ """
229
+ path = user_env_path(environ)
230
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
231
+ if not path.exists():
232
+ path.touch(mode=0o600)
233
+ present = dotenv_values(path)
234
+ for key, value in values.items():
235
+ if not is_benchkit_key(key):
236
+ raise ValueError(f"not a BenchKit setting: {key}")
237
+ if value:
238
+ set_key(path, key, value, quote_mode="auto")
239
+ elif key in present:
240
+ unset_key(path, key, quote_mode="auto")
241
+ return path
242
+
243
+
244
+ def overridden(keys: list[str]) -> dict[str, str]:
245
+ """Keys whose current value comes from somewhere above the user config."""
246
+ user = str(user_env_path())
247
+ return {
248
+ key: origin
249
+ for key in keys
250
+ if (origin := _loaded.origins.get(key)) is not None and origin != user
251
+ }
@@ -1,5 +1,6 @@
1
1
  """Sandboxed code execution via subprocess."""
2
2
 
3
+ import os
3
4
  import subprocess
4
5
  import sys
5
6
  import tempfile
@@ -32,6 +33,33 @@ _b.__import__ = _safe
32
33
  """
33
34
 
34
35
 
36
+ def sandbox_env(home: str, *, keep_prefixes: tuple[str, ...] = ()) -> dict[str, str]:
37
+ """A minimal environment for running model-written code.
38
+
39
+ The import guard is easy to bypass, so the child must not inherit API keys
40
+ or anything else from BenchKit's own environment. ``HOME`` and the temp dir
41
+ point at ``home``; ``keep_prefixes`` keeps evaluator knobs such as
42
+ ``EVALPLUS_``.
43
+ """
44
+ env = {
45
+ "PATH": os.environ.get("PATH") or os.defpath,
46
+ "HOME": home,
47
+ "TMPDIR": home,
48
+ "LANG": os.environ.get("LANG") or "C.UTF-8",
49
+ }
50
+ # Windows Python cannot start without these.
51
+ for name in ("SYSTEMROOT", "COMSPEC"):
52
+ if name in os.environ:
53
+ env[name] = os.environ[name]
54
+ if keep_prefixes:
55
+ env.update(
56
+ (key, value)
57
+ for key, value in os.environ.items()
58
+ if key.startswith(keep_prefixes)
59
+ )
60
+ return env
61
+
62
+
35
63
  @dataclass(frozen=True)
36
64
  class ExecutionResult:
37
65
  """Code-verifier result with answer-safe diagnostic feedback."""
@@ -68,27 +96,26 @@ def execute_with_feedback(code: str, timeout: int = 10) -> ExecutionResult:
68
96
  """Run code and retain only a sanitized failure category."""
69
97
  full = SANDBOX_HEADER + "\n" + code
70
98
 
71
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
72
- f.write(full)
73
- path = Path(f.name)
74
-
75
- try:
76
- result = subprocess.run(
77
- [sys.executable, str(path)],
78
- capture_output=True,
79
- text=True,
80
- timeout=timeout,
81
- )
82
- if result.returncode == 0:
83
- return ExecutionResult(True)
84
- return ExecutionResult(False, _failure_feedback(result.stderr or ""))
85
- except subprocess.TimeoutExpired:
86
- return ExecutionResult(
87
- False,
88
- f"The submitted Python exceeded the {timeout}s verifier timeout.",
89
- )
90
- finally:
91
- path.unlink(missing_ok=True)
99
+ with tempfile.TemporaryDirectory(prefix="benchkit-exec-") as home:
100
+ path = Path(home) / "solution.py"
101
+ path.write_text(full)
102
+ try:
103
+ result = subprocess.run(
104
+ [sys.executable, str(path)],
105
+ capture_output=True,
106
+ text=True,
107
+ timeout=timeout,
108
+ cwd=home,
109
+ env=sandbox_env(home),
110
+ )
111
+ except subprocess.TimeoutExpired:
112
+ return ExecutionResult(
113
+ False,
114
+ f"The submitted Python exceeded the {timeout}s verifier timeout.",
115
+ )
116
+ if result.returncode == 0:
117
+ return ExecutionResult(True)
118
+ return ExecutionResult(False, _failure_feedback(result.stderr or ""))
92
119
 
93
120
 
94
121
  def execute(code: str, timeout: int = 10) -> bool:
@@ -11,6 +11,7 @@ from textual.containers import Horizontal, Vertical, VerticalScroll
11
11
  from textual.screen import Screen
12
12
  from textual.widgets import Button, Footer, Header, Input, Label, Select, Static
13
13
 
14
+ from benchkit import config
14
15
  from benchkit.client import DEFAULT_HOST, InferenceClient
15
16
  from benchkit.demo import DemoClient
16
17
  from benchkit.tui.widgets import Banner
@@ -34,6 +35,7 @@ class ConnectScreen(Screen[None]):
34
35
  Binding("ctrl+r", "connect", "Connect"),
35
36
  Binding("f5", "connect", "Connect", show=False),
36
37
  Binding("f3", "demo", "Demo mode"),
38
+ Binding("ctrl+s", "save_default", "Save as default"),
37
39
  Binding("escape", "app.quit", "Quit", show=False),
38
40
  ]
39
41
 
@@ -72,6 +74,7 @@ class ConnectScreen(Screen[None]):
72
74
  yield Input(placeholder="300", id="timeout", compact=True)
73
75
  with Horizontal(id="connect-actions"):
74
76
  yield Button("Connect", id="connect", variant="primary")
77
+ yield Button("Save as default", id="save-default")
75
78
  yield Button("Quit", id="quit", variant="error")
76
79
  yield Static(HINT, id="connect-status")
77
80
  yield Footer()
@@ -111,6 +114,29 @@ class ConnectScreen(Screen[None]):
111
114
  self._status(f"Connecting to {host} …", "working")
112
115
  self._connect(host.rstrip("/"), str(provider), api_key, timeout)
113
116
 
117
+ def action_save_default(self) -> None:
118
+ """Write host, provider and key to the user config (~/.config/benchkit)."""
119
+ values = {
120
+ "BENCHKIT_HOST": self.query_one("#host", Input).value.strip().rstrip("/"),
121
+ "BENCHKIT_PROVIDER": str(self.query_one("#provider", Select).value),
122
+ "BENCHKIT_API_KEY": self.query_one("#api-key", Input).value.strip(),
123
+ }
124
+ try:
125
+ path = config.save_user(values)
126
+ except OSError as exc:
127
+ self._status(f"Could not save defaults: {exc}", "error")
128
+ return
129
+ message = f"Saved host, provider and key to {path}"
130
+ if shadowed := config.overridden(list(values)):
131
+ sources = ", ".join(sorted(set(shadowed.values())))
132
+ message += (
133
+ f"\n{', '.join(sorted(shadowed))} still come from {sources}, "
134
+ "which take precedence here."
135
+ )
136
+ self._status(message, "working")
137
+ else:
138
+ self._status(message, "ok")
139
+
114
140
  def action_demo(self) -> None:
115
141
  client = DemoClient()
116
142
  self._status("Demo mode — offline models, no server required.", "ok")
@@ -119,6 +145,8 @@ class ConnectScreen(Screen[None]):
119
145
  def on_button_pressed(self, event: Button.Pressed) -> None:
120
146
  if event.button.id == "connect":
121
147
  self.action_connect()
148
+ elif event.button.id == "save-default":
149
+ self.action_save_default()
122
150
  elif event.button.id == "quit":
123
151
  self.app.exit()
124
152
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: benchkit-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Benchmark your local LLMs with real eval suites
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -69,8 +69,10 @@ cp .env.example .env
69
69
  uv run benchkit
70
70
  ```
71
71
 
72
- Set your server in a `.env` in the directory you run `benchkit` from (parent
73
- directories are not searched), or enter it on the Connect screen:
72
+ Set your server in `~/.config/benchkit/.env` to use it from any folder, in a
73
+ `.env` in the directory you run `benchkit` from (parent directories are not
74
+ searched), or on the Connect screen, where "Save as default" writes the user
75
+ config for you:
74
76
 
75
77
  ```env
76
78
  BENCHKIT_PROVIDER=openai
@@ -85,6 +87,8 @@ BENCHKIT_PROVIDER=ollama
85
87
  OLLAMA_HOST=http://localhost:11434
86
88
  ```
87
89
 
90
+ `benchkit config` shows every setting and where it came from.
91
+
88
92
  No inference server available? Explore the complete UI offline:
89
93
 
90
94
  ```bash
@@ -9,6 +9,7 @@ src/benchkit/browser.py
9
9
  src/benchkit/catalog.py
10
10
  src/benchkit/cli.py
11
11
  src/benchkit/client.py
12
+ src/benchkit/config.py
12
13
  src/benchkit/controls.py
13
14
  src/benchkit/decision.py
14
15
  src/benchkit/demo.py
@@ -154,6 +155,7 @@ tests/test_aider_polyglot.py
154
155
  tests/test_banking77.py
155
156
  tests/test_code_verifier.py
156
157
  tests/test_concurrency.py
158
+ tests/test_config.py
157
159
  tests/test_datasets.py
158
160
  tests/test_decision.py
159
161
  tests/test_evalplus.py