hud-python 0.4.45__py3-none-any.whl → 0.5.13__py3-none-any.whl

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 (282) hide show
  1. hud/__init__.py +27 -7
  2. hud/agents/__init__.py +70 -5
  3. hud/agents/base.py +238 -500
  4. hud/agents/claude.py +236 -247
  5. hud/agents/gateway.py +42 -0
  6. hud/agents/gemini.py +264 -0
  7. hud/agents/gemini_cua.py +324 -0
  8. hud/agents/grounded_openai.py +98 -100
  9. hud/agents/misc/integration_test_agent.py +51 -20
  10. hud/agents/misc/response_agent.py +48 -36
  11. hud/agents/openai.py +282 -296
  12. hud/agents/{openai_chat_generic.py → openai_chat.py} +63 -33
  13. hud/agents/operator.py +199 -0
  14. hud/agents/resolver.py +70 -0
  15. hud/agents/tests/conftest.py +133 -0
  16. hud/agents/tests/test_base.py +300 -622
  17. hud/agents/tests/test_base_runtime.py +233 -0
  18. hud/agents/tests/test_claude.py +381 -214
  19. hud/agents/tests/test_client.py +9 -10
  20. hud/agents/tests/test_gemini.py +369 -0
  21. hud/agents/tests/test_grounded_openai_agent.py +65 -50
  22. hud/agents/tests/test_openai.py +377 -140
  23. hud/agents/tests/test_operator.py +362 -0
  24. hud/agents/tests/test_resolver.py +192 -0
  25. hud/agents/tests/test_run_eval.py +179 -0
  26. hud/agents/types.py +148 -0
  27. hud/cli/__init__.py +493 -546
  28. hud/cli/analyze.py +43 -5
  29. hud/cli/build.py +699 -113
  30. hud/cli/debug.py +8 -5
  31. hud/cli/dev.py +889 -732
  32. hud/cli/eval.py +793 -667
  33. hud/cli/flows/dev.py +167 -0
  34. hud/cli/flows/init.py +191 -0
  35. hud/cli/flows/tasks.py +153 -56
  36. hud/cli/flows/templates.py +151 -0
  37. hud/cli/flows/tests/__init__.py +1 -0
  38. hud/cli/flows/tests/test_dev.py +126 -0
  39. hud/cli/init.py +60 -58
  40. hud/cli/pull.py +1 -1
  41. hud/cli/push.py +38 -13
  42. hud/cli/rft.py +311 -0
  43. hud/cli/rft_status.py +145 -0
  44. hud/cli/tests/test_analyze.py +5 -5
  45. hud/cli/tests/test_analyze_metadata.py +3 -2
  46. hud/cli/tests/test_analyze_module.py +120 -0
  47. hud/cli/tests/test_build.py +110 -8
  48. hud/cli/tests/test_build_failure.py +41 -0
  49. hud/cli/tests/test_build_module.py +50 -0
  50. hud/cli/tests/test_cli_init.py +6 -1
  51. hud/cli/tests/test_cli_more_wrappers.py +30 -0
  52. hud/cli/tests/test_cli_root.py +140 -0
  53. hud/cli/tests/test_convert.py +361 -0
  54. hud/cli/tests/test_debug.py +12 -10
  55. hud/cli/tests/test_dev.py +197 -0
  56. hud/cli/tests/test_eval.py +251 -0
  57. hud/cli/tests/test_eval_bedrock.py +51 -0
  58. hud/cli/tests/test_init.py +124 -0
  59. hud/cli/tests/test_main_module.py +11 -5
  60. hud/cli/tests/test_mcp_server.py +12 -100
  61. hud/cli/tests/test_push.py +1 -1
  62. hud/cli/tests/test_push_happy.py +74 -0
  63. hud/cli/tests/test_push_wrapper.py +23 -0
  64. hud/cli/tests/test_registry.py +1 -1
  65. hud/cli/tests/test_utils.py +1 -1
  66. hud/cli/{rl → utils}/celebrate.py +14 -12
  67. hud/cli/utils/config.py +18 -1
  68. hud/cli/utils/docker.py +130 -4
  69. hud/cli/utils/env_check.py +9 -9
  70. hud/cli/utils/git.py +136 -0
  71. hud/cli/utils/interactive.py +39 -5
  72. hud/cli/utils/metadata.py +70 -1
  73. hud/cli/utils/runner.py +1 -1
  74. hud/cli/utils/server.py +2 -2
  75. hud/cli/utils/source_hash.py +3 -3
  76. hud/cli/utils/tasks.py +4 -1
  77. hud/cli/utils/tests/__init__.py +0 -0
  78. hud/cli/utils/tests/test_config.py +58 -0
  79. hud/cli/utils/tests/test_docker.py +93 -0
  80. hud/cli/utils/tests/test_docker_hints.py +71 -0
  81. hud/cli/utils/tests/test_env_check.py +74 -0
  82. hud/cli/utils/tests/test_environment.py +42 -0
  83. hud/cli/utils/tests/test_git.py +142 -0
  84. hud/cli/utils/tests/test_interactive_module.py +60 -0
  85. hud/cli/utils/tests/test_local_runner.py +50 -0
  86. hud/cli/utils/tests/test_logging_utils.py +23 -0
  87. hud/cli/utils/tests/test_metadata.py +49 -0
  88. hud/cli/utils/tests/test_package_runner.py +35 -0
  89. hud/cli/utils/tests/test_registry_utils.py +49 -0
  90. hud/cli/utils/tests/test_remote_runner.py +25 -0
  91. hud/cli/utils/tests/test_runner_modules.py +52 -0
  92. hud/cli/utils/tests/test_source_hash.py +36 -0
  93. hud/cli/utils/tests/test_tasks.py +80 -0
  94. hud/cli/utils/version_check.py +258 -0
  95. hud/cli/{rl → utils}/viewer.py +2 -2
  96. hud/clients/README.md +12 -11
  97. hud/clients/__init__.py +4 -3
  98. hud/clients/base.py +166 -26
  99. hud/clients/environment.py +51 -0
  100. hud/clients/fastmcp.py +13 -6
  101. hud/clients/mcp_use.py +45 -15
  102. hud/clients/tests/test_analyze_scenarios.py +206 -0
  103. hud/clients/tests/test_protocol.py +9 -3
  104. hud/datasets/__init__.py +23 -20
  105. hud/datasets/loader.py +326 -0
  106. hud/datasets/runner.py +198 -105
  107. hud/datasets/tests/__init__.py +0 -0
  108. hud/datasets/tests/test_loader.py +221 -0
  109. hud/datasets/tests/test_utils.py +315 -0
  110. hud/datasets/utils.py +270 -90
  111. hud/environment/__init__.py +52 -0
  112. hud/environment/connection.py +258 -0
  113. hud/environment/connectors/__init__.py +33 -0
  114. hud/environment/connectors/base.py +68 -0
  115. hud/environment/connectors/local.py +177 -0
  116. hud/environment/connectors/mcp_config.py +137 -0
  117. hud/environment/connectors/openai.py +101 -0
  118. hud/environment/connectors/remote.py +172 -0
  119. hud/environment/environment.py +835 -0
  120. hud/environment/integrations/__init__.py +45 -0
  121. hud/environment/integrations/adk.py +67 -0
  122. hud/environment/integrations/anthropic.py +196 -0
  123. hud/environment/integrations/gemini.py +92 -0
  124. hud/environment/integrations/langchain.py +82 -0
  125. hud/environment/integrations/llamaindex.py +68 -0
  126. hud/environment/integrations/openai.py +238 -0
  127. hud/environment/mock.py +306 -0
  128. hud/environment/router.py +263 -0
  129. hud/environment/scenarios.py +620 -0
  130. hud/environment/tests/__init__.py +1 -0
  131. hud/environment/tests/test_connection.py +317 -0
  132. hud/environment/tests/test_connectors.py +205 -0
  133. hud/environment/tests/test_environment.py +593 -0
  134. hud/environment/tests/test_integrations.py +257 -0
  135. hud/environment/tests/test_local_connectors.py +242 -0
  136. hud/environment/tests/test_scenarios.py +1086 -0
  137. hud/environment/tests/test_tools.py +208 -0
  138. hud/environment/types.py +23 -0
  139. hud/environment/utils/__init__.py +35 -0
  140. hud/environment/utils/formats.py +215 -0
  141. hud/environment/utils/schema.py +171 -0
  142. hud/environment/utils/tool_wrappers.py +113 -0
  143. hud/eval/__init__.py +67 -0
  144. hud/eval/context.py +727 -0
  145. hud/eval/display.py +299 -0
  146. hud/eval/instrument.py +187 -0
  147. hud/eval/manager.py +533 -0
  148. hud/eval/parallel.py +268 -0
  149. hud/eval/task.py +372 -0
  150. hud/eval/tests/__init__.py +1 -0
  151. hud/eval/tests/test_context.py +178 -0
  152. hud/eval/tests/test_eval.py +210 -0
  153. hud/eval/tests/test_manager.py +152 -0
  154. hud/eval/tests/test_parallel.py +168 -0
  155. hud/eval/tests/test_task.py +291 -0
  156. hud/eval/types.py +65 -0
  157. hud/eval/utils.py +194 -0
  158. hud/patches/__init__.py +19 -0
  159. hud/patches/mcp_patches.py +308 -0
  160. hud/patches/warnings.py +54 -0
  161. hud/samples/browser.py +4 -4
  162. hud/server/__init__.py +2 -1
  163. hud/server/low_level.py +2 -1
  164. hud/server/router.py +164 -0
  165. hud/server/server.py +567 -80
  166. hud/server/tests/test_mcp_server_integration.py +11 -11
  167. hud/server/tests/test_mcp_server_more.py +1 -1
  168. hud/server/tests/test_server_extra.py +2 -0
  169. hud/settings.py +45 -3
  170. hud/shared/exceptions.py +36 -10
  171. hud/shared/hints.py +26 -1
  172. hud/shared/requests.py +15 -3
  173. hud/shared/tests/test_exceptions.py +40 -31
  174. hud/shared/tests/test_hints.py +167 -0
  175. hud/telemetry/__init__.py +20 -19
  176. hud/telemetry/exporter.py +201 -0
  177. hud/telemetry/instrument.py +165 -253
  178. hud/telemetry/tests/test_eval_telemetry.py +356 -0
  179. hud/telemetry/tests/test_exporter.py +258 -0
  180. hud/telemetry/tests/test_instrument.py +401 -0
  181. hud/tools/__init__.py +18 -2
  182. hud/tools/agent.py +223 -0
  183. hud/tools/apply_patch.py +639 -0
  184. hud/tools/base.py +54 -4
  185. hud/tools/bash.py +2 -2
  186. hud/tools/computer/__init__.py +36 -3
  187. hud/tools/computer/anthropic.py +2 -2
  188. hud/tools/computer/gemini.py +385 -0
  189. hud/tools/computer/hud.py +23 -6
  190. hud/tools/computer/openai.py +20 -21
  191. hud/tools/computer/qwen.py +434 -0
  192. hud/tools/computer/settings.py +37 -0
  193. hud/tools/edit.py +3 -7
  194. hud/tools/executors/base.py +4 -2
  195. hud/tools/executors/pyautogui.py +1 -1
  196. hud/tools/grounding/grounded_tool.py +13 -18
  197. hud/tools/grounding/grounder.py +10 -31
  198. hud/tools/grounding/tests/test_grounded_tool.py +26 -44
  199. hud/tools/jupyter.py +330 -0
  200. hud/tools/playwright.py +18 -3
  201. hud/tools/shell.py +308 -0
  202. hud/tools/tests/test_agent_tool.py +355 -0
  203. hud/tools/tests/test_apply_patch.py +718 -0
  204. hud/tools/tests/test_computer.py +4 -9
  205. hud/tools/tests/test_computer_actions.py +24 -2
  206. hud/tools/tests/test_jupyter_tool.py +181 -0
  207. hud/tools/tests/test_shell.py +596 -0
  208. hud/tools/tests/test_submit.py +85 -0
  209. hud/tools/tests/test_types.py +193 -0
  210. hud/tools/types.py +21 -1
  211. hud/types.py +194 -56
  212. hud/utils/__init__.py +2 -0
  213. hud/utils/env.py +67 -0
  214. hud/utils/hud_console.py +89 -18
  215. hud/utils/mcp.py +15 -58
  216. hud/utils/strict_schema.py +162 -0
  217. hud/utils/tests/test_init.py +1 -2
  218. hud/utils/tests/test_mcp.py +1 -28
  219. hud/utils/tests/test_pretty_errors.py +186 -0
  220. hud/utils/tests/test_tool_shorthand.py +154 -0
  221. hud/utils/tests/test_version.py +1 -1
  222. hud/utils/types.py +20 -0
  223. hud/version.py +1 -1
  224. hud_python-0.5.13.dist-info/METADATA +264 -0
  225. hud_python-0.5.13.dist-info/RECORD +305 -0
  226. {hud_python-0.4.45.dist-info → hud_python-0.5.13.dist-info}/WHEEL +1 -1
  227. hud/agents/langchain.py +0 -261
  228. hud/agents/lite_llm.py +0 -72
  229. hud/cli/rl/__init__.py +0 -180
  230. hud/cli/rl/config.py +0 -101
  231. hud/cli/rl/display.py +0 -133
  232. hud/cli/rl/gpu.py +0 -63
  233. hud/cli/rl/gpu_utils.py +0 -321
  234. hud/cli/rl/local_runner.py +0 -595
  235. hud/cli/rl/presets.py +0 -96
  236. hud/cli/rl/remote_runner.py +0 -463
  237. hud/cli/rl/rl_api.py +0 -150
  238. hud/cli/rl/vllm.py +0 -177
  239. hud/cli/rl/wait_utils.py +0 -89
  240. hud/datasets/parallel.py +0 -687
  241. hud/misc/__init__.py +0 -1
  242. hud/misc/claude_plays_pokemon.py +0 -292
  243. hud/otel/__init__.py +0 -35
  244. hud/otel/collector.py +0 -142
  245. hud/otel/config.py +0 -181
  246. hud/otel/context.py +0 -570
  247. hud/otel/exporters.py +0 -369
  248. hud/otel/instrumentation.py +0 -135
  249. hud/otel/processors.py +0 -121
  250. hud/otel/tests/__init__.py +0 -1
  251. hud/otel/tests/test_processors.py +0 -197
  252. hud/rl/README.md +0 -30
  253. hud/rl/__init__.py +0 -1
  254. hud/rl/actor.py +0 -176
  255. hud/rl/buffer.py +0 -405
  256. hud/rl/chat_template.jinja +0 -101
  257. hud/rl/config.py +0 -192
  258. hud/rl/distributed.py +0 -132
  259. hud/rl/learner.py +0 -637
  260. hud/rl/tests/__init__.py +0 -1
  261. hud/rl/tests/test_learner.py +0 -186
  262. hud/rl/train.py +0 -382
  263. hud/rl/types.py +0 -101
  264. hud/rl/utils/start_vllm_server.sh +0 -30
  265. hud/rl/utils.py +0 -524
  266. hud/rl/vllm_adapter.py +0 -143
  267. hud/telemetry/job.py +0 -352
  268. hud/telemetry/replay.py +0 -74
  269. hud/telemetry/tests/test_replay.py +0 -40
  270. hud/telemetry/tests/test_trace.py +0 -63
  271. hud/telemetry/trace.py +0 -158
  272. hud/utils/agent_factories.py +0 -86
  273. hud/utils/async_utils.py +0 -65
  274. hud/utils/group_eval.py +0 -223
  275. hud/utils/progress.py +0 -149
  276. hud/utils/tasks.py +0 -127
  277. hud/utils/tests/test_async_utils.py +0 -173
  278. hud/utils/tests/test_progress.py +0 -261
  279. hud_python-0.4.45.dist-info/METADATA +0 -552
  280. hud_python-0.4.45.dist-info/RECORD +0 -228
  281. {hud_python-0.4.45.dist-info → hud_python-0.5.13.dist-info}/entry_points.txt +0 -0
  282. {hud_python-0.4.45.dist-info → hud_python-0.5.13.dist-info}/licenses/LICENSE +0 -0
hud/cli/flows/dev.py ADDED
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import contextlib
5
+ import json
6
+ import logging
7
+ from typing import Any
8
+
9
+ from hud.settings import settings
10
+ from hud.shared.requests import make_request
11
+ from hud.utils.hud_console import hud_console
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ async def create_dynamic_trace(
17
+ *,
18
+ mcp_config: dict[str, dict[str, Any]],
19
+ build_status: bool,
20
+ environment_name: str,
21
+ ) -> tuple[str | None, str | None]:
22
+ """
23
+ Create a dynamic trace for HUD dev sessions when running in HTTP mode.
24
+
25
+ Sends a POST to the HUD API with:
26
+ - mcp_config: points to the local MCP config (same as Cursor)
27
+ - build_status: True if Docker mode (built image), False if basic Python mode
28
+ - environment_name: Name of the environment/server/image
29
+ - git_info: Repository information (if available)
30
+
31
+ Returns the full URL to the live trace when successful, otherwise None.
32
+ """
33
+ api_base = settings.hud_api_url.rstrip("/")
34
+ # Endpoint TBD; use a sensible default path that the backend can wire up
35
+ url = f"{api_base}/dev/dynamic-traces"
36
+
37
+ # Get git repository information
38
+ from hud.cli.utils.git import get_git_info
39
+
40
+ git_info = get_git_info()
41
+
42
+ payload = {
43
+ "mcp_config": mcp_config,
44
+ "build_status": bool(build_status),
45
+ "environment_name": environment_name,
46
+ }
47
+
48
+ # Add git info if available
49
+ if git_info and git_info.get("remote_url"):
50
+ payload["git_info"] = git_info
51
+ logger.info("Detected git repository: %s", git_info.get("remote_url"))
52
+ else:
53
+ logger.info("No git repository detected")
54
+
55
+ # Require API key for dev mode
56
+ api_key = settings.api_key
57
+ if not api_key:
58
+ hud_console.error("HUD_API_KEY is required for hud dev command")
59
+ hud_console.info("")
60
+ hud_console.info("Please set your API key using one of these methods:")
61
+ hud_console.info(" 1. Set environment variable: export HUD_API_KEY=your_key")
62
+ hud_console.info(" 2. Use hud set command: hud set api_key your_key")
63
+ hud_console.info("")
64
+ hud_console.info("Get your API key at: https://hud.ai/settings")
65
+ import sys
66
+
67
+ sys.exit(1)
68
+
69
+ try:
70
+ resp = await make_request("POST", url=url, json=payload, api_key=api_key)
71
+ # New API returns an id; construct the URL as https://hud.ai/trace/{id}
72
+ trace_id = resp.get("id")
73
+
74
+ if isinstance(trace_id, str) and trace_id:
75
+ return trace_id, f"https://hud.ai/trace/{trace_id}"
76
+ return None, None
77
+ except Exception as e:
78
+ # Do not interrupt dev flow
79
+ try:
80
+ preview = json.dumps(payload)[:500]
81
+ logger.warning("Failed to create dynamic dev trace: %s | payload=%s", e, preview)
82
+ except Exception:
83
+ logger.warning("Failed to create dynamic dev trace: %s", e)
84
+ return None, None
85
+
86
+
87
+ def show_dev_ui(
88
+ *,
89
+ live_trace_url: str,
90
+ server_name: str,
91
+ port: int,
92
+ cursor_deeplink: str,
93
+ is_docker: bool = False,
94
+ ) -> None:
95
+ """
96
+ Show the minimal dev UI with live trace link.
97
+
98
+ This is called only when we have a successful trace URL.
99
+ For full UI mode, the caller should use show_dev_server_info() directly.
100
+
101
+ Args:
102
+ live_trace_url: URL to the live trace
103
+ server_name: Name of the server/image
104
+ port: Port the server is running on
105
+ cursor_deeplink: Pre-generated Cursor deeplink URL
106
+ is_docker: Whether this is Docker mode (affects hot-reload message)
107
+ """
108
+ import webbrowser
109
+
110
+ from rich.panel import Panel
111
+
112
+ # Show header first
113
+ hud_console.header("HUD Development Server", icon="🚀")
114
+
115
+ # Try to open the live trace in the default browser
116
+ with contextlib.suppress(Exception):
117
+ # new=2 -> open in a new tab, if possible
118
+ webbrowser.open(live_trace_url, new=2)
119
+
120
+ # Show panel with just the link
121
+ # Center the link and style it: blue, bold, underlined
122
+ link_markup = f"[bold underline rgb(108,113,196)][link={live_trace_url}]{live_trace_url}[/link][/bold underline rgb(108,113,196)]" # noqa: E501
123
+ # Use center alignment by surrounding with spaces via justify
124
+ from rich.align import Align
125
+
126
+ panel = Panel(
127
+ Align.center(link_markup),
128
+ title="🔗 Live Dev Trace",
129
+ border_style="rgb(192,150,12)", # HUD gold
130
+ padding=(1, 2),
131
+ )
132
+ hud_console.console.print(panel)
133
+
134
+ # Show other info below
135
+ label = "Base image" if is_docker else "Server"
136
+ hud_console.info("")
137
+ hud_console.info(f"{hud_console.sym.ITEM} {label}: {server_name}")
138
+ hud_console.info(f"{hud_console.sym.ITEM} Cursor:")
139
+ # Display the Cursor link on its own line to prevent wrapping
140
+ hud_console.link(cursor_deeplink)
141
+ hud_console.info("")
142
+ hud_console.info(f"{hud_console.sym.SUCCESS} Hot-reload enabled")
143
+ if is_docker:
144
+ hud_console.dim_info(
145
+ "",
146
+ "Container restarts on file changes in watched folders (-w), "
147
+ "rebuild with 'hud dev' if changing other files",
148
+ )
149
+ hud_console.info("")
150
+
151
+
152
+ def generate_cursor_deeplink(server_name: str, port: int) -> str:
153
+ """Generate a Cursor deeplink for the MCP server.
154
+
155
+ Args:
156
+ server_name: Name of the server
157
+ port: Port the server is running on
158
+
159
+ Returns:
160
+ Cursor deeplink URL
161
+ """
162
+ server_config = {"url": f"http://localhost:{port}/mcp"}
163
+ config_json = json.dumps(server_config, indent=2)
164
+ config_base64 = base64.b64encode(config_json.encode()).decode()
165
+ return (
166
+ f"cursor://anysphere.cursor-deeplink/mcp/install?name={server_name}&config={config_base64}"
167
+ )
hud/cli/flows/init.py ADDED
@@ -0,0 +1,191 @@
1
+ """Smart HUD environment initialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from hud.utils.hud_console import HUDConsole
9
+
10
+ from .templates import DOCKERFILE_HUD, ENV_PY, PYPROJECT_TOML
11
+
12
+ # Files that indicate this might be an existing project
13
+ PROJECT_INDICATORS = {
14
+ "pyproject.toml",
15
+ "package.json",
16
+ "requirements.txt",
17
+ "setup.py",
18
+ "Cargo.toml",
19
+ "go.mod",
20
+ }
21
+
22
+
23
+ def _normalize_name(name: str) -> str:
24
+ """Normalize name for Python identifiers."""
25
+ name = name.replace("-", "_").replace(" ", "_")
26
+ return "".join(c if c.isalnum() or c == "_" else "_" for c in name)
27
+
28
+
29
+ def _has_hud_dependency(directory: Path) -> bool:
30
+ """Check if hud-python is already in pyproject.toml."""
31
+ pyproject = directory / "pyproject.toml"
32
+ if not pyproject.exists():
33
+ return False
34
+ content = pyproject.read_text()
35
+ return "hud-python" in content or "hud_python" in content
36
+
37
+
38
+ def _add_hud_dependency(directory: Path) -> str:
39
+ """Add hud-python using uv if available.
40
+
41
+ Returns:
42
+ "exists" if already present, "added" if added, "failed" if failed
43
+ """
44
+ if _has_hud_dependency(directory):
45
+ return "exists"
46
+
47
+ try:
48
+ result = subprocess.run(
49
+ ["uv", "add", "hud-python", "openai"], # noqa: S607
50
+ capture_output=True,
51
+ text=True,
52
+ cwd=directory,
53
+ check=False,
54
+ )
55
+ if result.returncode == 0 or "already" in result.stderr.lower():
56
+ return "added"
57
+ return "failed"
58
+ except FileNotFoundError:
59
+ return "failed"
60
+
61
+
62
+ def _is_empty_or_trivial(directory: Path) -> bool:
63
+ """Check if directory is empty or only has trivial files."""
64
+ if not directory.exists():
65
+ return True
66
+ files = list(directory.iterdir())
67
+ # Empty
68
+ if not files:
69
+ return True
70
+ # Only has hidden files or common trivial files
71
+ trivial = {".git", ".gitignore", ".DS_Store", "README.md", "LICENSE"}
72
+ return all(f.name in trivial or f.name.startswith(".") for f in files)
73
+
74
+
75
+ def _has_project_files(directory: Path) -> bool:
76
+ """Check if directory has files indicating an existing project."""
77
+ if not directory.exists():
78
+ return False
79
+ return any(f.name in PROJECT_INDICATORS for f in directory.iterdir())
80
+
81
+
82
+ def smart_init(
83
+ name: str | None = None,
84
+ directory: str = ".",
85
+ force: bool = False,
86
+ ) -> None:
87
+ """Initialize HUD environment files in a directory.
88
+
89
+ - If directory is empty: delegate to preset selection
90
+ - If directory has project files: add HUD files to existing project
91
+ - Otherwise: create new HUD environment
92
+ """
93
+ from hud.settings import settings
94
+
95
+ hud_console = HUDConsole()
96
+
97
+ # Check for API key first
98
+ if not settings.api_key:
99
+ hud_console.error("HUD_API_KEY not found")
100
+ hud_console.info("")
101
+ hud_console.info("Set your API key:")
102
+ hud_console.info(" hud set HUD_API_KEY=your-key-here")
103
+ hud_console.info(" Or: export HUD_API_KEY=your-key")
104
+ hud_console.info("")
105
+ hud_console.info("Get your key at: https://hud.ai/project/api-keys")
106
+ return
107
+
108
+ target = Path(directory).resolve()
109
+
110
+ # If directory is empty, use preset selection
111
+ if _is_empty_or_trivial(target):
112
+ from hud.cli.init import create_environment
113
+
114
+ hud_console.info("Empty directory - showing preset selection")
115
+ create_environment(name, directory, force, preset=None)
116
+ return
117
+
118
+ # Directory has files - use smart init
119
+ target.mkdir(parents=True, exist_ok=True)
120
+ env_name = _normalize_name(name or target.name)
121
+ has_pyproject = (target / "pyproject.toml").exists()
122
+
123
+ hud_console.header(f"HUD Init: {env_name}")
124
+
125
+ if has_pyproject:
126
+ hud_console.info("Found pyproject.toml - adding HUD files")
127
+ else:
128
+ hud_console.info("Creating HUD environment in existing directory")
129
+
130
+ created = []
131
+
132
+ # Create pyproject.toml if needed
133
+ if not has_pyproject:
134
+ pyproject = target / "pyproject.toml"
135
+ pyproject.write_text(PYPROJECT_TOML.format(name=env_name.replace("_", "-")))
136
+ created.append("pyproject.toml")
137
+
138
+ # Create Dockerfile.hud
139
+ dockerfile = target / "Dockerfile.hud"
140
+ if not dockerfile.exists() or force:
141
+ dockerfile.write_text(DOCKERFILE_HUD)
142
+ created.append("Dockerfile.hud")
143
+ else:
144
+ hud_console.warning("Dockerfile.hud exists, skipping (use --force)")
145
+
146
+ # Create env.py
147
+ env_py = target / "env.py"
148
+ if not env_py.exists() or force:
149
+ env_py.write_text(ENV_PY.format(env_name=env_name))
150
+ created.append("env.py")
151
+ else:
152
+ hud_console.warning("env.py exists, skipping (use --force)")
153
+
154
+ # Add dependency
155
+ dep_result = _add_hud_dependency(target)
156
+ if dep_result == "added":
157
+ hud_console.success("Added hud-python dependency")
158
+ elif dep_result == "exists":
159
+ hud_console.info("hud-python already in dependencies")
160
+ else:
161
+ hud_console.info("Run manually: uv add hud-python openai")
162
+
163
+ # Summary
164
+ if created:
165
+ hud_console.section_title("Created")
166
+ for f in created:
167
+ hud_console.status_item(f, "✓")
168
+
169
+ hud_console.section_title("Next Steps")
170
+ hud_console.info("")
171
+ hud_console.info("1. Define your tools in env.py")
172
+ hud_console.info(" Tools are functions the agent can call. Wrap existing code")
173
+ hud_console.info(" with @env.tool() or connect FastAPI/OpenAPI servers.")
174
+ hud_console.info("")
175
+ hud_console.info("2. Write scripts that test agent behavior")
176
+ hud_console.info(" Scripts define prompts and scoring. The agent runs between")
177
+ hud_console.info(" two yields: first sends the task, second scores the result.")
178
+ hud_console.info("")
179
+ hud_console.info("3. Run locally to iterate")
180
+ hud_console.command_example("python env.py", "Run the test script")
181
+ hud_console.info("")
182
+ hud_console.info("4. Deploy for scale")
183
+ hud_console.info(" Push to GitHub, connect on hud.ai. Then run hundreds of")
184
+ hud_console.info(" evals in parallel and collect training data.")
185
+ hud_console.info("")
186
+ hud_console.section_title("Files")
187
+ hud_console.info("• env.py Your tools, scripts, and test code")
188
+ hud_console.info("• Dockerfile.hud Container config for remote deployment")
189
+
190
+
191
+ __all__ = ["smart_init"]