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.letter_counter import gr_letter_counter
6
- from tdagent.tools.lookup_company_cloud_account_information import gr_lookup_company_cloud_account_information
 
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 gr_virus_total
 
10
 
11
- gr_app = gr.TabbedInterface(
12
- [
13
- gr_get_url_http_content,
14
- gr_letter_counter,
15
- gr_query_abuseipdb,
16
- gr_virus_total,
17
- gr_internal_company,
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  gr_lookup_company_cloud_account_information,
19
- gr_send_email,
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 = ["gradio[mcp]>=5.32.1", "requests>=2.32.3", "vt-py~=0.21.0"]
 
 
 
 
 
 
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
- # via pip-audit
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
- # via
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 pip compile pyproject.toml -o requirements.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
23
- # via aiohttp
24
  attrs==25.3.0
25
- # via aiohttp
 
26
  certifi==2025.4.26
27
- # via
28
- # httpcore
29
- # httpx
30
- # requests
31
  charset-normalizer==3.4.2
32
- # via requests
33
- click==8.2.1
34
- # via
35
- # typer
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
- # via
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
- # via
81
- # anyio
82
- # httpx
83
- # requests
84
- # yarl
85
  jinja2==3.1.6
86
- # via gradio
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
- # via gradio
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
- # via gradio
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
- # via gradio
132
- pygments==2.19.1
133
- # via rich
 
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
- # via
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
- # via
150
- # tdagent (pyproject.toml)
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
- # via gradio
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
- # via
170
- # fastapi
171
- # gradio
172
- # mcp
173
  tomlkit==0.13.2
174
- # via gradio
175
  tqdm==4.67.1
176
- # via huggingface-hub
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
- # via requests
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
- # via gradio-client
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
- response = requests.get(
41
- url,
42
- headers=headers,
43
- timeout=timeout,
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
- Function to lookup user information.
29
  Company User Lookup System. Enter a username to get user details.
30
- Returns a formatted string with user details if found, otherwise returns error message
 
 
 
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
- else:
43
- return """User Not Found
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. Enter a username to get user details",
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
- Function to lookup cloud account information
59
- Returns a formatted string with account details if found, otherwise returns error message
 
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 f"""{{
66
- "public_cloud_provider": "{account['public_cloud_provider']}",
67
- "account_id": "{account['account_id']}",
68
- "cloud_account_name": "{account['cloud_account_name']}",
69
- "owner_user_id": "{account['owner_user_id']}",
70
- "owner_email": "{account['owner_email']}",
71
- "deployed_applications": {str(account['deployed_applications'])}
72
- }}"""
73
- else:
74
- return f"""{{
75
- "error": "Account not found",
76
- "public_cloud_provider": "{public_cloud_provider}",
77
- "account_id": "{account_id}",
78
- "message": "No cloud account information found"
79
- }}"""
80
 
81
 
82
  # Create Gradio Interface
83
  gr_lookup_company_cloud_account_information = gr.Interface(
84
  fn=lookup_cloud_account,
85
- inputs=[gr.Dropdown(
86
- choices=["AWS", "Azure", "GCP"],
87
- label="Cloud Provider",
88
- info="Select the cloud provider"
89
- ), "text"],
 
 
 
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 os
2
 
3
- import requests
4
- from datetime import datetime
5
  from dataclasses import dataclass
6
- from typing import List, Optional, Dict, Any, Tuple, Union
 
 
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: List[AbuseReport]
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") -> Dict[str, Any]:
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 = 'https://api.abuseipdb.com/api/v2/check'
57
 
58
- headers = {
59
- 'Accept': 'application/json',
60
- 'Key': api_key
61
- }
62
 
63
  params = {
64
- 'ipAddress': ip_address,
65
- 'maxAgeInDays': days,
66
- 'verbose': True
67
  }
68
 
69
  try:
70
- response = requests.get(url, headers=headers, params=params)
 
 
 
 
 
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(response: Dict[str, Any]) -> Tuple[Optional[IPCheckResult], Optional[str]]:
78
- """
79
- Parse the API response into a dataclass
 
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 "reports" in data and data["reports"]:
97
  for report in data["reports"]:
98
- reported_at = datetime.fromisoformat(report["reportedAt"].replace("Z", "+00:00")).strftime(
99
- "%Y-%m-%d %H:%M:%S")
 
100
  categories = ", ".join([str(cat) for cat in report.get("categories", [])])
101
 
102
- reports.append(AbuseReport(
103
- date=reported_at,
104
- categories=categories,
105
- comment=report.get("comment", ""),
106
- reporter=str(report.get("reporterId", "Anonymous"))
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, None
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 error:
145
- return error
146
 
147
- return result.format_summary()
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="Check if an IP address has been reported for abusive behavior using AbuseIP DB API",
 
 
 
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
- Parameters:
15
- -----------
16
- recipient : str
17
- The email address of the recipient (To field)
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
- >>> send_email("jane@example.com", "Security Alert", "Please update your password.")
32
- ------ EMAIL SENT ------
33
- From: cert@company.com
34
- To: jane@example.com
35
- Subject: Security Alert
36
- Date: Wed, 04 Jun 2025 16:40:58 +0000
37
- Message-ID: <123456.7890123456@mail-server>
38
-
39
- Please update your password.
40
- ------------------------
41
- 'Email successfully sent to jane@example.com at 16:40:58'
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("%a, %d %b %Y %H:%M:%S +0000")
 
 
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 f"Email successfully sent from {sender} to {recipient} at {datetime.datetime.now().strftime('%H:%M:%S')}"
 
 
 
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 by formatting and printing it to the console."
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
- import os
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('VT_API_KEY')
 
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.utcfromtimestamp(last_analysis_date)
37
- date_str = last_analysis_date.strftime('%Y-%m-%d %H:%M:%S UTC')
 
 
 
38
  else:
39
  date_str = "Not available"
40
 
41
- result = f"""
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
- return get_url_info_cached(url, timestamp)
 
75
 
76
 
77
- gr_virus_total = gr.Interface(
78
- fn=get_url_info,
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