hackagent 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. hackagent/__init__.py +12 -0
  2. hackagent/agent.py +214 -0
  3. hackagent/api/__init__.py +1 -0
  4. hackagent/api/agent/__init__.py +1 -0
  5. hackagent/api/agent/agent_create.py +347 -0
  6. hackagent/api/agent/agent_destroy.py +140 -0
  7. hackagent/api/agent/agent_list.py +242 -0
  8. hackagent/api/agent/agent_partial_update.py +361 -0
  9. hackagent/api/agent/agent_retrieve.py +235 -0
  10. hackagent/api/agent/agent_update.py +361 -0
  11. hackagent/api/apilogs/__init__.py +1 -0
  12. hackagent/api/apilogs/apilogs_list.py +170 -0
  13. hackagent/api/apilogs/apilogs_retrieve.py +162 -0
  14. hackagent/api/attack/__init__.py +1 -0
  15. hackagent/api/attack/attack_create.py +275 -0
  16. hackagent/api/attack/attack_destroy.py +146 -0
  17. hackagent/api/attack/attack_list.py +254 -0
  18. hackagent/api/attack/attack_partial_update.py +289 -0
  19. hackagent/api/attack/attack_retrieve.py +247 -0
  20. hackagent/api/attack/attack_update.py +289 -0
  21. hackagent/api/checkout/__init__.py +1 -0
  22. hackagent/api/checkout/checkout_create.py +225 -0
  23. hackagent/api/generate/__init__.py +1 -0
  24. hackagent/api/generate/generate_create.py +253 -0
  25. hackagent/api/judge/__init__.py +1 -0
  26. hackagent/api/judge/judge_create.py +253 -0
  27. hackagent/api/key/__init__.py +1 -0
  28. hackagent/api/key/key_create.py +179 -0
  29. hackagent/api/key/key_destroy.py +103 -0
  30. hackagent/api/key/key_list.py +170 -0
  31. hackagent/api/key/key_retrieve.py +162 -0
  32. hackagent/api/organization/__init__.py +1 -0
  33. hackagent/api/organization/organization_create.py +208 -0
  34. hackagent/api/organization/organization_destroy.py +104 -0
  35. hackagent/api/organization/organization_list.py +170 -0
  36. hackagent/api/organization/organization_me_retrieve.py +126 -0
  37. hackagent/api/organization/organization_partial_update.py +222 -0
  38. hackagent/api/organization/organization_retrieve.py +163 -0
  39. hackagent/api/organization/organization_update.py +222 -0
  40. hackagent/api/prompt/__init__.py +1 -0
  41. hackagent/api/prompt/prompt_create.py +171 -0
  42. hackagent/api/prompt/prompt_destroy.py +104 -0
  43. hackagent/api/prompt/prompt_list.py +185 -0
  44. hackagent/api/prompt/prompt_partial_update.py +185 -0
  45. hackagent/api/prompt/prompt_retrieve.py +163 -0
  46. hackagent/api/prompt/prompt_update.py +185 -0
  47. hackagent/api/result/__init__.py +1 -0
  48. hackagent/api/result/result_create.py +175 -0
  49. hackagent/api/result/result_destroy.py +106 -0
  50. hackagent/api/result/result_list.py +249 -0
  51. hackagent/api/result/result_partial_update.py +193 -0
  52. hackagent/api/result/result_retrieve.py +167 -0
  53. hackagent/api/result/result_trace_create.py +177 -0
  54. hackagent/api/result/result_update.py +189 -0
  55. hackagent/api/run/__init__.py +1 -0
  56. hackagent/api/run/run_create.py +187 -0
  57. hackagent/api/run/run_destroy.py +112 -0
  58. hackagent/api/run/run_list.py +291 -0
  59. hackagent/api/run/run_partial_update.py +201 -0
  60. hackagent/api/run/run_result_create.py +177 -0
  61. hackagent/api/run/run_retrieve.py +179 -0
  62. hackagent/api/run/run_run_tests_create.py +187 -0
  63. hackagent/api/run/run_update.py +201 -0
  64. hackagent/api/user/__init__.py +1 -0
  65. hackagent/api/user/user_create.py +212 -0
  66. hackagent/api/user/user_destroy.py +106 -0
  67. hackagent/api/user/user_list.py +174 -0
  68. hackagent/api/user/user_me_retrieve.py +126 -0
  69. hackagent/api/user/user_me_update.py +196 -0
  70. hackagent/api/user/user_partial_update.py +226 -0
  71. hackagent/api/user/user_retrieve.py +167 -0
  72. hackagent/api/user/user_update.py +226 -0
  73. hackagent/attacks/AdvPrefix/__init__.py +41 -0
  74. hackagent/attacks/AdvPrefix/completions.py +416 -0
  75. hackagent/attacks/AdvPrefix/config.py +259 -0
  76. hackagent/attacks/AdvPrefix/evaluation.py +745 -0
  77. hackagent/attacks/AdvPrefix/evaluators.py +564 -0
  78. hackagent/attacks/AdvPrefix/generate.py +711 -0
  79. hackagent/attacks/AdvPrefix/utils.py +307 -0
  80. hackagent/attacks/__init__.py +35 -0
  81. hackagent/attacks/advprefix.py +507 -0
  82. hackagent/attacks/base.py +106 -0
  83. hackagent/attacks/strategies.py +906 -0
  84. hackagent/cli/__init__.py +19 -0
  85. hackagent/cli/commands/__init__.py +20 -0
  86. hackagent/cli/commands/agent.py +100 -0
  87. hackagent/cli/commands/attack.py +417 -0
  88. hackagent/cli/commands/config.py +301 -0
  89. hackagent/cli/commands/results.py +327 -0
  90. hackagent/cli/config.py +249 -0
  91. hackagent/cli/main.py +515 -0
  92. hackagent/cli/tui/__init__.py +31 -0
  93. hackagent/cli/tui/actions_logger.py +200 -0
  94. hackagent/cli/tui/app.py +288 -0
  95. hackagent/cli/tui/base.py +137 -0
  96. hackagent/cli/tui/logger.py +318 -0
  97. hackagent/cli/tui/views/__init__.py +33 -0
  98. hackagent/cli/tui/views/agents.py +488 -0
  99. hackagent/cli/tui/views/attacks.py +624 -0
  100. hackagent/cli/tui/views/config.py +244 -0
  101. hackagent/cli/tui/views/dashboard.py +307 -0
  102. hackagent/cli/tui/views/results.py +1210 -0
  103. hackagent/cli/tui/widgets/__init__.py +24 -0
  104. hackagent/cli/tui/widgets/actions.py +346 -0
  105. hackagent/cli/tui/widgets/logs.py +435 -0
  106. hackagent/cli/utils.py +276 -0
  107. hackagent/client.py +286 -0
  108. hackagent/errors.py +37 -0
  109. hackagent/logger.py +83 -0
  110. hackagent/models/__init__.py +109 -0
  111. hackagent/models/agent.py +223 -0
  112. hackagent/models/agent_request.py +129 -0
  113. hackagent/models/api_token_log.py +184 -0
  114. hackagent/models/attack.py +154 -0
  115. hackagent/models/attack_request.py +82 -0
  116. hackagent/models/checkout_session_request_request.py +76 -0
  117. hackagent/models/checkout_session_response.py +59 -0
  118. hackagent/models/choice.py +81 -0
  119. hackagent/models/choice_message.py +67 -0
  120. hackagent/models/evaluation_status_enum.py +14 -0
  121. hackagent/models/generate_error_response.py +59 -0
  122. hackagent/models/generate_request_request.py +212 -0
  123. hackagent/models/generate_success_response.py +115 -0
  124. hackagent/models/generic_error_response.py +70 -0
  125. hackagent/models/message_request.py +67 -0
  126. hackagent/models/organization.py +102 -0
  127. hackagent/models/organization_minimal.py +68 -0
  128. hackagent/models/organization_request.py +71 -0
  129. hackagent/models/paginated_agent_list.py +123 -0
  130. hackagent/models/paginated_api_token_log_list.py +123 -0
  131. hackagent/models/paginated_attack_list.py +123 -0
  132. hackagent/models/paginated_organization_list.py +123 -0
  133. hackagent/models/paginated_prompt_list.py +123 -0
  134. hackagent/models/paginated_result_list.py +123 -0
  135. hackagent/models/paginated_run_list.py +123 -0
  136. hackagent/models/paginated_user_api_key_list.py +123 -0
  137. hackagent/models/paginated_user_profile_list.py +123 -0
  138. hackagent/models/patched_agent_request.py +128 -0
  139. hackagent/models/patched_attack_request.py +92 -0
  140. hackagent/models/patched_organization_request.py +71 -0
  141. hackagent/models/patched_prompt_request.py +125 -0
  142. hackagent/models/patched_result_request.py +237 -0
  143. hackagent/models/patched_run_request.py +138 -0
  144. hackagent/models/patched_user_profile_request.py +99 -0
  145. hackagent/models/prompt.py +220 -0
  146. hackagent/models/prompt_request.py +126 -0
  147. hackagent/models/result.py +294 -0
  148. hackagent/models/result_list_evaluation_status.py +14 -0
  149. hackagent/models/result_request.py +232 -0
  150. hackagent/models/run.py +233 -0
  151. hackagent/models/run_list_status.py +12 -0
  152. hackagent/models/run_request.py +133 -0
  153. hackagent/models/status_enum.py +12 -0
  154. hackagent/models/step_type_enum.py +14 -0
  155. hackagent/models/trace.py +121 -0
  156. hackagent/models/trace_request.py +94 -0
  157. hackagent/models/usage.py +75 -0
  158. hackagent/models/user_api_key.py +201 -0
  159. hackagent/models/user_api_key_request.py +73 -0
  160. hackagent/models/user_profile.py +135 -0
  161. hackagent/models/user_profile_minimal.py +76 -0
  162. hackagent/models/user_profile_request.py +99 -0
  163. hackagent/router/__init__.py +25 -0
  164. hackagent/router/adapters/__init__.py +20 -0
  165. hackagent/router/adapters/base.py +63 -0
  166. hackagent/router/adapters/google_adk.py +671 -0
  167. hackagent/router/adapters/litellm_adapter.py +524 -0
  168. hackagent/router/adapters/openai_adapter.py +426 -0
  169. hackagent/router/router.py +969 -0
  170. hackagent/router/types.py +54 -0
  171. hackagent/tracking/__init__.py +42 -0
  172. hackagent/tracking/context.py +163 -0
  173. hackagent/tracking/decorators.py +299 -0
  174. hackagent/tracking/tracker.py +441 -0
  175. hackagent/types.py +54 -0
  176. hackagent/utils.py +194 -0
  177. hackagent/vulnerabilities/__init__.py +13 -0
  178. hackagent/vulnerabilities/prompts.py +81 -0
  179. hackagent-0.3.1.dist-info/METADATA +122 -0
  180. hackagent-0.3.1.dist-info/RECORD +183 -0
  181. hackagent-0.3.1.dist-info/WHEEL +4 -0
  182. hackagent-0.3.1.dist-info/entry_points.txt +2 -0
  183. hackagent-0.3.1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,488 @@
1
+ # Copyright 2025 - AI4I. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ Agents Tab
17
+
18
+ Manage and view AI agents.
19
+ """
20
+
21
+ from datetime import datetime
22
+ from typing import Any
23
+
24
+ from textual.app import ComposeResult
25
+ from textual.binding import Binding
26
+ from textual.containers import Horizontal, VerticalScroll
27
+ from textual.widgets import Button, DataTable, Static
28
+
29
+ from hackagent.cli.config import CLIConfig
30
+ from hackagent.cli.tui.base import BaseTab
31
+
32
+
33
+ class AgentsTab(BaseTab):
34
+ """Agents tab for managing AI agents."""
35
+
36
+ DEFAULT_CSS = """
37
+ AgentsTab {
38
+ layout: vertical;
39
+ }
40
+
41
+ AgentsTab .section-header {
42
+ background: $panel;
43
+ color: $accent;
44
+ text-style: bold;
45
+ padding: 0 1;
46
+ height: 1;
47
+ border-bottom: solid $primary;
48
+ }
49
+
50
+ AgentsTab .toolbar {
51
+ height: 3;
52
+ padding: 1;
53
+ background: $panel;
54
+ border-bottom: solid $primary;
55
+ }
56
+
57
+ AgentsTab .stats-bar {
58
+ height: 3;
59
+ background: $panel;
60
+ padding: 0 2;
61
+ border-bottom: solid $primary;
62
+ }
63
+
64
+ AgentsTab #agents-table {
65
+ height: 2fr;
66
+ min-height: 10;
67
+ border: solid $primary;
68
+ }
69
+
70
+ AgentsTab #agent-details-container {
71
+ height: 1fr;
72
+ min-height: 10;
73
+ max-height: 25;
74
+ background: $panel;
75
+ border: solid $accent;
76
+ margin: 1;
77
+ }
78
+
79
+ AgentsTab .agent-details {
80
+ padding: 1 2;
81
+ }
82
+
83
+ AgentsTab .empty-state {
84
+ height: 100%;
85
+ content-align: center middle;
86
+ background: $panel;
87
+ }
88
+ """
89
+
90
+ BINDINGS = [
91
+ Binding("n", "new_agent", "New Agent"),
92
+ Binding("d", "delete_agent", "Delete Agent"),
93
+ Binding("enter", "view_agent", "View Details"),
94
+ Binding("f5", "refresh", "Refresh"),
95
+ ]
96
+
97
+ def __init__(self, cli_config: CLIConfig):
98
+ """Initialize agents tab.
99
+
100
+ Args:
101
+ cli_config: CLI configuration object
102
+ """
103
+ super().__init__(cli_config)
104
+ self.agents_data: list[Any] = []
105
+ self.selected_agent: Any = None
106
+
107
+ def compose(self) -> ComposeResult:
108
+ """Compose the agents layout."""
109
+ # Title section
110
+ yield Static(
111
+ "šŸ¤– [bold cyan]Agent Management[/bold cyan]", classes="section-header"
112
+ )
113
+
114
+ # Statistics bar
115
+ yield Static(
116
+ "šŸ“Š [cyan]Total Agents:[/cyan] [yellow]0[/yellow] | "
117
+ "🟢 [green]Active:[/green] [yellow]0[/yellow] | "
118
+ "⚔ [magenta]Last Updated:[/magenta] [dim]Never[/dim]",
119
+ id="agents-stats",
120
+ classes="stats-bar",
121
+ )
122
+
123
+ # Toolbar with action buttons
124
+ with Horizontal(classes="toolbar"):
125
+ yield Button("šŸ”„ Refresh", id="refresh-agents", variant="primary")
126
+ yield Button("āž• New Agent", id="new-agent", variant="success")
127
+ yield Button("šŸ—‘ļø Delete", id="delete-agent", variant="error")
128
+
129
+ # Agents table
130
+ table: DataTable = DataTable(
131
+ id="agents-table", zebra_stripes=True, cursor_type="row"
132
+ )
133
+ table.add_columns("ID", "Name", "Type", "Endpoint", "Status", "Created")
134
+ yield table
135
+
136
+ # Details panel
137
+ with VerticalScroll(classes="agent-details", id="agent-details-container"):
138
+ yield Static(
139
+ "[dim italic]šŸ’” Select an agent from the table above to view detailed information[/dim]",
140
+ id="agent-details",
141
+ )
142
+
143
+ def on_mount(self) -> None:
144
+ """Called when the tab is mounted."""
145
+ # Show loading message immediately
146
+ try:
147
+ details_widget = self.query_one("#agent-details", Static)
148
+ details_widget.update("ā³ [cyan]Loading agents from API...[/cyan]")
149
+
150
+ stats_widget = self.query_one("#agents-stats", Static)
151
+ stats_widget.update(
152
+ "šŸ“Š [cyan]Total Agents:[/cyan] [yellow]...[/yellow] | "
153
+ "🟢 [green]Active:[/green] [yellow]...[/yellow] | "
154
+ "⚔ [magenta]Status:[/magenta] [cyan]Loading...[/cyan]"
155
+ )
156
+ except Exception:
157
+ pass
158
+
159
+ # Call base class mount which handles initial refresh
160
+ super().on_mount()
161
+
162
+ # Enable auto-refresh every 10 seconds
163
+ self.enable_auto_refresh(interval=10.0)
164
+
165
+ def on_button_pressed(self, event: Button.Pressed) -> None:
166
+ """Handle button press events."""
167
+ if event.button.id == "refresh-agents":
168
+ self.action_refresh()
169
+ elif event.button.id == "new-agent":
170
+ self._show_info_message("āž• Create new agent feature coming soon!")
171
+ elif event.button.id == "delete-agent":
172
+ if self.selected_agent:
173
+ self._show_info_message(
174
+ f"šŸ—‘ļø Delete agent '{self.selected_agent.name}' - feature coming soon!"
175
+ )
176
+ else:
177
+ self._show_info_message("āš ļø Please select an agent to delete")
178
+
179
+ def action_refresh(self) -> None:
180
+ """Action to manually refresh agents data."""
181
+ try:
182
+ stats_widget = self.query_one("#agents-stats", Static)
183
+ stats_widget.update(
184
+ "šŸ“Š [cyan]Total Agents:[/cyan] [yellow]...[/yellow] | "
185
+ "🟢 [green]Active:[/green] [yellow]...[/yellow] | "
186
+ "⚔ [magenta]Status:[/magenta] [cyan]Refreshing...[/cyan]"
187
+ )
188
+ except Exception:
189
+ pass
190
+ self.refresh_data()
191
+
192
+ def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
193
+ """Handle row selection in the agents table."""
194
+ table = self.query_one(DataTable)
195
+ row_key = event.row_key
196
+ row_index = table.get_row_index(row_key)
197
+
198
+ if row_index < len(self.agents_data):
199
+ self.selected_agent = self.agents_data[row_index]
200
+ self._show_agent_details()
201
+
202
+ def refresh_data(self) -> None:
203
+ """Refresh agents data from API."""
204
+ try:
205
+ from hackagent.api.agent import agent_list
206
+
207
+ # Validate configuration
208
+ if not self.cli_config.api_key:
209
+ self._show_empty_state(
210
+ "šŸ”‘ [bold yellow]API Key Not Configured[/bold yellow]\n\n"
211
+ "[cyan]To get started:[/cyan]\n"
212
+ "1. Run: [bold]hackagent config set --api-key YOUR_KEY[/bold]\n"
213
+ "2. Press [bold]F5[/bold] to refresh\n\n"
214
+ "[dim]Need an API key? Visit the HackAgent dashboard[/dim]"
215
+ )
216
+ return
217
+
218
+ # Create API client with timeout
219
+ client = self.create_api_client()
220
+
221
+ # Fetch agents
222
+ response = agent_list.sync_detailed(client=client)
223
+
224
+ if response.status_code == 200 and response.parsed:
225
+ self.agents_data = (
226
+ response.parsed.results if response.parsed.results else []
227
+ )
228
+
229
+ # Always update the table, even if empty
230
+ if not self.agents_data:
231
+ # Clear table and show empty message
232
+ table = self.query_one("#agents-table", DataTable)
233
+ table.clear()
234
+
235
+ # Update stats
236
+ stats_widget = self.query_one("#agents-stats", Static)
237
+ stats_widget.update(
238
+ "šŸ“Š [cyan]Total Agents:[/cyan] [yellow]0[/yellow] | "
239
+ "🟢 [green]Active:[/green] [yellow]0[/yellow] | "
240
+ "⚔ [magenta]Status:[/magenta] [green]Loaded[/green]"
241
+ )
242
+
243
+ details_widget = self.query_one("#agent-details", Static)
244
+ details_widget.update(
245
+ "šŸ“­ [bold cyan]No Agents Found[/bold cyan]\n\n"
246
+ "[yellow]Get started by creating your first agent:[/yellow]\n\n"
247
+ "• Click [bold]āž• New Agent[/bold] button above\n"
248
+ "• Or use the CLI: [bold]hackagent agent create[/bold]\n\n"
249
+ "[dim]Agents are AI systems that you can test for security vulnerabilities[/dim]"
250
+ )
251
+ else:
252
+ self._update_table()
253
+ elif response.status_code == 401:
254
+ error_msg = self.handle_api_error(Exception("401"), "Authentication")
255
+ self._show_empty_state(error_msg)
256
+ elif response.status_code == 403:
257
+ self._show_empty_state(
258
+ "🚫 [bold red]Access Forbidden[/bold red]\n\n"
259
+ "[yellow]Your API key doesn't have permission to view agents[/yellow]\n\n"
260
+ "Contact your administrator or check your API key permissions"
261
+ )
262
+ else:
263
+ error_status = f"API error: {response.status_code}"
264
+ self._show_empty_state(
265
+ f"āš ļø [bold red]API Error[/bold red]\n\n{error_status}\n\n[dim]Press F5 to retry[/dim]"
266
+ )
267
+
268
+ except Exception as e:
269
+ error_msg = self.handle_api_error(e, "Loading agents")
270
+ self._show_empty_state(error_msg)
271
+
272
+ def _show_empty_state(self, message: str) -> None:
273
+ """Show an empty state message when no data is available.
274
+
275
+ Args:
276
+ message: Message to display
277
+ """
278
+ table = self.query_one("#agents-table", DataTable)
279
+ table.clear()
280
+
281
+ # Update stats bar
282
+ try:
283
+ stats_widget = self.query_one("#agents-stats", Static)
284
+ stats_widget.update(
285
+ "šŸ“Š [cyan]Total Agents:[/cyan] [red]0[/red] | "
286
+ "🟢 [green]Active:[/green] [red]0[/red] | "
287
+ "⚔ [magenta]Status:[/magenta] [red]Error[/red]"
288
+ )
289
+ except Exception:
290
+ pass
291
+
292
+ # Show message in details area
293
+ details_widget = self.query_one("#agent-details", Static)
294
+ details_widget.update(message)
295
+
296
+ def _show_info_message(self, message: str) -> None:
297
+ """Show an informational message in the details panel.
298
+
299
+ Args:
300
+ message: Message to display
301
+ """
302
+ details_widget = self.query_one("#agent-details", Static)
303
+ details_widget.update(
304
+ f"\n{message}\n\n[dim]This message will be replaced when you select an agent[/dim]"
305
+ )
306
+
307
+ def _update_table(self) -> None:
308
+ """Update the agents table with current data."""
309
+ details_widget = self.query_one("#agent-details", Static)
310
+ try:
311
+ table = self.query_one("#agents-table", DataTable)
312
+ table.clear()
313
+
314
+ rows_added = 0
315
+ active_count = 0
316
+
317
+ for agent in self.agents_data:
318
+ # Format creation date
319
+ created = "Unknown"
320
+ if hasattr(agent, "created_at") and agent.created_at:
321
+ try:
322
+ if isinstance(agent.created_at, datetime):
323
+ created = agent.created_at.strftime("%Y-%m-%d %H:%M")
324
+ else:
325
+ created = str(agent.created_at)[:16]
326
+ except (AttributeError, ValueError, TypeError):
327
+ created = str(agent.created_at)[:16]
328
+
329
+ # Get agent type
330
+ agent_type = "Unknown"
331
+ try:
332
+ agent_type = (
333
+ agent.agent_type.value
334
+ if hasattr(agent.agent_type, "value")
335
+ else str(agent.agent_type)
336
+ )
337
+ except Exception:
338
+ agent_type = "Unknown"
339
+
340
+ # Get endpoint
341
+ endpoint = "N/A"
342
+ try:
343
+ if agent.endpoint:
344
+ endpoint = (
345
+ (agent.endpoint[:35] + "...")
346
+ if len(agent.endpoint) > 35
347
+ else agent.endpoint
348
+ )
349
+ except Exception:
350
+ endpoint = "N/A"
351
+
352
+ # Determine status
353
+ status = "🟢 Active"
354
+ if hasattr(agent, "endpoint") and agent.endpoint:
355
+ active_count += 1
356
+ else:
357
+ status = "⚪ Inactive"
358
+
359
+ table.add_row(
360
+ str(agent.id)[:8] + "...",
361
+ agent.name or "Unnamed",
362
+ agent_type,
363
+ endpoint,
364
+ status,
365
+ created,
366
+ )
367
+ rows_added += 1
368
+
369
+ # Update statistics bar
370
+ from datetime import datetime as dt
371
+
372
+ current_time = dt.now().strftime("%H:%M:%S")
373
+
374
+ stats_widget = self.query_one("#agents-stats", Static)
375
+ stats_widget.update(
376
+ f"šŸ“Š [cyan]Total Agents:[/cyan] [green]{rows_added}[/green] | "
377
+ f"🟢 [green]Active:[/green] [green]{active_count}[/green] | "
378
+ f"⚔ [magenta]Last Updated:[/magenta] [yellow]{current_time}[/yellow]"
379
+ )
380
+
381
+ # Show success message
382
+ inactive_count = rows_added - active_count
383
+ details_widget.update(
384
+ f"āœ… [bold green]Successfully loaded {rows_added} agent(s)[/bold green]\n\n"
385
+ f"[cyan]Agent Summary:[/cyan]\n"
386
+ f"• Total: [yellow]{rows_added}[/yellow]\n"
387
+ f"• Active (with endpoint): [green]{active_count}[/green]\n"
388
+ f"• Inactive: [yellow]{inactive_count}[/yellow]\n\n"
389
+ f"[dim italic]šŸ’” Click on any agent in the table to view detailed information[/dim]"
390
+ )
391
+
392
+ except Exception as e:
393
+ # If table update fails, show detailed error
394
+ import traceback
395
+ from rich.markup import escape
396
+
397
+ error_details = traceback.format_exc()
398
+ error_msg = escape(str(e))
399
+ escaped_details = escape(error_details[:400])
400
+
401
+ details_widget.update(
402
+ f"āŒ [bold red]Error updating table[/bold red]\n\n"
403
+ f"[yellow]{type(e).__name__}:[/yellow] {error_msg}\n\n"
404
+ f"[dim]Debug info:\n{escaped_details}[/dim]"
405
+ )
406
+
407
+ def _show_agent_details(self) -> None:
408
+ """Show details of the selected agent."""
409
+ if not self.selected_agent:
410
+ return
411
+
412
+ agent = self.selected_agent
413
+ details_widget = self.query_one("#agent-details", Static)
414
+
415
+ # Format creation date
416
+ created = "Unknown"
417
+ if hasattr(agent, "created_at") and agent.created_at:
418
+ try:
419
+ if isinstance(agent.created_at, datetime):
420
+ created = agent.created_at.strftime("%Y-%m-%d %H:%M:%S")
421
+ else:
422
+ created = str(agent.created_at)
423
+ except (AttributeError, ValueError, TypeError):
424
+ created = str(agent.created_at)
425
+
426
+ # Get agent type
427
+ agent_type = "Unknown"
428
+ try:
429
+ agent_type = (
430
+ agent.agent_type.value
431
+ if hasattr(agent.agent_type, "value")
432
+ else str(agent.agent_type)
433
+ )
434
+ except Exception:
435
+ agent_type = "Unknown"
436
+
437
+ # Determine status emoji
438
+ status_icon = "🟢" if (hasattr(agent, "endpoint") and agent.endpoint) else "⚪"
439
+ status_text = "Active" if status_icon == "🟢" else "Inactive"
440
+
441
+ # Build details view with better formatting
442
+ details = f"""╭─ [bold cyan]šŸ¤– Agent Details[/bold cyan] ─────────────────────────────────────╮
443
+
444
+ {status_icon} [bold yellow]Status:[/bold yellow] {status_text}
445
+
446
+ [bold cyan]━━━ Basic Information ━━━[/bold cyan]
447
+
448
+ [bold]šŸ†” ID:[/bold]
449
+ {agent.id}
450
+
451
+ [bold]šŸ“› Name:[/bold]
452
+ {agent.name or "[dim]Unnamed[/dim]"}
453
+
454
+ [bold]šŸ·ļø Type:[/bold]
455
+ {agent_type}
456
+
457
+ [bold]šŸ“… Created:[/bold]
458
+ {created}
459
+
460
+ [bold cyan]━━━ Configuration ━━━[/bold cyan]
461
+
462
+ [bold]🌐 Endpoint:[/bold]
463
+ {agent.endpoint or "[dim]Not specified[/dim]"}
464
+
465
+ [bold]šŸ“ Description:[/bold]
466
+ {agent.description or "[dim]No description provided[/dim]"}
467
+ """
468
+
469
+ if hasattr(agent, "organization") and agent.organization:
470
+ details += f"\n [bold]šŸ¢ Organization:[/bold]\n {agent.organization}\n"
471
+
472
+ # Add metadata section if available
473
+ if hasattr(agent, "metadata") and agent.metadata:
474
+ details += "\n[bold cyan]━━━ Metadata ━━━[/bold cyan]\n\n"
475
+ try:
476
+ import json
477
+
478
+ metadata_str = json.dumps(agent.metadata, indent=2)
479
+ details += f" {metadata_str}\n"
480
+ except Exception:
481
+ details += f" {str(agent.metadata)}\n"
482
+
483
+ details += "\n╰────────────────────────────────────────────────────────────╯\n"
484
+ details += (
485
+ "\n[dim italic]šŸ’” Press 'd' to delete this agent or 'F5' to refresh[/dim]"
486
+ )
487
+
488
+ details_widget.update(details)