Spaces:
Runtime error
Runtime error
WHOIS and fixes
#3
by
jponf
- opened
- app.py +36 -12
- packages.txt +1 -0
- pyproject.toml +8 -2
- requirements-dev.txt +2 -194
- requirements.txt +25 -152
- tdagent/tools/get_url_content.py +9 -5
- tdagent/tools/internal_company_user_search.py +15 -11
- tdagent/tools/lookup_company_cloud_account_information.py +37 -32
- tdagent/tools/query_abuse_ip_db.py +50 -40
- tdagent/tools/send_email.py +30 -32
- tdagent/tools/virus_total.py +23 -35
- tdagent/tools/whois.py +35 -0
- tdagent/utils/__init__.py +0 -0
- tdagent/utils/json_utils.py +14 -0
- uv.lock +0 -0
app.py
CHANGED
@@ -1,23 +1,47 @@
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
|
3 |
from tdagent.tools.get_url_content import gr_get_url_http_content
|
4 |
from tdagent.tools.internal_company_user_search import gr_internal_company
|
5 |
-
from tdagent.tools.
|
6 |
-
|
|
|
7 |
from tdagent.tools.query_abuse_ip_db import gr_query_abuseipdb
|
8 |
from tdagent.tools.send_email import gr_send_email
|
9 |
-
from tdagent.tools.virus_total import
|
|
|
10 |
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
gr_lookup_company_cloud_account_information,
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
)
|
22 |
|
23 |
if __name__ == "__main__":
|
|
|
1 |
+
from typing import NamedTuple
|
2 |
+
|
3 |
import gradio as gr
|
4 |
|
5 |
from tdagent.tools.get_url_content import gr_get_url_http_content
|
6 |
from tdagent.tools.internal_company_user_search import gr_internal_company
|
7 |
+
from tdagent.tools.lookup_company_cloud_account_information import (
|
8 |
+
gr_lookup_company_cloud_account_information,
|
9 |
+
)
|
10 |
from tdagent.tools.query_abuse_ip_db import gr_query_abuseipdb
|
11 |
from tdagent.tools.send_email import gr_send_email
|
12 |
+
from tdagent.tools.virus_total import gr_virus_total_url_info
|
13 |
+
from tdagent.tools.whois import gr_query_whois
|
14 |
|
15 |
+
|
16 |
+
## Tools to load into the application interface ##
|
17 |
+
|
18 |
+
|
19 |
+
class ToolInfo(NamedTuple):
|
20 |
+
"""Gradio MCP tool info."""
|
21 |
+
|
22 |
+
name: str
|
23 |
+
interface: gr.Interface
|
24 |
+
|
25 |
+
|
26 |
+
TOOLS = (
|
27 |
+
ToolInfo("Get URL Content", gr_get_url_http_content),
|
28 |
+
ToolInfo("Query AbuseIPDB", gr_query_abuseipdb),
|
29 |
+
ToolInfo("Query WHOIS", gr_query_whois),
|
30 |
+
ToolInfo("Virus Total URL info", gr_virus_total_url_info),
|
31 |
+
## Fake tools
|
32 |
+
ToolInfo("Fake company directory", gr_internal_company),
|
33 |
+
ToolInfo(
|
34 |
+
"Fake company cloud accounts",
|
35 |
gr_lookup_company_cloud_account_information,
|
36 |
+
),
|
37 |
+
ToolInfo("Send email", gr_send_email),
|
38 |
+
)
|
39 |
+
|
40 |
+
## Application Interface ##
|
41 |
+
gr_app = gr.TabbedInterface(
|
42 |
+
interface_list=[t_info.interface for t_info in TOOLS],
|
43 |
+
tab_names=[t_info.name for t_info in TOOLS],
|
44 |
+
title="TDAgentTools",
|
45 |
)
|
46 |
|
47 |
if __name__ == "__main__":
|
packages.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
whois
|
pyproject.toml
CHANGED
@@ -12,7 +12,13 @@ authors = [
|
|
12 |
requires-python = ">=3.10,<4"
|
13 |
readme = "README.md"
|
14 |
license = ""
|
15 |
-
dependencies = [
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
[project.scripts]
|
18 |
|
@@ -98,7 +104,7 @@ line-length = 88
|
|
98 |
|
99 |
[tool.ruff.lint]
|
100 |
select = ["ALL"]
|
101 |
-
ignore = ["D100", "D104", "D107", "D401", "EM102", "ERA001", "TRY003"]
|
102 |
|
103 |
[tool.ruff.lint.flake8-quotes]
|
104 |
inline-quotes = "double"
|
|
|
12 |
requires-python = ">=3.10,<4"
|
13 |
readme = "README.md"
|
14 |
license = ""
|
15 |
+
dependencies = [
|
16 |
+
"cachetools>=6.0.0",
|
17 |
+
"gradio[mcp]>=5.32.1",
|
18 |
+
"python-whois>=0.9.5",
|
19 |
+
"requests>=2.32.3",
|
20 |
+
"vt-py~=0.21.0",
|
21 |
+
]
|
22 |
|
23 |
[project.scripts]
|
24 |
|
|
|
104 |
|
105 |
[tool.ruff.lint]
|
106 |
select = ["ALL"]
|
107 |
+
ignore = ["D100", "D104", "D107", "D401", "EM102", "ERA001", "TRY003", "UP038"]
|
108 |
|
109 |
[tool.ruff.lint.flake8-quotes]
|
110 |
inline-quotes = "double"
|
requirements-dev.txt
CHANGED
@@ -1,301 +1,109 @@
|
|
1 |
# This file was autogenerated by uv via the following command:
|
2 |
# uv export --format requirements-txt --no-hashes --group dev --group test -o requirements-dev.txt
|
3 |
aiofiles==24.1.0
|
4 |
-
# via
|
5 |
-
# gradio
|
6 |
-
# vt-py
|
7 |
aiohappyeyeballs==2.6.1
|
8 |
-
# via aiohttp
|
9 |
aiohttp==3.12.8
|
10 |
-
# via vt-py
|
11 |
aiosignal==1.3.2
|
12 |
-
# via aiohttp
|
13 |
annotated-types==0.7.0
|
14 |
-
# via pydantic
|
15 |
anyio==4.9.0
|
16 |
-
# via
|
17 |
-
# gradio
|
18 |
-
# httpx
|
19 |
-
# mcp
|
20 |
-
# sse-starlette
|
21 |
-
# starlette
|
22 |
async-timeout==5.0.1 ; python_full_version < '3.11'
|
23 |
-
# via aiohttp
|
24 |
attrs==25.3.0
|
25 |
-
# via aiohttp
|
26 |
audioop-lts==0.2.1 ; python_full_version >= '3.13'
|
27 |
-
# via gradio
|
28 |
boolean-py==5.0
|
29 |
-
# via license-expression
|
30 |
cachecontrol==0.14.3
|
31 |
-
|
32 |
certifi==2025.4.26
|
33 |
-
# via
|
34 |
-
# httpcore
|
35 |
-
# httpx
|
36 |
-
# requests
|
37 |
cfgv==3.4.0
|
38 |
-
# via pre-commit
|
39 |
charset-normalizer==3.4.2
|
40 |
-
# via requests
|
41 |
click==8.2.1 ; sys_platform != 'emscripten'
|
42 |
-
# via
|
43 |
-
# typer
|
44 |
-
# uvicorn
|
45 |
colorama==0.4.6 ; sys_platform == 'win32'
|
46 |
-
# via
|
47 |
-
# click
|
48 |
-
# pytest
|
49 |
-
# tqdm
|
50 |
coverage==7.8.2
|
51 |
-
# via pytest-cov
|
52 |
cyclonedx-python-lib==9.1.0
|
53 |
-
# via pip-audit
|
54 |
defusedxml==0.7.1
|
55 |
-
# via py-serializable
|
56 |
distlib==0.3.9
|
57 |
-
# via virtualenv
|
58 |
exceptiongroup==1.3.0 ; python_full_version < '3.11'
|
59 |
-
# via
|
60 |
-
# anyio
|
61 |
-
# pytest
|
62 |
fastapi==0.115.12
|
63 |
-
# via gradio
|
64 |
ffmpy==0.6.0
|
65 |
-
# via gradio
|
66 |
filelock==3.18.0
|
67 |
-
# via
|
68 |
-
# cachecontrol
|
69 |
-
# huggingface-hub
|
70 |
-
# virtualenv
|
71 |
frozenlist==1.6.2
|
72 |
-
# via
|
73 |
-
# aiohttp
|
74 |
-
# aiosignal
|
75 |
fsspec==2025.5.1
|
76 |
-
# via
|
77 |
-
# gradio-client
|
78 |
-
# huggingface-hub
|
79 |
gradio==5.32.1
|
80 |
-
# via tdagent
|
81 |
gradio-client==1.10.2
|
82 |
-
# via gradio
|
83 |
groovy==0.1.2
|
84 |
-
# via gradio
|
85 |
h11==0.16.0
|
86 |
-
# via
|
87 |
-
# httpcore
|
88 |
-
# uvicorn
|
89 |
hf-xet==1.1.2 ; platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'
|
90 |
-
# via huggingface-hub
|
91 |
httpcore==1.0.9
|
92 |
-
# via httpx
|
93 |
httpx==0.28.1
|
94 |
-
# via
|
95 |
-
# gradio
|
96 |
-
# gradio-client
|
97 |
-
# mcp
|
98 |
-
# safehttpx
|
99 |
httpx-sse==0.4.0
|
100 |
-
# via mcp
|
101 |
huggingface-hub==0.32.4
|
102 |
-
# via
|
103 |
-
# gradio
|
104 |
-
# gradio-client
|
105 |
identify==2.6.12
|
106 |
-
# via pre-commit
|
107 |
idna==3.10
|
108 |
-
# via
|
109 |
-
# anyio
|
110 |
-
# httpx
|
111 |
-
# requests
|
112 |
-
# yarl
|
113 |
iniconfig==2.1.0
|
114 |
-
# via pytest
|
115 |
jinja2==3.1.6
|
116 |
-
# via gradio
|
117 |
license-expression==30.4.1
|
118 |
-
# via cyclonedx-python-lib
|
119 |
markdown-it-py==3.0.0
|
120 |
-
# via rich
|
121 |
markupsafe==3.0.2
|
122 |
-
# via
|
123 |
-
# gradio
|
124 |
-
# jinja2
|
125 |
mcp==1.9.0
|
126 |
-
# via gradio
|
127 |
mdurl==0.1.2
|
128 |
-
# via markdown-it-py
|
129 |
msgpack==1.1.0
|
130 |
-
# via cachecontrol
|
131 |
multidict==6.4.4
|
132 |
-
# via
|
133 |
-
# aiohttp
|
134 |
-
# yarl
|
135 |
mypy==1.16.0
|
136 |
mypy-extensions==1.1.0
|
137 |
-
# via mypy
|
138 |
nodeenv==1.9.1
|
139 |
-
# via pre-commit
|
140 |
numpy==2.2.6
|
141 |
-
# via
|
142 |
-
# gradio
|
143 |
-
# pandas
|
144 |
orjson==3.10.18
|
145 |
-
# via gradio
|
146 |
packageurl-python==0.16.0
|
147 |
-
# via cyclonedx-python-lib
|
148 |
packaging==25.0
|
149 |
-
# via
|
150 |
-
# gradio
|
151 |
-
# gradio-client
|
152 |
-
# huggingface-hub
|
153 |
-
# pip-audit
|
154 |
-
# pip-requirements-parser
|
155 |
-
# pytest
|
156 |
pandas==2.2.3
|
157 |
-
# via gradio
|
158 |
pathspec==0.12.1
|
159 |
-
# via mypy
|
160 |
pillow==11.2.1
|
161 |
-
# via gradio
|
162 |
pip==25.1.1
|
163 |
-
# via pip-api
|
164 |
pip-api==0.0.34
|
165 |
-
# via pip-audit
|
166 |
pip-audit==2.9.0
|
167 |
pip-requirements-parser==32.0.1
|
168 |
-
# via pip-audit
|
169 |
platformdirs==4.3.8
|
170 |
-
# via
|
171 |
-
# pip-audit
|
172 |
-
# virtualenv
|
173 |
pluggy==1.6.0
|
174 |
-
# via pytest
|
175 |
pre-commit==3.8.0
|
176 |
propcache==0.3.1
|
177 |
-
# via
|
178 |
-
# aiohttp
|
179 |
-
# yarl
|
180 |
py-serializable==2.0.0
|
181 |
-
# via cyclonedx-python-lib
|
182 |
pydantic==2.11.5
|
183 |
-
# via
|
184 |
-
# fastapi
|
185 |
-
# gradio
|
186 |
-
# mcp
|
187 |
-
# pydantic-settings
|
188 |
pydantic-core==2.33.2
|
189 |
-
# via pydantic
|
190 |
pydantic-settings==2.9.1
|
191 |
-
# via mcp
|
192 |
pydub==0.25.1
|
193 |
-
# via gradio
|
194 |
pygments==2.19.1
|
195 |
-
# via rich
|
196 |
pyparsing==3.2.3
|
197 |
-
# via pip-requirements-parser
|
198 |
pytest==7.4.4
|
199 |
-
# via
|
200 |
-
# pytest-cov
|
201 |
-
# pytest-randomly
|
202 |
pytest-cov==4.1.0
|
203 |
pytest-randomly==3.16.0
|
204 |
python-dateutil==2.9.0.post0
|
205 |
-
# via pandas
|
206 |
python-dotenv==1.1.0
|
207 |
-
# via pydantic-settings
|
208 |
python-multipart==0.0.20
|
209 |
-
|
210 |
-
# gradio
|
211 |
-
# mcp
|
212 |
pytz==2025.2
|
213 |
-
# via pandas
|
214 |
pyyaml==6.0.2
|
215 |
-
# via
|
216 |
-
# gradio
|
217 |
-
# huggingface-hub
|
218 |
-
# pre-commit
|
219 |
requests==2.32.3
|
220 |
-
# via
|
221 |
-
# cachecontrol
|
222 |
-
# huggingface-hub
|
223 |
-
# pip-audit
|
224 |
-
# tdagent
|
225 |
rich==14.0.0
|
226 |
-
# via
|
227 |
-
# pip-audit
|
228 |
-
# typer
|
229 |
ruff==0.11.12
|
230 |
-
# via gradio
|
231 |
safehttpx==0.1.6
|
232 |
-
# via gradio
|
233 |
semantic-version==2.10.0
|
234 |
-
# via gradio
|
235 |
shellingham==1.5.4 ; sys_platform != 'emscripten'
|
236 |
-
# via typer
|
237 |
six==1.17.0
|
238 |
-
# via python-dateutil
|
239 |
sniffio==1.3.1
|
240 |
-
# via anyio
|
241 |
sortedcontainers==2.4.0
|
242 |
-
# via cyclonedx-python-lib
|
243 |
sse-starlette==2.3.6
|
244 |
-
# via mcp
|
245 |
starlette==0.46.2
|
246 |
-
# via
|
247 |
-
# fastapi
|
248 |
-
# gradio
|
249 |
-
# mcp
|
250 |
toml==0.10.2
|
251 |
-
# via pip-audit
|
252 |
tomli==2.2.1 ; python_full_version <= '3.11'
|
253 |
-
# via
|
254 |
-
# coverage
|
255 |
-
# mypy
|
256 |
-
# pytest
|
257 |
tomlkit==0.13.2
|
258 |
-
# via gradio
|
259 |
tqdm==4.67.1
|
260 |
-
# via huggingface-hub
|
261 |
typer==0.16.0 ; sys_platform != 'emscripten'
|
262 |
-
# via gradio
|
263 |
typing-extensions==4.14.0
|
264 |
-
# via
|
265 |
-
# anyio
|
266 |
-
# exceptiongroup
|
267 |
-
# fastapi
|
268 |
-
# gradio
|
269 |
-
# gradio-client
|
270 |
-
# huggingface-hub
|
271 |
-
# multidict
|
272 |
-
# mypy
|
273 |
-
# pydantic
|
274 |
-
# pydantic-core
|
275 |
-
# rich
|
276 |
-
# typer
|
277 |
-
# typing-inspection
|
278 |
-
# uvicorn
|
279 |
typing-inspection==0.4.1
|
280 |
-
# via
|
281 |
-
# pydantic
|
282 |
-
# pydantic-settings
|
283 |
tzdata==2025.2
|
284 |
-
# via pandas
|
285 |
urllib3==2.4.0
|
286 |
-
# via
|
287 |
-
# gradio
|
288 |
-
# requests
|
289 |
uvicorn==0.34.3 ; sys_platform != 'emscripten'
|
290 |
-
# via
|
291 |
-
# gradio
|
292 |
-
# mcp
|
293 |
virtualenv==20.31.2
|
294 |
-
# via pre-commit
|
295 |
vt-py==0.21.0
|
296 |
-
# via tdagent
|
297 |
websockets==15.0.1
|
298 |
-
# via gradio-client
|
299 |
xdoctest==1.2.0
|
300 |
yarl==1.20.0
|
301 |
-
# via aiohttp
|
|
|
1 |
# This file was autogenerated by uv via the following command:
|
2 |
# uv export --format requirements-txt --no-hashes --group dev --group test -o requirements-dev.txt
|
3 |
aiofiles==24.1.0
|
|
|
|
|
|
|
4 |
aiohappyeyeballs==2.6.1
|
|
|
5 |
aiohttp==3.12.8
|
|
|
6 |
aiosignal==1.3.2
|
|
|
7 |
annotated-types==0.7.0
|
|
|
8 |
anyio==4.9.0
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
async-timeout==5.0.1 ; python_full_version < '3.11'
|
|
|
10 |
attrs==25.3.0
|
|
|
11 |
audioop-lts==0.2.1 ; python_full_version >= '3.13'
|
|
|
12 |
boolean-py==5.0
|
|
|
13 |
cachecontrol==0.14.3
|
14 |
+
cachetools==6.0.0
|
15 |
certifi==2025.4.26
|
|
|
|
|
|
|
|
|
16 |
cfgv==3.4.0
|
|
|
17 |
charset-normalizer==3.4.2
|
|
|
18 |
click==8.2.1 ; sys_platform != 'emscripten'
|
|
|
|
|
|
|
19 |
colorama==0.4.6 ; sys_platform == 'win32'
|
|
|
|
|
|
|
|
|
20 |
coverage==7.8.2
|
|
|
21 |
cyclonedx-python-lib==9.1.0
|
|
|
22 |
defusedxml==0.7.1
|
|
|
23 |
distlib==0.3.9
|
|
|
24 |
exceptiongroup==1.3.0 ; python_full_version < '3.11'
|
|
|
|
|
|
|
25 |
fastapi==0.115.12
|
|
|
26 |
ffmpy==0.6.0
|
|
|
27 |
filelock==3.18.0
|
|
|
|
|
|
|
|
|
28 |
frozenlist==1.6.2
|
|
|
|
|
|
|
29 |
fsspec==2025.5.1
|
|
|
|
|
|
|
30 |
gradio==5.32.1
|
|
|
31 |
gradio-client==1.10.2
|
|
|
32 |
groovy==0.1.2
|
|
|
33 |
h11==0.16.0
|
|
|
|
|
|
|
34 |
hf-xet==1.1.2 ; platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'
|
|
|
35 |
httpcore==1.0.9
|
|
|
36 |
httpx==0.28.1
|
|
|
|
|
|
|
|
|
|
|
37 |
httpx-sse==0.4.0
|
|
|
38 |
huggingface-hub==0.32.4
|
|
|
|
|
|
|
39 |
identify==2.6.12
|
|
|
40 |
idna==3.10
|
|
|
|
|
|
|
|
|
|
|
41 |
iniconfig==2.1.0
|
|
|
42 |
jinja2==3.1.6
|
|
|
43 |
license-expression==30.4.1
|
|
|
44 |
markdown-it-py==3.0.0
|
|
|
45 |
markupsafe==3.0.2
|
|
|
|
|
|
|
46 |
mcp==1.9.0
|
|
|
47 |
mdurl==0.1.2
|
|
|
48 |
msgpack==1.1.0
|
|
|
49 |
multidict==6.4.4
|
|
|
|
|
|
|
50 |
mypy==1.16.0
|
51 |
mypy-extensions==1.1.0
|
|
|
52 |
nodeenv==1.9.1
|
|
|
53 |
numpy==2.2.6
|
|
|
|
|
|
|
54 |
orjson==3.10.18
|
|
|
55 |
packageurl-python==0.16.0
|
|
|
56 |
packaging==25.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
pandas==2.2.3
|
|
|
58 |
pathspec==0.12.1
|
|
|
59 |
pillow==11.2.1
|
|
|
60 |
pip==25.1.1
|
|
|
61 |
pip-api==0.0.34
|
|
|
62 |
pip-audit==2.9.0
|
63 |
pip-requirements-parser==32.0.1
|
|
|
64 |
platformdirs==4.3.8
|
|
|
|
|
|
|
65 |
pluggy==1.6.0
|
|
|
66 |
pre-commit==3.8.0
|
67 |
propcache==0.3.1
|
|
|
|
|
|
|
68 |
py-serializable==2.0.0
|
|
|
69 |
pydantic==2.11.5
|
|
|
|
|
|
|
|
|
|
|
70 |
pydantic-core==2.33.2
|
|
|
71 |
pydantic-settings==2.9.1
|
|
|
72 |
pydub==0.25.1
|
|
|
73 |
pygments==2.19.1
|
|
|
74 |
pyparsing==3.2.3
|
|
|
75 |
pytest==7.4.4
|
|
|
|
|
|
|
76 |
pytest-cov==4.1.0
|
77 |
pytest-randomly==3.16.0
|
78 |
python-dateutil==2.9.0.post0
|
|
|
79 |
python-dotenv==1.1.0
|
|
|
80 |
python-multipart==0.0.20
|
81 |
+
python-whois==0.9.5
|
|
|
|
|
82 |
pytz==2025.2
|
|
|
83 |
pyyaml==6.0.2
|
|
|
|
|
|
|
|
|
84 |
requests==2.32.3
|
|
|
|
|
|
|
|
|
|
|
85 |
rich==14.0.0
|
|
|
|
|
|
|
86 |
ruff==0.11.12
|
|
|
87 |
safehttpx==0.1.6
|
|
|
88 |
semantic-version==2.10.0
|
|
|
89 |
shellingham==1.5.4 ; sys_platform != 'emscripten'
|
|
|
90 |
six==1.17.0
|
|
|
91 |
sniffio==1.3.1
|
|
|
92 |
sortedcontainers==2.4.0
|
|
|
93 |
sse-starlette==2.3.6
|
|
|
94 |
starlette==0.46.2
|
|
|
|
|
|
|
|
|
95 |
toml==0.10.2
|
|
|
96 |
tomli==2.2.1 ; python_full_version <= '3.11'
|
|
|
|
|
|
|
|
|
97 |
tomlkit==0.13.2
|
|
|
98 |
tqdm==4.67.1
|
|
|
99 |
typer==0.16.0 ; sys_platform != 'emscripten'
|
|
|
100 |
typing-extensions==4.14.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
typing-inspection==0.4.1
|
|
|
|
|
|
|
102 |
tzdata==2025.2
|
|
|
103 |
urllib3==2.4.0
|
|
|
|
|
|
|
104 |
uvicorn==0.34.3 ; sys_platform != 'emscripten'
|
|
|
|
|
|
|
105 |
virtualenv==20.31.2
|
|
|
106 |
vt-py==0.21.0
|
|
|
107 |
websockets==15.0.1
|
|
|
108 |
xdoctest==1.2.0
|
109 |
yarl==1.20.0
|
|
requirements.txt
CHANGED
@@ -1,211 +1,84 @@
|
|
1 |
# This file was autogenerated by uv via the following command:
|
2 |
-
# uv
|
3 |
aiofiles==24.1.0
|
4 |
-
# via
|
5 |
-
# gradio
|
6 |
-
# vt-py
|
7 |
aiohappyeyeballs==2.6.1
|
8 |
-
# via aiohttp
|
9 |
aiohttp==3.12.8
|
10 |
-
# via vt-py
|
11 |
aiosignal==1.3.2
|
12 |
-
# via aiohttp
|
13 |
annotated-types==0.7.0
|
14 |
-
# via pydantic
|
15 |
anyio==4.9.0
|
16 |
-
|
17 |
-
# gradio
|
18 |
-
# httpx
|
19 |
-
# mcp
|
20 |
-
# sse-starlette
|
21 |
-
# starlette
|
22 |
-
async-timeout==5.0.1
|
23 |
-
# via aiohttp
|
24 |
attrs==25.3.0
|
25 |
-
|
|
|
26 |
certifi==2025.4.26
|
27 |
-
# via
|
28 |
-
# httpcore
|
29 |
-
# httpx
|
30 |
-
# requests
|
31 |
charset-normalizer==3.4.2
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
# uvicorn
|
37 |
-
exceptiongroup==1.3.0
|
38 |
-
# via anyio
|
39 |
fastapi==0.115.12
|
40 |
-
# via gradio
|
41 |
ffmpy==0.6.0
|
42 |
-
# via gradio
|
43 |
filelock==3.18.0
|
44 |
-
# via huggingface-hub
|
45 |
frozenlist==1.6.2
|
46 |
-
# via
|
47 |
-
# aiohttp
|
48 |
-
# aiosignal
|
49 |
fsspec==2025.5.1
|
50 |
-
# via
|
51 |
-
# gradio-client
|
52 |
-
# huggingface-hub
|
53 |
gradio==5.32.1
|
54 |
-
# via tdagent (pyproject.toml)
|
55 |
gradio-client==1.10.2
|
56 |
-
# via gradio
|
57 |
groovy==0.1.2
|
58 |
-
# via gradio
|
59 |
h11==0.16.0
|
60 |
-
|
61 |
-
# httpcore
|
62 |
-
# uvicorn
|
63 |
-
hf-xet==1.1.2
|
64 |
-
# via huggingface-hub
|
65 |
httpcore==1.0.9
|
66 |
-
# via httpx
|
67 |
httpx==0.28.1
|
68 |
-
# via
|
69 |
-
# gradio
|
70 |
-
# gradio-client
|
71 |
-
# mcp
|
72 |
-
# safehttpx
|
73 |
httpx-sse==0.4.0
|
74 |
-
# via mcp
|
75 |
huggingface-hub==0.32.4
|
76 |
-
# via
|
77 |
-
# gradio
|
78 |
-
# gradio-client
|
79 |
idna==3.10
|
80 |
-
|
81 |
-
# anyio
|
82 |
-
# httpx
|
83 |
-
# requests
|
84 |
-
# yarl
|
85 |
jinja2==3.1.6
|
86 |
-
|
87 |
-
markdown-it-py==3.0.0
|
88 |
-
# via rich
|
89 |
markupsafe==3.0.2
|
90 |
-
# via
|
91 |
-
# gradio
|
92 |
-
# jinja2
|
93 |
mcp==1.9.0
|
94 |
-
|
95 |
-
mdurl==0.1.2
|
96 |
-
# via markdown-it-py
|
97 |
multidict==6.4.4
|
98 |
-
# via
|
99 |
-
# aiohttp
|
100 |
-
# yarl
|
101 |
numpy==2.2.6
|
102 |
-
# via
|
103 |
-
# gradio
|
104 |
-
# pandas
|
105 |
orjson==3.10.18
|
106 |
-
# via gradio
|
107 |
packaging==25.0
|
108 |
-
# via
|
109 |
-
# gradio
|
110 |
-
# gradio-client
|
111 |
-
# huggingface-hub
|
112 |
pandas==2.2.3
|
113 |
-
# via gradio
|
114 |
pillow==11.2.1
|
115 |
-
|
116 |
propcache==0.3.1
|
117 |
-
# via
|
118 |
-
# aiohttp
|
119 |
-
# yarl
|
120 |
pydantic==2.11.5
|
121 |
-
# via
|
122 |
-
# fastapi
|
123 |
-
# gradio
|
124 |
-
# mcp
|
125 |
-
# pydantic-settings
|
126 |
pydantic-core==2.33.2
|
127 |
-
# via pydantic
|
128 |
pydantic-settings==2.9.1
|
129 |
-
# via mcp
|
130 |
pydub==0.25.1
|
131 |
-
|
132 |
-
|
133 |
-
|
|
|
134 |
python-dateutil==2.9.0.post0
|
135 |
-
# via pandas
|
136 |
python-dotenv==1.1.0
|
137 |
-
# via pydantic-settings
|
138 |
python-multipart==0.0.20
|
139 |
-
|
140 |
-
# gradio
|
141 |
-
# mcp
|
142 |
pytz==2025.2
|
143 |
-
# via pandas
|
144 |
pyyaml==6.0.2
|
145 |
-
# via
|
146 |
-
# gradio
|
147 |
-
# huggingface-hub
|
148 |
requests==2.32.3
|
149 |
-
|
150 |
-
|
151 |
-
# huggingface-hub
|
152 |
-
rich==14.0.0
|
153 |
-
# via typer
|
154 |
-
ruff==0.11.12
|
155 |
-
# via gradio
|
156 |
safehttpx==0.1.6
|
157 |
-
# via gradio
|
158 |
semantic-version==2.10.0
|
159 |
-
|
160 |
-
shellingham==1.5.4
|
161 |
-
# via typer
|
162 |
six==1.17.0
|
163 |
-
# via python-dateutil
|
164 |
sniffio==1.3.1
|
165 |
-
# via anyio
|
166 |
sse-starlette==2.3.6
|
167 |
-
# via mcp
|
168 |
starlette==0.46.2
|
169 |
-
|
170 |
-
# fastapi
|
171 |
-
# gradio
|
172 |
-
# mcp
|
173 |
tomlkit==0.13.2
|
174 |
-
# via gradio
|
175 |
tqdm==4.67.1
|
176 |
-
|
177 |
-
typer==0.16.0
|
178 |
-
# via gradio
|
179 |
typing-extensions==4.14.0
|
180 |
-
# via
|
181 |
-
# anyio
|
182 |
-
# exceptiongroup
|
183 |
-
# fastapi
|
184 |
-
# gradio
|
185 |
-
# gradio-client
|
186 |
-
# huggingface-hub
|
187 |
-
# multidict
|
188 |
-
# pydantic
|
189 |
-
# pydantic-core
|
190 |
-
# rich
|
191 |
-
# typer
|
192 |
-
# typing-inspection
|
193 |
-
# uvicorn
|
194 |
typing-inspection==0.4.1
|
195 |
-
# via
|
196 |
-
# pydantic
|
197 |
-
# pydantic-settings
|
198 |
tzdata==2025.2
|
199 |
-
# via pandas
|
200 |
urllib3==2.4.0
|
201 |
-
|
202 |
-
uvicorn==0.34.3
|
203 |
-
# via
|
204 |
-
# gradio
|
205 |
-
# mcp
|
206 |
vt-py==0.21.0
|
207 |
-
# via tdagent (pyproject.toml)
|
208 |
websockets==15.0.1
|
209 |
-
|
210 |
yarl==1.20.0
|
211 |
-
# via aiohttp
|
|
|
1 |
# This file was autogenerated by uv via the following command:
|
2 |
+
# uv export --format requirements-txt --no-hashes --no-dev -o requirements.txt
|
3 |
aiofiles==24.1.0
|
|
|
|
|
|
|
4 |
aiohappyeyeballs==2.6.1
|
|
|
5 |
aiohttp==3.12.8
|
|
|
6 |
aiosignal==1.3.2
|
|
|
7 |
annotated-types==0.7.0
|
|
|
8 |
anyio==4.9.0
|
9 |
+
async-timeout==5.0.1 ; python_full_version < '3.11'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
attrs==25.3.0
|
11 |
+
audioop-lts==0.2.1 ; python_full_version >= '3.13'
|
12 |
+
cachetools==6.0.0
|
13 |
certifi==2025.4.26
|
|
|
|
|
|
|
|
|
14 |
charset-normalizer==3.4.2
|
15 |
+
click==8.2.1 ; sys_platform != 'emscripten'
|
16 |
+
colorama==0.4.6 ; sys_platform == 'win32'
|
17 |
+
coverage==7.8.2
|
18 |
+
exceptiongroup==1.3.0 ; python_full_version < '3.11'
|
|
|
|
|
|
|
19 |
fastapi==0.115.12
|
|
|
20 |
ffmpy==0.6.0
|
|
|
21 |
filelock==3.18.0
|
|
|
22 |
frozenlist==1.6.2
|
|
|
|
|
|
|
23 |
fsspec==2025.5.1
|
|
|
|
|
|
|
24 |
gradio==5.32.1
|
|
|
25 |
gradio-client==1.10.2
|
|
|
26 |
groovy==0.1.2
|
|
|
27 |
h11==0.16.0
|
28 |
+
hf-xet==1.1.2 ; platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'
|
|
|
|
|
|
|
|
|
29 |
httpcore==1.0.9
|
|
|
30 |
httpx==0.28.1
|
|
|
|
|
|
|
|
|
|
|
31 |
httpx-sse==0.4.0
|
|
|
32 |
huggingface-hub==0.32.4
|
|
|
|
|
|
|
33 |
idna==3.10
|
34 |
+
iniconfig==2.1.0
|
|
|
|
|
|
|
|
|
35 |
jinja2==3.1.6
|
36 |
+
markdown-it-py==3.0.0 ; sys_platform != 'emscripten'
|
|
|
|
|
37 |
markupsafe==3.0.2
|
|
|
|
|
|
|
38 |
mcp==1.9.0
|
39 |
+
mdurl==0.1.2 ; sys_platform != 'emscripten'
|
|
|
|
|
40 |
multidict==6.4.4
|
|
|
|
|
|
|
41 |
numpy==2.2.6
|
|
|
|
|
|
|
42 |
orjson==3.10.18
|
|
|
43 |
packaging==25.0
|
|
|
|
|
|
|
|
|
44 |
pandas==2.2.3
|
|
|
45 |
pillow==11.2.1
|
46 |
+
pluggy==1.6.0
|
47 |
propcache==0.3.1
|
|
|
|
|
|
|
48 |
pydantic==2.11.5
|
|
|
|
|
|
|
|
|
|
|
49 |
pydantic-core==2.33.2
|
|
|
50 |
pydantic-settings==2.9.1
|
|
|
51 |
pydub==0.25.1
|
52 |
+
pygments==2.19.1 ; sys_platform != 'emscripten'
|
53 |
+
pytest==7.4.4
|
54 |
+
pytest-cov==4.1.0
|
55 |
+
pytest-randomly==3.16.0
|
56 |
python-dateutil==2.9.0.post0
|
|
|
57 |
python-dotenv==1.1.0
|
|
|
58 |
python-multipart==0.0.20
|
59 |
+
python-whois==0.9.5
|
|
|
|
|
60 |
pytz==2025.2
|
|
|
61 |
pyyaml==6.0.2
|
|
|
|
|
|
|
62 |
requests==2.32.3
|
63 |
+
rich==14.0.0 ; sys_platform != 'emscripten'
|
64 |
+
ruff==0.11.12 ; sys_platform != 'emscripten'
|
|
|
|
|
|
|
|
|
|
|
65 |
safehttpx==0.1.6
|
|
|
66 |
semantic-version==2.10.0
|
67 |
+
shellingham==1.5.4 ; sys_platform != 'emscripten'
|
|
|
|
|
68 |
six==1.17.0
|
|
|
69 |
sniffio==1.3.1
|
|
|
70 |
sse-starlette==2.3.6
|
|
|
71 |
starlette==0.46.2
|
72 |
+
tomli==2.2.1 ; python_full_version <= '3.11'
|
|
|
|
|
|
|
73 |
tomlkit==0.13.2
|
|
|
74 |
tqdm==4.67.1
|
75 |
+
typer==0.16.0 ; sys_platform != 'emscripten'
|
|
|
|
|
76 |
typing-extensions==4.14.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
typing-inspection==0.4.1
|
|
|
|
|
|
|
78 |
tzdata==2025.2
|
|
|
79 |
urllib3==2.4.0
|
80 |
+
uvicorn==0.34.3 ; sys_platform != 'emscripten'
|
|
|
|
|
|
|
|
|
81 |
vt-py==0.21.0
|
|
|
82 |
websockets==15.0.1
|
83 |
+
xdoctest==1.2.0
|
84 |
yarl==1.20.0
|
|
tdagent/tools/get_url_content.py
CHANGED
@@ -37,11 +37,15 @@ def get_url_http_content(
|
|
37 |
|
38 |
if content_type:
|
39 |
headers["Accept"] = ",".join(content_type)
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
|
|
|
|
|
|
45 |
|
46 |
try:
|
47 |
response.raise_for_status()
|
|
|
37 |
|
38 |
if content_type:
|
39 |
headers["Accept"] = ",".join(content_type)
|
40 |
+
|
41 |
+
try:
|
42 |
+
response = requests.get(
|
43 |
+
url,
|
44 |
+
headers=headers,
|
45 |
+
timeout=timeout,
|
46 |
+
)
|
47 |
+
except requests.exceptions.MissingSchema as err:
|
48 |
+
return "", str(err)
|
49 |
|
50 |
try:
|
51 |
response.raise_for_status()
|
tdagent/tools/internal_company_user_search.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import gradio as gr
|
2 |
|
|
|
3 |
# Fake user database
|
4 |
users_db = {
|
5 |
"jsmith": {
|
@@ -9,7 +10,7 @@ users_db = {
|
|
9 |
"user_id": "US789456",
|
10 |
"jobtitle": "Software Engineer",
|
11 |
"department": "Engineering",
|
12 |
-
"country": "United States"
|
13 |
},
|
14 |
"mhacker": {
|
15 |
"username": "mhacker",
|
@@ -18,16 +19,19 @@ users_db = {
|
|
18 |
"user_id": "US123789",
|
19 |
"jobtitle": "Security Specialist",
|
20 |
"department": "Pentests",
|
21 |
-
"country": "Germany"
|
22 |
-
}
|
23 |
}
|
24 |
|
25 |
|
26 |
-
def lookup_user(username):
|
27 |
-
"""
|
28 |
-
|
29 |
Company User Lookup System. Enter a username to get user details.
|
30 |
-
|
|
|
|
|
|
|
31 |
"""
|
32 |
if username in users_db:
|
33 |
user = users_db[username]
|
@@ -39,8 +43,8 @@ User ID: {user['user_id']}
|
|
39 |
Job Title: {user['jobtitle']}
|
40 |
Department: {user['department']}
|
41 |
Country: {user['country']}"""
|
42 |
-
|
43 |
-
|
44 |
Username: Not found
|
45 |
Email: N/A
|
46 |
Name: N/A
|
@@ -56,6 +60,6 @@ gr_internal_company = gr.Interface(
|
|
56 |
inputs=["text"],
|
57 |
outputs=["text"],
|
58 |
title="Company User Lookup System",
|
59 |
-
description="Company User Lookup System.
|
60 |
-
theme="default"
|
61 |
)
|
|
|
1 |
import gradio as gr
|
2 |
|
3 |
+
|
4 |
# Fake user database
|
5 |
users_db = {
|
6 |
"jsmith": {
|
|
|
10 |
"user_id": "US789456",
|
11 |
"jobtitle": "Software Engineer",
|
12 |
"department": "Engineering",
|
13 |
+
"country": "United States",
|
14 |
},
|
15 |
"mhacker": {
|
16 |
"username": "mhacker",
|
|
|
19 |
"user_id": "US123789",
|
20 |
"jobtitle": "Security Specialist",
|
21 |
"department": "Pentests",
|
22 |
+
"country": "Germany",
|
23 |
+
},
|
24 |
}
|
25 |
|
26 |
|
27 |
+
def lookup_user(username: str) -> str:
|
28 |
+
"""Function to lookup user information.
|
29 |
+
|
30 |
Company User Lookup System. Enter a username to get user details.
|
31 |
+
|
32 |
+
Returns:
|
33 |
+
A formatted string with user details if found, otherwise an
|
34 |
+
error message.
|
35 |
"""
|
36 |
if username in users_db:
|
37 |
user = users_db[username]
|
|
|
43 |
Job Title: {user['jobtitle']}
|
44 |
Department: {user['department']}
|
45 |
Country: {user['country']}"""
|
46 |
+
|
47 |
+
return """User Not Found
|
48 |
Username: Not found
|
49 |
Email: N/A
|
50 |
Name: N/A
|
|
|
60 |
inputs=["text"],
|
61 |
outputs=["text"],
|
62 |
title="Company User Lookup System",
|
63 |
+
description="Company User Lookup System.",
|
64 |
+
theme="default",
|
65 |
)
|
tdagent/tools/lookup_company_cloud_account_information.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import gradio as gr
|
2 |
|
|
|
3 |
# Fake cloud accounts database
|
4 |
cloud_accounts = {
|
5 |
("AWS", "123456789012"): {
|
@@ -8,7 +9,7 @@ cloud_accounts = {
|
|
8 |
"cloud_account_name": "Production-Main",
|
9 |
"owner_user_id": "AWS001",
|
10 |
"owner_email": "cloud.admin@company.com",
|
11 |
-
"deployed_applications": ["ERP System", "Customer Portal", "Data Lake"]
|
12 |
},
|
13 |
("AWS", "098765432109"): {
|
14 |
"public_cloud_provider": "AWS",
|
@@ -16,7 +17,7 @@ cloud_accounts = {
|
|
16 |
"cloud_account_name": "Development-Team1",
|
17 |
"owner_user_id": "AWS002",
|
18 |
"owner_email": "dev.lead@company.com",
|
19 |
-
"deployed_applications": ["Test Environment", "CI/CD Pipeline"]
|
20 |
},
|
21 |
("Azure", "sub-abc-123-def-456"): {
|
22 |
"public_cloud_provider": "Azure",
|
@@ -24,7 +25,7 @@ cloud_accounts = {
|
|
24 |
"cloud_account_name": "Enterprise-Solutions",
|
25 |
"owner_user_id": "AZ001",
|
26 |
"owner_email": "azure.admin@company.com",
|
27 |
-
"deployed_applications": ["Microsoft 365 Integration", "Azure AD Connect"]
|
28 |
},
|
29 |
("Azure", "sub-xyz-789-uvw-321"): {
|
30 |
"public_cloud_provider": "Azure",
|
@@ -32,7 +33,7 @@ cloud_accounts = {
|
|
32 |
"cloud_account_name": "Research-Division",
|
33 |
"owner_user_id": "AZ002",
|
34 |
"owner_email": "research.lead@company.com",
|
35 |
-
"deployed_applications": ["ML Platform", "Research Portal"]
|
36 |
},
|
37 |
("GCP", "project-id-123456"): {
|
38 |
"public_cloud_provider": "GCP",
|
@@ -40,7 +41,7 @@ cloud_accounts = {
|
|
40 |
"cloud_account_name": "Analytics-Platform",
|
41 |
"owner_user_id": "GCP001",
|
42 |
"owner_email": "gcp.admin@company.com",
|
43 |
-
"deployed_applications": ["BigQuery Data Warehouse", "Kubernetes Cluster"]
|
44 |
},
|
45 |
("GCP", "project-id-789012"): {
|
46 |
"public_cloud_provider": "GCP",
|
@@ -48,45 +49,49 @@ cloud_accounts = {
|
|
48 |
"cloud_account_name": "ML-Operations",
|
49 |
"owner_user_id": "GCP002",
|
50 |
"owner_email": "ml.ops@company.com",
|
51 |
-
"deployed_applications": ["TensorFlow Training", "Model Serving Platform"]
|
52 |
-
}
|
53 |
}
|
54 |
|
55 |
|
56 |
-
def lookup_cloud_account(public_cloud_provider, account_id):
|
57 |
-
"""
|
58 |
-
|
59 |
-
Returns a formatted string with account details if
|
|
|
60 |
"""
|
61 |
account_key = (public_cloud_provider, account_id)
|
62 |
|
63 |
if account_key in cloud_accounts:
|
64 |
account = cloud_accounts[account_key]
|
65 |
-
return
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
}
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
}
|
80 |
|
81 |
|
82 |
# Create Gradio Interface
|
83 |
gr_lookup_company_cloud_account_information = gr.Interface(
|
84 |
fn=lookup_cloud_account,
|
85 |
-
inputs=[
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
|
|
90 |
outputs="text",
|
91 |
title="Company Cloud Account Lookup System",
|
92 |
description="""
|
@@ -97,5 +102,5 @@ gr_lookup_company_cloud_account_information = gr.Interface(
|
|
97 |
Azure: sub-abc-123-def-456, sub-xyz-789-uvw-321
|
98 |
GCP: project-id-123456, project-id-789012
|
99 |
""",
|
100 |
-
theme="default"
|
101 |
)
|
|
|
1 |
import gradio as gr
|
2 |
|
3 |
+
|
4 |
# Fake cloud accounts database
|
5 |
cloud_accounts = {
|
6 |
("AWS", "123456789012"): {
|
|
|
9 |
"cloud_account_name": "Production-Main",
|
10 |
"owner_user_id": "AWS001",
|
11 |
"owner_email": "cloud.admin@company.com",
|
12 |
+
"deployed_applications": ["ERP System", "Customer Portal", "Data Lake"],
|
13 |
},
|
14 |
("AWS", "098765432109"): {
|
15 |
"public_cloud_provider": "AWS",
|
|
|
17 |
"cloud_account_name": "Development-Team1",
|
18 |
"owner_user_id": "AWS002",
|
19 |
"owner_email": "dev.lead@company.com",
|
20 |
+
"deployed_applications": ["Test Environment", "CI/CD Pipeline"],
|
21 |
},
|
22 |
("Azure", "sub-abc-123-def-456"): {
|
23 |
"public_cloud_provider": "Azure",
|
|
|
25 |
"cloud_account_name": "Enterprise-Solutions",
|
26 |
"owner_user_id": "AZ001",
|
27 |
"owner_email": "azure.admin@company.com",
|
28 |
+
"deployed_applications": ["Microsoft 365 Integration", "Azure AD Connect"],
|
29 |
},
|
30 |
("Azure", "sub-xyz-789-uvw-321"): {
|
31 |
"public_cloud_provider": "Azure",
|
|
|
33 |
"cloud_account_name": "Research-Division",
|
34 |
"owner_user_id": "AZ002",
|
35 |
"owner_email": "research.lead@company.com",
|
36 |
+
"deployed_applications": ["ML Platform", "Research Portal"],
|
37 |
},
|
38 |
("GCP", "project-id-123456"): {
|
39 |
"public_cloud_provider": "GCP",
|
|
|
41 |
"cloud_account_name": "Analytics-Platform",
|
42 |
"owner_user_id": "GCP001",
|
43 |
"owner_email": "gcp.admin@company.com",
|
44 |
+
"deployed_applications": ["BigQuery Data Warehouse", "Kubernetes Cluster"],
|
45 |
},
|
46 |
("GCP", "project-id-789012"): {
|
47 |
"public_cloud_provider": "GCP",
|
|
|
49 |
"cloud_account_name": "ML-Operations",
|
50 |
"owner_user_id": "GCP002",
|
51 |
"owner_email": "ml.ops@company.com",
|
52 |
+
"deployed_applications": ["TensorFlow Training", "Model Serving Platform"],
|
53 |
+
},
|
54 |
}
|
55 |
|
56 |
|
57 |
+
def lookup_cloud_account(public_cloud_provider: str, account_id: str) -> dict[str, str]:
|
58 |
+
"""Function to lookup cloud account information.
|
59 |
+
|
60 |
+
Returns a formatted string with account details if
|
61 |
+
found, otherwise returns error message.
|
62 |
"""
|
63 |
account_key = (public_cloud_provider, account_id)
|
64 |
|
65 |
if account_key in cloud_accounts:
|
66 |
account = cloud_accounts[account_key]
|
67 |
+
return {
|
68 |
+
"public_cloud_provider": f"{account['public_cloud_provider']}",
|
69 |
+
"account_id": f"{account['account_id']}",
|
70 |
+
"cloud_account_name": f"{account['cloud_account_name']}",
|
71 |
+
"owner_user_id": f"{account['owner_user_id']}",
|
72 |
+
"owner_email": f"{account['owner_email']}",
|
73 |
+
"deployed_applications": f"{account['deployed_applications']!s}",
|
74 |
+
}
|
75 |
+
|
76 |
+
return {
|
77 |
+
"error": "Account not found",
|
78 |
+
"public_cloud_provider": f"{public_cloud_provider}",
|
79 |
+
"account_id": f"{account_id}",
|
80 |
+
"message": "No cloud account information found",
|
81 |
+
}
|
82 |
|
83 |
|
84 |
# Create Gradio Interface
|
85 |
gr_lookup_company_cloud_account_information = gr.Interface(
|
86 |
fn=lookup_cloud_account,
|
87 |
+
inputs=[
|
88 |
+
gr.Dropdown(
|
89 |
+
choices=["AWS", "Azure", "GCP"],
|
90 |
+
label="Cloud Provider",
|
91 |
+
info="Select the cloud provider",
|
92 |
+
),
|
93 |
+
"text",
|
94 |
+
],
|
95 |
outputs="text",
|
96 |
title="Company Cloud Account Lookup System",
|
97 |
description="""
|
|
|
102 |
Azure: sub-abc-123-def-456, sub-xyz-789-uvw-321
|
103 |
GCP: project-id-123456, project-id-789012
|
104 |
""",
|
105 |
+
theme="default",
|
106 |
)
|
tdagent/tools/query_abuse_ip_db.py
CHANGED
@@ -1,16 +1,19 @@
|
|
1 |
-
import
|
2 |
|
3 |
-
import
|
4 |
-
from datetime import datetime
|
5 |
from dataclasses import dataclass
|
6 |
-
from
|
|
|
|
|
7 |
import gradio as gr
|
|
|
8 |
|
9 |
|
10 |
# API docs: https://docs.abuseipdb.com/#check-endpoint
|
11 |
|
|
|
12 |
@dataclass
|
13 |
-
class AbuseReport:
|
14 |
date: str
|
15 |
categories: str
|
16 |
comment: str
|
@@ -18,7 +21,7 @@ class AbuseReport:
|
|
18 |
|
19 |
|
20 |
@dataclass
|
21 |
-
class IPCheckResult:
|
22 |
ip_address: str
|
23 |
abuse_confidence_score: int
|
24 |
total_reports: int
|
@@ -26,10 +29,10 @@ class IPCheckResult:
|
|
26 |
domain: str
|
27 |
isp: str
|
28 |
last_reported: str
|
29 |
-
reports:
|
30 |
|
31 |
def format_summary(self) -> str:
|
32 |
-
"""Format a summary of the IP check result"""
|
33 |
return f"""
|
34 |
IP Address: {self.ip_address}
|
35 |
Abuse Confidence Score: {self.abuse_confidence_score}%
|
@@ -41,9 +44,8 @@ class IPCheckResult:
|
|
41 |
"""
|
42 |
|
43 |
|
44 |
-
def check_ip(ip_address: str, api_key: str, days: str = "30") ->
|
45 |
-
"""
|
46 |
-
Query the AbuseIPDB API to check if an IP address has been reported.
|
47 |
|
48 |
Args:
|
49 |
ip_address: The IP address to check
|
@@ -53,30 +55,33 @@ def check_ip(ip_address: str, api_key: str, days: str = "30") -> Dict[str, Any]:
|
|
53 |
Returns:
|
54 |
API response data as a dictionary
|
55 |
"""
|
56 |
-
url =
|
57 |
|
58 |
-
headers = {
|
59 |
-
'Accept': 'application/json',
|
60 |
-
'Key': api_key
|
61 |
-
}
|
62 |
|
63 |
params = {
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
}
|
68 |
|
69 |
try:
|
70 |
-
response = requests.get(
|
|
|
|
|
|
|
|
|
|
|
71 |
response.raise_for_status() # Raise exception for HTTP errors
|
72 |
return response.json()
|
73 |
except requests.exceptions.RequestException as e:
|
74 |
return {"error": str(e)}
|
75 |
|
76 |
|
77 |
-
def parse_response(
|
78 |
-
|
79 |
-
|
|
|
80 |
|
81 |
Args:
|
82 |
response: The API response dictionary
|
@@ -93,18 +98,21 @@ def parse_response(response: Dict[str, Any]) -> Tuple[Optional[IPCheckResult], O
|
|
93 |
|
94 |
# Create a list of AbuseReport objects
|
95 |
reports = []
|
96 |
-
if
|
97 |
for report in data["reports"]:
|
98 |
-
reported_at = datetime.fromisoformat(
|
99 |
-
"
|
|
|
100 |
categories = ", ".join([str(cat) for cat in report.get("categories", [])])
|
101 |
|
102 |
-
reports.append(
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
|
|
|
|
108 |
|
109 |
# Create the main result object
|
110 |
result = IPCheckResult(
|
@@ -115,15 +123,14 @@ def parse_response(response: Dict[str, Any]) -> Tuple[Optional[IPCheckResult], O
|
|
115 |
domain=data.get("domain", "N/A"),
|
116 |
isp=data.get("isp", "N/A"),
|
117 |
last_reported=data.get("lastReportedAt", "Never"),
|
118 |
-
reports=reports
|
119 |
)
|
120 |
|
121 |
-
return result,
|
122 |
|
123 |
|
124 |
def query_abuseipdb(ip_address: str, days: int = 30) -> str:
|
125 |
-
"""
|
126 |
-
Main function to query AbuseIPDB and format the response for Gradio
|
127 |
|
128 |
Args:
|
129 |
ip_address: The IP address to check
|
@@ -141,10 +148,10 @@ def query_abuseipdb(ip_address: str, days: int = 30) -> str:
|
|
141 |
response = check_ip(ip_address, api_key, str(days))
|
142 |
result, error = parse_response(response)
|
143 |
|
144 |
-
if
|
145 |
-
return
|
146 |
|
147 |
-
return
|
148 |
|
149 |
|
150 |
gr_query_abuseipdb = gr.Interface(
|
@@ -152,5 +159,8 @@ gr_query_abuseipdb = gr.Interface(
|
|
152 |
inputs=["text"],
|
153 |
outputs="text",
|
154 |
title="AbuseIPDB IP Checker",
|
155 |
-
description=
|
|
|
|
|
|
|
156 |
)
|
|
|
1 |
+
from __future__ import annotations
|
2 |
|
3 |
+
import os
|
|
|
4 |
from dataclasses import dataclass
|
5 |
+
from datetime import datetime
|
6 |
+
from typing import Any
|
7 |
+
|
8 |
import gradio as gr
|
9 |
+
import requests
|
10 |
|
11 |
|
12 |
# API docs: https://docs.abuseipdb.com/#check-endpoint
|
13 |
|
14 |
+
|
15 |
@dataclass
|
16 |
+
class AbuseReport: # noqa: D101
|
17 |
date: str
|
18 |
categories: str
|
19 |
comment: str
|
|
|
21 |
|
22 |
|
23 |
@dataclass
|
24 |
+
class IPCheckResult: # noqa: D101
|
25 |
ip_address: str
|
26 |
abuse_confidence_score: int
|
27 |
total_reports: int
|
|
|
29 |
domain: str
|
30 |
isp: str
|
31 |
last_reported: str
|
32 |
+
reports: list[AbuseReport]
|
33 |
|
34 |
def format_summary(self) -> str:
|
35 |
+
"""Format a summary of the IP check result."""
|
36 |
return f"""
|
37 |
IP Address: {self.ip_address}
|
38 |
Abuse Confidence Score: {self.abuse_confidence_score}%
|
|
|
44 |
"""
|
45 |
|
46 |
|
47 |
+
def check_ip(ip_address: str, api_key: str, days: str = "30") -> dict[str, Any]:
|
48 |
+
"""Query the AbuseIPDB API to check if an IP address has been reported.
|
|
|
49 |
|
50 |
Args:
|
51 |
ip_address: The IP address to check
|
|
|
55 |
Returns:
|
56 |
API response data as a dictionary
|
57 |
"""
|
58 |
+
url = "https://api.abuseipdb.com/api/v2/check"
|
59 |
|
60 |
+
headers = {"Accept": "application/json", "Key": api_key}
|
|
|
|
|
|
|
61 |
|
62 |
params = {
|
63 |
+
"ipAddress": ip_address,
|
64 |
+
"maxAgeInDays": days,
|
65 |
+
"verbose": str(True),
|
66 |
}
|
67 |
|
68 |
try:
|
69 |
+
response = requests.get(
|
70 |
+
url,
|
71 |
+
headers=headers,
|
72 |
+
params=params,
|
73 |
+
timeout=30,
|
74 |
+
)
|
75 |
response.raise_for_status() # Raise exception for HTTP errors
|
76 |
return response.json()
|
77 |
except requests.exceptions.RequestException as e:
|
78 |
return {"error": str(e)}
|
79 |
|
80 |
|
81 |
+
def parse_response(
|
82 |
+
response: dict[str, Any],
|
83 |
+
) -> tuple[IPCheckResult | None, str]:
|
84 |
+
"""Parse the API response into a dataclass.
|
85 |
|
86 |
Args:
|
87 |
response: The API response dictionary
|
|
|
98 |
|
99 |
# Create a list of AbuseReport objects
|
100 |
reports = []
|
101 |
+
if data.get("reports"):
|
102 |
for report in data["reports"]:
|
103 |
+
reported_at = datetime.fromisoformat(
|
104 |
+
report["reportedAt"].replace("Z", "+00:00"),
|
105 |
+
).strftime("%Y-%m-%d %H:%M:%S")
|
106 |
categories = ", ".join([str(cat) for cat in report.get("categories", [])])
|
107 |
|
108 |
+
reports.append(
|
109 |
+
AbuseReport(
|
110 |
+
date=reported_at,
|
111 |
+
categories=categories,
|
112 |
+
comment=report.get("comment", ""),
|
113 |
+
reporter=str(report.get("reporterId", "Anonymous")),
|
114 |
+
),
|
115 |
+
)
|
116 |
|
117 |
# Create the main result object
|
118 |
result = IPCheckResult(
|
|
|
123 |
domain=data.get("domain", "N/A"),
|
124 |
isp=data.get("isp", "N/A"),
|
125 |
last_reported=data.get("lastReportedAt", "Never"),
|
126 |
+
reports=reports,
|
127 |
)
|
128 |
|
129 |
+
return result, ""
|
130 |
|
131 |
|
132 |
def query_abuseipdb(ip_address: str, days: int = 30) -> str:
|
133 |
+
"""Query AbuseIP to find if an IP has been reported for abusive behavior.
|
|
|
134 |
|
135 |
Args:
|
136 |
ip_address: The IP address to check
|
|
|
148 |
response = check_ip(ip_address, api_key, str(days))
|
149 |
result, error = parse_response(response)
|
150 |
|
151 |
+
if result:
|
152 |
+
return result.format_summary()
|
153 |
|
154 |
+
return error
|
155 |
|
156 |
|
157 |
gr_query_abuseipdb = gr.Interface(
|
|
|
159 |
inputs=["text"],
|
160 |
outputs="text",
|
161 |
title="AbuseIPDB IP Checker",
|
162 |
+
description=(
|
163 |
+
"Check if an IP address has been reported for abusive behavior"
|
164 |
+
" using AbuseIP DB API"
|
165 |
+
),
|
166 |
)
|
tdagent/tools/send_email.py
CHANGED
@@ -1,53 +1,48 @@
|
|
1 |
-
import gradio as gr
|
2 |
import datetime
|
3 |
import random
|
4 |
|
|
|
|
|
5 |
|
6 |
-
def send_email(recipient, subject, message):
|
7 |
-
"""
|
8 |
-
Simulates sending an email from cert@company.com and prints it to the console in a well-formatted way.
|
9 |
|
10 |
This function takes email details, formats them into a standard email structure
|
11 |
with headers and body, prints the formatted email to the console, and returns
|
12 |
a success message. The sender is always set to cert@company.com.
|
13 |
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
The
|
18 |
-
subject : str
|
19 |
-
The subject line of the email
|
20 |
-
message : str
|
21 |
-
The main body content of the email
|
22 |
|
23 |
Returns:
|
24 |
-
--------
|
25 |
-
str
|
26 |
A success message indicating that the email was sent, including
|
27 |
the recipient address and the current time
|
28 |
|
29 |
Example:
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
"""
|
43 |
# Fixed sender email
|
44 |
sender = "cert@company.com"
|
45 |
|
46 |
# Generate a random message ID
|
47 |
-
message_id = f"<{random.randint(100000, 999999)}.{random.randint(1000000000, 9999999999)}@mail-server>"
|
48 |
|
49 |
# Get current timestamp
|
50 |
-
timestamp = datetime.datetime.now().strftime(
|
|
|
|
|
51 |
|
52 |
# Format the email
|
53 |
email_format = f"""
|
@@ -63,10 +58,13 @@ Message-ID: {message_id}
|
|
63 |
"""
|
64 |
|
65 |
# Print the email to console
|
66 |
-
print(email_format)
|
67 |
|
68 |
# Return a success message
|
69 |
-
return
|
|
|
|
|
|
|
70 |
|
71 |
|
72 |
# Create Gradio Interface
|
@@ -75,5 +73,5 @@ gr_send_email = gr.Interface(
|
|
75 |
inputs=["text", "text", "text"],
|
76 |
outputs="text",
|
77 |
title="Email Sender Simulator",
|
78 |
-
description="This tool simulates sending an email
|
79 |
)
|
|
|
|
|
1 |
import datetime
|
2 |
import random
|
3 |
|
4 |
+
import gradio as gr
|
5 |
+
|
6 |
|
7 |
+
def send_email(recipient: str, subject: str, message: str) -> str:
|
8 |
+
"""Simulates sending an email from cert@company.com.
|
|
|
9 |
|
10 |
This function takes email details, formats them into a standard email structure
|
11 |
with headers and body, prints the formatted email to the console, and returns
|
12 |
a success message. The sender is always set to cert@company.com.
|
13 |
|
14 |
+
Args:
|
15 |
+
recipient: The email address of the recipient (To field)
|
16 |
+
subject: The subject line of the email
|
17 |
+
message: The main body content of the email
|
|
|
|
|
|
|
|
|
18 |
|
19 |
Returns:
|
|
|
|
|
20 |
A success message indicating that the email was sent, including
|
21 |
the recipient address and the current time
|
22 |
|
23 |
Example:
|
24 |
+
>>> send_email("jane@example.com", "Security Alert", "Please update your password.")
|
25 |
+
------ EMAIL SENT ------
|
26 |
+
From: cert@company.com
|
27 |
+
To: jane@example.com
|
28 |
+
Subject: Security Alert
|
29 |
+
Date: Wed, 04 Jun 2025 16:40:58 +0000
|
30 |
+
Message-ID: <123456.7890123456@mail-server>
|
31 |
+
|
32 |
+
Please update your password.
|
33 |
+
------------------------
|
34 |
+
'Email successfully sent to jane@example.com at 16:40:58'
|
35 |
+
""" # noqa: E501
|
|
|
36 |
# Fixed sender email
|
37 |
sender = "cert@company.com"
|
38 |
|
39 |
# Generate a random message ID
|
40 |
+
message_id = f"<{random.randint(100000, 999999)}.{random.randint(1000000000, 9999999999)}@mail-server>" # noqa: E501, S311
|
41 |
|
42 |
# Get current timestamp
|
43 |
+
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime(
|
44 |
+
"%a, %d %b %Y %H:%M:%S +0000",
|
45 |
+
)
|
46 |
|
47 |
# Format the email
|
48 |
email_format = f"""
|
|
|
58 |
"""
|
59 |
|
60 |
# Print the email to console
|
61 |
+
print(email_format) # noqa: T201
|
62 |
|
63 |
# Return a success message
|
64 |
+
return (
|
65 |
+
f"Email successfully sent from {sender} to {recipient} at "
|
66 |
+
+ datetime.datetime.now(datetime.timezone.utc).strftime("%H:%M:%S")
|
67 |
+
)
|
68 |
|
69 |
|
70 |
# Create Gradio Interface
|
|
|
73 |
inputs=["text", "text", "text"],
|
74 |
outputs="text",
|
75 |
title="Email Sender Simulator",
|
76 |
+
description="This tool simulates sending an email.",
|
77 |
)
|
tdagent/tools/virus_total.py
CHANGED
@@ -1,21 +1,21 @@
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import vt
|
3 |
-
|
4 |
-
from datetime import datetime
|
5 |
-
from functools import lru_cache
|
6 |
-
from typing import Optional
|
7 |
-
import time
|
8 |
-
import asyncio
|
9 |
|
10 |
# Get API key from environment variable
|
11 |
-
API_KEY = os.getenv(
|
|
|
12 |
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
-
@lru_cache(maxsize=100)
|
15 |
-
def get_url_info_cached(url: str, timestamp: Optional[int] = None) -> str:
|
16 |
-
"""
|
17 |
-
Get URL Info from VirusTotal URL Scanner. Scan URL is not available
|
18 |
-
"""
|
19 |
try:
|
20 |
# Create a new client for each request (thread-safe)
|
21 |
with vt.Client(API_KEY) as client:
|
@@ -33,12 +33,15 @@ def get_url_info_cached(url: str, timestamp: Optional[int] = None) -> str:
|
|
33 |
if last_analysis_date:
|
34 |
# Convert to datetime if it's a timestamp
|
35 |
if isinstance(last_analysis_date, (int, float)):
|
36 |
-
last_analysis_date = datetime.
|
37 |
-
|
|
|
|
|
|
|
38 |
else:
|
39 |
date_str = "Not available"
|
40 |
|
41 |
-
|
42 |
URL: {url}
|
43 |
Last Analysis Date: {date_str}
|
44 |
|
@@ -53,29 +56,14 @@ Reputation Score: {url_analysis.reputation}
|
|
53 |
Times Submitted: {url_analysis.times_submitted}
|
54 |
|
55 |
Cache Status: Hit
|
56 |
-
"""
|
57 |
-
|
58 |
-
return result
|
59 |
-
|
60 |
-
except Exception as e:
|
61 |
-
return f"Error: {str(e)}"
|
62 |
-
|
63 |
-
|
64 |
-
def get_url_info(url: str) -> str:
|
65 |
-
"""
|
66 |
-
Wrapper function to handle the cached URL info retrieval
|
67 |
-
"""
|
68 |
-
# Clean the URL to ensure consistent caching
|
69 |
-
url = url.strip().lower()
|
70 |
-
|
71 |
-
# Use current timestamp rounded to nearest hour to maintain cache for an hour
|
72 |
-
timestamp = int(time.time()) // 3600
|
73 |
|
74 |
-
|
|
|
75 |
|
76 |
|
77 |
-
|
78 |
-
fn=
|
79 |
inputs=["text"],
|
80 |
outputs="text",
|
81 |
title="VirusTotal URL Scanner",
|
|
|
1 |
+
import os
|
2 |
+
from datetime import datetime, timezone
|
3 |
+
|
4 |
+
import cachetools
|
5 |
import gradio as gr
|
6 |
import vt
|
7 |
+
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
# Get API key from environment variable
|
10 |
+
API_KEY = os.getenv("VT_API_KEY")
|
11 |
+
|
12 |
|
13 |
+
@cachetools.cached(cache=cachetools.TTLCache(maxsize=100, ttl=3600))
|
14 |
+
def get_virus_total_url_info(url: str) -> str:
|
15 |
+
"""Get URL Info from VirusTotal URL Scanner. Scan URL is not available."""
|
16 |
+
if not API_KEY:
|
17 |
+
return "Error: Virus total API key not configured."
|
18 |
|
|
|
|
|
|
|
|
|
|
|
19 |
try:
|
20 |
# Create a new client for each request (thread-safe)
|
21 |
with vt.Client(API_KEY) as client:
|
|
|
33 |
if last_analysis_date:
|
34 |
# Convert to datetime if it's a timestamp
|
35 |
if isinstance(last_analysis_date, (int, float)):
|
36 |
+
last_analysis_date = datetime.fromtimestamp(
|
37 |
+
last_analysis_date,
|
38 |
+
timezone.utc,
|
39 |
+
)
|
40 |
+
date_str = last_analysis_date.strftime("%Y-%m-%d %H:%M:%S UTC")
|
41 |
else:
|
42 |
date_str = "Not available"
|
43 |
|
44 |
+
return f"""
|
45 |
URL: {url}
|
46 |
Last Analysis Date: {date_str}
|
47 |
|
|
|
56 |
Times Submitted: {url_analysis.times_submitted}
|
57 |
|
58 |
Cache Status: Hit
|
59 |
+
""".strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
+
except Exception as err: # noqa: BLE001
|
62 |
+
return f"Error: {err}"
|
63 |
|
64 |
|
65 |
+
gr_virus_total_url_info = gr.Interface(
|
66 |
+
fn=get_virus_total_url_info,
|
67 |
inputs=["text"],
|
68 |
outputs="text",
|
69 |
title="VirusTotal URL Scanner",
|
tdagent/tools/whois.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
import gradio as gr
|
4 |
+
import whois
|
5 |
+
|
6 |
+
from tdagent.utils.json_utils import TDAgentJsonEncoder
|
7 |
+
|
8 |
+
|
9 |
+
def query_whois(url: str) -> str:
|
10 |
+
"""Query a WHOIS database to gather information about a url or domain.
|
11 |
+
|
12 |
+
WHOIS information includes: domain names, IP address blocks and autonomous
|
13 |
+
systems, but it is also used for a wider range of other information.
|
14 |
+
|
15 |
+
Args:
|
16 |
+
url: URL to query for WHOIS information.
|
17 |
+
|
18 |
+
Returns:
|
19 |
+
A JSON formatted string with the gathered information
|
20 |
+
"""
|
21 |
+
try:
|
22 |
+
whois_result = whois.whois(url)
|
23 |
+
except whois.parser.PywhoisError as err:
|
24 |
+
return json.dumps({"error": str(err)})
|
25 |
+
|
26 |
+
return json.dumps(whois_result, cls=TDAgentJsonEncoder)
|
27 |
+
|
28 |
+
|
29 |
+
gr_query_whois = gr.Interface(
|
30 |
+
fn=query_whois,
|
31 |
+
inputs=["text"],
|
32 |
+
outputs="text",
|
33 |
+
title="Get WHOIS information for a given URL.",
|
34 |
+
description="Query a WHOIS database to gather information about a url or domain.",
|
35 |
+
)
|
tdagent/utils/__init__.py
ADDED
File without changes
|
tdagent/utils/json_utils.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import datetime
|
2 |
+
import json
|
3 |
+
|
4 |
+
|
5 |
+
class TDAgentJsonEncoder(json.JSONEncoder):
|
6 |
+
"""Extend JSON encoder with known types."""
|
7 |
+
|
8 |
+
def default(self, o: object) -> object: # noqa: D102
|
9 |
+
if isinstance(o, datetime.datetime):
|
10 |
+
return {"__type__": "datetime", "value": o.isoformat()}
|
11 |
+
if isinstance(o, datetime.date):
|
12 |
+
return {"__type__": "date", "value": o.isoformat()}
|
13 |
+
|
14 |
+
return super().default(o)
|
uv.lock
CHANGED
The diff for this file is too large to render.
See raw diff
|
|