hud-python 0.4.1__py3-none-any.whl → 0.4.3__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.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/tools/bash.py
CHANGED
|
@@ -1,161 +1,161 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
-
|
|
8
|
-
from .base import BaseTool
|
|
9
|
-
from .types import ContentResult, ToolError
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from mcp.types import ContentBlock
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class _BashSession:
|
|
16
|
-
"""A session of a bash shell."""
|
|
17
|
-
|
|
18
|
-
_started: bool
|
|
19
|
-
_process: asyncio.subprocess.Process
|
|
20
|
-
|
|
21
|
-
command: str = "/bin/bash"
|
|
22
|
-
_output_delay: float = 0.2 # seconds
|
|
23
|
-
_timeout: float = 120.0 # seconds
|
|
24
|
-
_sentinel: str = "<<exit>>"
|
|
25
|
-
|
|
26
|
-
def __init__(self) -> None:
|
|
27
|
-
self._started = False
|
|
28
|
-
self._timed_out = False
|
|
29
|
-
|
|
30
|
-
async def start(self) -> None:
|
|
31
|
-
if self._started:
|
|
32
|
-
await asyncio.sleep(0)
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
# Platform-specific subprocess creation
|
|
36
|
-
kwargs = {
|
|
37
|
-
"shell": True,
|
|
38
|
-
"bufsize": 0,
|
|
39
|
-
"stdin": asyncio.subprocess.PIPE,
|
|
40
|
-
"stdout": asyncio.subprocess.PIPE,
|
|
41
|
-
"stderr": asyncio.subprocess.PIPE,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
# Only use setsid on Unix-like systems
|
|
45
|
-
if sys.platform != "win32":
|
|
46
|
-
kwargs["preexec_fn"] = os.setsid
|
|
47
|
-
|
|
48
|
-
self._process = await asyncio.create_subprocess_shell(self.command, **kwargs)
|
|
49
|
-
|
|
50
|
-
self._started = True
|
|
51
|
-
|
|
52
|
-
def stop(self) -> None:
|
|
53
|
-
"""Terminate the bash shell."""
|
|
54
|
-
if not self._started:
|
|
55
|
-
raise ToolError("Session has not started.")
|
|
56
|
-
if self._process.returncode is not None:
|
|
57
|
-
return
|
|
58
|
-
self._process.terminate()
|
|
59
|
-
|
|
60
|
-
async def run(self, command: str) -> ContentResult:
|
|
61
|
-
"""Execute a command in the bash shell."""
|
|
62
|
-
if not self._started:
|
|
63
|
-
raise ToolError("Session has not started.")
|
|
64
|
-
if self._process.returncode is not None:
|
|
65
|
-
await asyncio.sleep(0)
|
|
66
|
-
return ContentResult(
|
|
67
|
-
system="tool must be restarted",
|
|
68
|
-
error=f"bash has exited with returncode {self._process.returncode}",
|
|
69
|
-
)
|
|
70
|
-
if self._timed_out:
|
|
71
|
-
raise ToolError(
|
|
72
|
-
f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
|
|
73
|
-
) from None
|
|
74
|
-
|
|
75
|
-
if self._process.stdin is None:
|
|
76
|
-
raise ToolError("stdin is None")
|
|
77
|
-
if self._process.stdout is None:
|
|
78
|
-
raise ToolError("stdout is None")
|
|
79
|
-
if self._process.stderr is None:
|
|
80
|
-
raise ToolError("stderr is None")
|
|
81
|
-
|
|
82
|
-
# Send command to the process
|
|
83
|
-
self._process.stdin.write(command.encode() + f"; echo '{self._sentinel}'\n".encode())
|
|
84
|
-
await self._process.stdin.drain()
|
|
85
|
-
|
|
86
|
-
# Read output from the process, until the sentinel is found
|
|
87
|
-
sentinel_line = f"{self._sentinel}\n"
|
|
88
|
-
sentinel_bytes = sentinel_line.encode()
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
raw_out: bytes = await asyncio.wait_for(
|
|
92
|
-
self._process.stdout.readuntil(sentinel_bytes),
|
|
93
|
-
timeout=self._timeout,
|
|
94
|
-
)
|
|
95
|
-
output = raw_out.decode()[: -len(sentinel_line)]
|
|
96
|
-
except (TimeoutError, asyncio.LimitOverrunError):
|
|
97
|
-
self._timed_out = True
|
|
98
|
-
raise ToolError(
|
|
99
|
-
f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
|
|
100
|
-
) from None
|
|
101
|
-
|
|
102
|
-
# Attempt non-blocking stderr fetch (may return empty)
|
|
103
|
-
try:
|
|
104
|
-
error_bytes = await asyncio.wait_for(self._process.stderr.read(), timeout=0.01)
|
|
105
|
-
error = error_bytes.decode().rstrip("\n")
|
|
106
|
-
except TimeoutError:
|
|
107
|
-
error = ""
|
|
108
|
-
|
|
109
|
-
return ContentResult(output=output, error=error)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class BashTool(BaseTool):
|
|
113
|
-
"""
|
|
114
|
-
A tool that allows the agent to run bash commands.
|
|
115
|
-
The tool maintains a persistent bash session that can be restarted.
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
def __init__(self, session: _BashSession | None = None) -> None:
|
|
119
|
-
"""Initialize BashTool with an optional session.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
session: Optional pre-configured bash session. If not provided,
|
|
123
|
-
a new session will be created on first use.
|
|
124
|
-
"""
|
|
125
|
-
super().__init__(
|
|
126
|
-
env=session,
|
|
127
|
-
name="bash",
|
|
128
|
-
title="Bash Shell",
|
|
129
|
-
description="Execute bash commands in a persistent shell session",
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
@property
|
|
133
|
-
def session(self) -> _BashSession | None:
|
|
134
|
-
"""Get the current bash session (alias for context)."""
|
|
135
|
-
return self.env
|
|
136
|
-
|
|
137
|
-
@session.setter
|
|
138
|
-
def session(self, value: _BashSession | None) -> None:
|
|
139
|
-
"""Set the bash session (alias for context)."""
|
|
140
|
-
self.env = value
|
|
141
|
-
|
|
142
|
-
async def __call__(
|
|
143
|
-
self, command: str | None = None, restart: bool = False, **kwargs: Any
|
|
144
|
-
) -> list[ContentBlock]:
|
|
145
|
-
if restart:
|
|
146
|
-
if self.session:
|
|
147
|
-
self.session.stop()
|
|
148
|
-
self.session = _BashSession()
|
|
149
|
-
await self.session.start()
|
|
150
|
-
|
|
151
|
-
return ContentResult(output="Bash session restarted.").to_content_blocks()
|
|
152
|
-
|
|
153
|
-
if self.session is None:
|
|
154
|
-
self.session = _BashSession()
|
|
155
|
-
await self.session.start()
|
|
156
|
-
|
|
157
|
-
if command is not None:
|
|
158
|
-
result = await self.session.run(command)
|
|
159
|
-
return result.to_content_blocks()
|
|
160
|
-
|
|
161
|
-
raise ToolError("No command provided.")
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from .base import BaseTool
|
|
9
|
+
from .types import ContentResult, ToolError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from mcp.types import ContentBlock
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _BashSession:
|
|
16
|
+
"""A session of a bash shell."""
|
|
17
|
+
|
|
18
|
+
_started: bool
|
|
19
|
+
_process: asyncio.subprocess.Process
|
|
20
|
+
|
|
21
|
+
command: str = "/bin/bash"
|
|
22
|
+
_output_delay: float = 0.2 # seconds
|
|
23
|
+
_timeout: float = 120.0 # seconds
|
|
24
|
+
_sentinel: str = "<<exit>>"
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self._started = False
|
|
28
|
+
self._timed_out = False
|
|
29
|
+
|
|
30
|
+
async def start(self) -> None:
|
|
31
|
+
if self._started:
|
|
32
|
+
await asyncio.sleep(0)
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# Platform-specific subprocess creation
|
|
36
|
+
kwargs = {
|
|
37
|
+
"shell": True,
|
|
38
|
+
"bufsize": 0,
|
|
39
|
+
"stdin": asyncio.subprocess.PIPE,
|
|
40
|
+
"stdout": asyncio.subprocess.PIPE,
|
|
41
|
+
"stderr": asyncio.subprocess.PIPE,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Only use setsid on Unix-like systems
|
|
45
|
+
if sys.platform != "win32":
|
|
46
|
+
kwargs["preexec_fn"] = os.setsid
|
|
47
|
+
|
|
48
|
+
self._process = await asyncio.create_subprocess_shell(self.command, **kwargs)
|
|
49
|
+
|
|
50
|
+
self._started = True
|
|
51
|
+
|
|
52
|
+
def stop(self) -> None:
|
|
53
|
+
"""Terminate the bash shell."""
|
|
54
|
+
if not self._started:
|
|
55
|
+
raise ToolError("Session has not started.")
|
|
56
|
+
if self._process.returncode is not None:
|
|
57
|
+
return
|
|
58
|
+
self._process.terminate()
|
|
59
|
+
|
|
60
|
+
async def run(self, command: str) -> ContentResult:
|
|
61
|
+
"""Execute a command in the bash shell."""
|
|
62
|
+
if not self._started:
|
|
63
|
+
raise ToolError("Session has not started.")
|
|
64
|
+
if self._process.returncode is not None:
|
|
65
|
+
await asyncio.sleep(0)
|
|
66
|
+
return ContentResult(
|
|
67
|
+
system="tool must be restarted",
|
|
68
|
+
error=f"bash has exited with returncode {self._process.returncode}",
|
|
69
|
+
)
|
|
70
|
+
if self._timed_out:
|
|
71
|
+
raise ToolError(
|
|
72
|
+
f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
|
|
73
|
+
) from None
|
|
74
|
+
|
|
75
|
+
if self._process.stdin is None:
|
|
76
|
+
raise ToolError("stdin is None")
|
|
77
|
+
if self._process.stdout is None:
|
|
78
|
+
raise ToolError("stdout is None")
|
|
79
|
+
if self._process.stderr is None:
|
|
80
|
+
raise ToolError("stderr is None")
|
|
81
|
+
|
|
82
|
+
# Send command to the process
|
|
83
|
+
self._process.stdin.write(command.encode() + f"; echo '{self._sentinel}'\n".encode())
|
|
84
|
+
await self._process.stdin.drain()
|
|
85
|
+
|
|
86
|
+
# Read output from the process, until the sentinel is found
|
|
87
|
+
sentinel_line = f"{self._sentinel}\n"
|
|
88
|
+
sentinel_bytes = sentinel_line.encode()
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
raw_out: bytes = await asyncio.wait_for(
|
|
92
|
+
self._process.stdout.readuntil(sentinel_bytes),
|
|
93
|
+
timeout=self._timeout,
|
|
94
|
+
)
|
|
95
|
+
output = raw_out.decode()[: -len(sentinel_line)]
|
|
96
|
+
except (TimeoutError, asyncio.LimitOverrunError):
|
|
97
|
+
self._timed_out = True
|
|
98
|
+
raise ToolError(
|
|
99
|
+
f"timed out: bash did not return in {self._timeout} seconds and must be restarted",
|
|
100
|
+
) from None
|
|
101
|
+
|
|
102
|
+
# Attempt non-blocking stderr fetch (may return empty)
|
|
103
|
+
try:
|
|
104
|
+
error_bytes = await asyncio.wait_for(self._process.stderr.read(), timeout=0.01)
|
|
105
|
+
error = error_bytes.decode().rstrip("\n")
|
|
106
|
+
except TimeoutError:
|
|
107
|
+
error = ""
|
|
108
|
+
|
|
109
|
+
return ContentResult(output=output, error=error)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class BashTool(BaseTool):
|
|
113
|
+
"""
|
|
114
|
+
A tool that allows the agent to run bash commands.
|
|
115
|
+
The tool maintains a persistent bash session that can be restarted.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self, session: _BashSession | None = None) -> None:
|
|
119
|
+
"""Initialize BashTool with an optional session.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
session: Optional pre-configured bash session. If not provided,
|
|
123
|
+
a new session will be created on first use.
|
|
124
|
+
"""
|
|
125
|
+
super().__init__(
|
|
126
|
+
env=session,
|
|
127
|
+
name="bash",
|
|
128
|
+
title="Bash Shell",
|
|
129
|
+
description="Execute bash commands in a persistent shell session",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def session(self) -> _BashSession | None:
|
|
134
|
+
"""Get the current bash session (alias for context)."""
|
|
135
|
+
return self.env
|
|
136
|
+
|
|
137
|
+
@session.setter
|
|
138
|
+
def session(self, value: _BashSession | None) -> None:
|
|
139
|
+
"""Set the bash session (alias for context)."""
|
|
140
|
+
self.env = value
|
|
141
|
+
|
|
142
|
+
async def __call__(
|
|
143
|
+
self, command: str | None = None, restart: bool = False, **kwargs: Any
|
|
144
|
+
) -> list[ContentBlock]:
|
|
145
|
+
if restart:
|
|
146
|
+
if self.session:
|
|
147
|
+
self.session.stop()
|
|
148
|
+
self.session = _BashSession()
|
|
149
|
+
await self.session.start()
|
|
150
|
+
|
|
151
|
+
return ContentResult(output="Bash session restarted.").to_content_blocks()
|
|
152
|
+
|
|
153
|
+
if self.session is None:
|
|
154
|
+
self.session = _BashSession()
|
|
155
|
+
await self.session.start()
|
|
156
|
+
|
|
157
|
+
if command is not None:
|
|
158
|
+
result = await self.session.run(command)
|
|
159
|
+
return result.to_content_blocks()
|
|
160
|
+
|
|
161
|
+
raise ToolError("No command provided.")
|
hud/tools/computer/__init__.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"""Computer control tools for different agent APIs."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from .anthropic import AnthropicComputerTool
|
|
6
|
-
from .hud import HudComputerTool
|
|
7
|
-
from .openai import OpenAIComputerTool
|
|
8
|
-
from .settings import computer_settings
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"AnthropicComputerTool",
|
|
12
|
-
"HudComputerTool",
|
|
13
|
-
"OpenAIComputerTool",
|
|
14
|
-
"computer_settings",
|
|
15
|
-
]
|
|
1
|
+
"""Computer control tools for different agent APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .anthropic import AnthropicComputerTool
|
|
6
|
+
from .hud import HudComputerTool
|
|
7
|
+
from .openai import OpenAIComputerTool
|
|
8
|
+
from .settings import computer_settings
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AnthropicComputerTool",
|
|
12
|
+
"HudComputerTool",
|
|
13
|
+
"OpenAIComputerTool",
|
|
14
|
+
"computer_settings",
|
|
15
|
+
]
|