alloy-runtime-cli 0.1.0__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 (451) hide show
  1. alloy_runtime_cli-0.1.0.dist-info/METADATA +61 -0
  2. alloy_runtime_cli-0.1.0.dist-info/RECORD +451 -0
  3. alloy_runtime_cli-0.1.0.dist-info/WHEEL +5 -0
  4. alloy_runtime_cli-0.1.0.dist-info/entry_points.txt +2 -0
  5. alloy_runtime_cli-0.1.0.dist-info/top_level.txt +1 -0
  6. cli/__init__.py +0 -0
  7. cli/commands/__init__.py +0 -0
  8. cli/commands/admin/__init__.py +0 -0
  9. cli/commands/admin/bootstrap_command.py +118 -0
  10. cli/commands/admin/credentials/__init__.py +0 -0
  11. cli/commands/admin/credentials/create/__init__.py +0 -0
  12. cli/commands/admin/credentials/create/command.py +148 -0
  13. cli/commands/admin/credentials/create/presenter.py +16 -0
  14. cli/commands/admin/credentials/grant/__init__.py +0 -0
  15. cli/commands/admin/credentials/grant/command.py +119 -0
  16. cli/commands/admin/credentials/grant/fields.py +33 -0
  17. cli/commands/admin/credentials/grant/presenter.py +23 -0
  18. cli/commands/agents/__init__.py +0 -0
  19. cli/commands/agents/create/__init__.py +0 -0
  20. cli/commands/agents/create/command.py +475 -0
  21. cli/commands/agents/create/fields.py +64 -0
  22. cli/commands/agents/create/presenter.py +68 -0
  23. cli/commands/agents/delete/__init__.py +0 -0
  24. cli/commands/agents/delete/command.py +47 -0
  25. cli/commands/agents/delete/presenter.py +16 -0
  26. cli/commands/agents/get/command.py +37 -0
  27. cli/commands/agents/get/presenter.py +32 -0
  28. cli/commands/agents/list/__init__.py +1 -0
  29. cli/commands/agents/list/command.py +54 -0
  30. cli/commands/agents/list/presenter.py +82 -0
  31. cli/commands/agents/update/__init__.py +0 -0
  32. cli/commands/agents/update/command.py +435 -0
  33. cli/commands/agents/update/fields.py +40 -0
  34. cli/commands/agents/update/presenter.py +68 -0
  35. cli/commands/audio/__init__.py +0 -0
  36. cli/commands/audio/transcribe/__init__.py +0 -0
  37. cli/commands/audio/transcribe/command.py +144 -0
  38. cli/commands/audio/transcribe/presenter.py +15 -0
  39. cli/commands/auth/__init__.py +0 -0
  40. cli/commands/auth/login/__init__.py +0 -0
  41. cli/commands/auth/login/command.py +80 -0
  42. cli/commands/auth/signup/__init__.py +0 -0
  43. cli/commands/auth/signup/command.py +115 -0
  44. cli/commands/billing/__init__.py +1 -0
  45. cli/commands/billing/costs/__init__.py +1 -0
  46. cli/commands/billing/costs/by_agent/__init__.py +1 -0
  47. cli/commands/billing/costs/by_agent/command.py +57 -0
  48. cli/commands/billing/costs/by_agent/presenter.py +81 -0
  49. cli/commands/billing/costs/by_model/__init__.py +1 -0
  50. cli/commands/billing/costs/by_model/command.py +57 -0
  51. cli/commands/billing/costs/by_model/presenter.py +80 -0
  52. cli/commands/billing/costs/daily/__init__.py +1 -0
  53. cli/commands/billing/costs/daily/command.py +55 -0
  54. cli/commands/billing/costs/daily/presenter.py +75 -0
  55. cli/commands/billing/costs/summary/__init__.py +1 -0
  56. cli/commands/billing/costs/summary/command.py +57 -0
  57. cli/commands/billing/costs/summary/presenter.py +42 -0
  58. cli/commands/billing/projects/__init__.py +1 -0
  59. cli/commands/billing/projects/create/__init__.py +1 -0
  60. cli/commands/billing/projects/create/command.py +60 -0
  61. cli/commands/billing/projects/create/presenter.py +26 -0
  62. cli/commands/billing/projects/get/__init__.py +1 -0
  63. cli/commands/billing/projects/get/command.py +33 -0
  64. cli/commands/billing/projects/get/presenter.py +32 -0
  65. cli/commands/billing/projects/list/__init__.py +1 -0
  66. cli/commands/billing/projects/list/command.py +40 -0
  67. cli/commands/billing/projects/list/presenter.py +57 -0
  68. cli/commands/content/__init__.py +1 -0
  69. cli/commands/content/delete/__init__.py +0 -0
  70. cli/commands/content/delete/command.py +49 -0
  71. cli/commands/content/delete/presenter.py +18 -0
  72. cli/commands/content/edit/__init__.py +1 -0
  73. cli/commands/content/edit/command.py +155 -0
  74. cli/commands/content/edit/editor.py +150 -0
  75. cli/commands/content/edit/presenter.py +146 -0
  76. cli/commands/content/get/__init__.py +1 -0
  77. cli/commands/content/get/command.py +39 -0
  78. cli/commands/content/get/presenter.py +176 -0
  79. cli/commands/content/list/__init__.py +1 -0
  80. cli/commands/content/list/command.py +347 -0
  81. cli/commands/content/list/export_formatters.py +409 -0
  82. cli/commands/content/list/export_handler.py +165 -0
  83. cli/commands/content/list/presenter.py +190 -0
  84. cli/commands/credentials/__init__.py +0 -0
  85. cli/commands/credentials/create/__init__.py +0 -0
  86. cli/commands/credentials/create/command.py +165 -0
  87. cli/commands/credentials/create/fields.py +38 -0
  88. cli/commands/credentials/create/presenter.py +20 -0
  89. cli/commands/credentials/update/__init__.py +0 -0
  90. cli/commands/credentials/update/command.py +53 -0
  91. cli/commands/credentials/update/fields.py +71 -0
  92. cli/commands/credentials/update/presenter.py +16 -0
  93. cli/commands/flag_utils.py +366 -0
  94. cli/commands/generate/__init__.py +0 -0
  95. cli/commands/generate/cancel/__init__.py +1 -0
  96. cli/commands/generate/cancel/command.py +44 -0
  97. cli/commands/generate/cancel/presenter.py +26 -0
  98. cli/commands/generate/status/__init__.py +1 -0
  99. cli/commands/generate/status/command.py +58 -0
  100. cli/commands/generate/status/presenter.py +78 -0
  101. cli/commands/generate/text/__init__.py +0 -0
  102. cli/commands/generate/text/command.py +1325 -0
  103. cli/commands/generate/text/concurrent_renderer.py +355 -0
  104. cli/commands/generate/text/presenter.py +287 -0
  105. cli/commands/generate/text/stream_renderer.py +129 -0
  106. cli/commands/knowledge/__init__.py +0 -0
  107. cli/commands/knowledge/collections/__init__.py +0 -0
  108. cli/commands/knowledge/collections/cluster/__init__.py +0 -0
  109. cli/commands/knowledge/collections/cluster/command.py +64 -0
  110. cli/commands/knowledge/collections/cluster/presenter.py +74 -0
  111. cli/commands/knowledge/collections/cluster_status/__init__.py +0 -0
  112. cli/commands/knowledge/collections/cluster_status/command.py +46 -0
  113. cli/commands/knowledge/collections/cluster_status/presenter.py +10 -0
  114. cli/commands/knowledge/collections/create/__init__.py +0 -0
  115. cli/commands/knowledge/collections/create/command.py +137 -0
  116. cli/commands/knowledge/collections/create/presenter.py +38 -0
  117. cli/commands/knowledge/collections/delete/__init__.py +1 -0
  118. cli/commands/knowledge/collections/delete/command.py +47 -0
  119. cli/commands/knowledge/collections/delete/presenter.py +20 -0
  120. cli/commands/knowledge/collections/get/__init__.py +1 -0
  121. cli/commands/knowledge/collections/get/command.py +30 -0
  122. cli/commands/knowledge/collections/get/presenter.py +44 -0
  123. cli/commands/knowledge/collections/list/__init__.py +1 -0
  124. cli/commands/knowledge/collections/list/command.py +41 -0
  125. cli/commands/knowledge/collections/list/presenter.py +68 -0
  126. cli/commands/knowledge/collections/update/__init__.py +0 -0
  127. cli/commands/knowledge/collections/update/command.py +97 -0
  128. cli/commands/knowledge/collections/update/presenter.py +42 -0
  129. cli/commands/knowledge/documents/__init__.py +0 -0
  130. cli/commands/knowledge/documents/bulk_metadata/__init__.py +0 -0
  131. cli/commands/knowledge/documents/bulk_metadata/command.py +119 -0
  132. cli/commands/knowledge/documents/bulk_metadata/presenter.py +36 -0
  133. cli/commands/knowledge/documents/delete/__init__.py +0 -0
  134. cli/commands/knowledge/documents/delete/command.py +47 -0
  135. cli/commands/knowledge/documents/delete/presenter.py +20 -0
  136. cli/commands/knowledge/documents/get/__init__.py +0 -0
  137. cli/commands/knowledge/documents/get/command.py +39 -0
  138. cli/commands/knowledge/documents/get/presenter.py +78 -0
  139. cli/commands/knowledge/documents/ingest/__init__.py +0 -0
  140. cli/commands/knowledge/documents/ingest/command.py +222 -0
  141. cli/commands/knowledge/documents/ingest/presenter.py +41 -0
  142. cli/commands/knowledge/documents/list/__init__.py +0 -0
  143. cli/commands/knowledge/documents/list/command.py +69 -0
  144. cli/commands/knowledge/documents/list/presenter.py +86 -0
  145. cli/commands/knowledge/documents/reingest/__init__.py +0 -0
  146. cli/commands/knowledge/documents/reingest/command.py +102 -0
  147. cli/commands/knowledge/documents/reingest/presenter.py +70 -0
  148. cli/commands/knowledge/documents/update/__init__.py +0 -0
  149. cli/commands/knowledge/documents/update/command.py +85 -0
  150. cli/commands/knowledge/documents/update/presenter.py +37 -0
  151. cli/commands/knowledge/recover/__init__.py +0 -0
  152. cli/commands/knowledge/recover/command.py +46 -0
  153. cli/commands/knowledge/recover/presenter.py +79 -0
  154. cli/commands/knowledge/search/__init__.py +0 -0
  155. cli/commands/knowledge/search/command.py +218 -0
  156. cli/commands/knowledge/search/presenter.py +111 -0
  157. cli/commands/knowledge/synthesis/__init__.py +0 -0
  158. cli/commands/knowledge/synthesis/create/__init__.py +0 -0
  159. cli/commands/knowledge/synthesis/create/command.py +127 -0
  160. cli/commands/knowledge/synthesis/create/presenter.py +33 -0
  161. cli/commands/knowledge/synthesis/delete/__init__.py +0 -0
  162. cli/commands/knowledge/synthesis/delete/command.py +53 -0
  163. cli/commands/knowledge/synthesis/delete/presenter.py +31 -0
  164. cli/commands/knowledge/synthesis/get/__init__.py +0 -0
  165. cli/commands/knowledge/synthesis/get/command.py +55 -0
  166. cli/commands/knowledge/synthesis/get/presenter.py +114 -0
  167. cli/commands/knowledge/synthesis/list/__init__.py +0 -0
  168. cli/commands/knowledge/synthesis/list/command.py +132 -0
  169. cli/commands/knowledge/synthesis/list/presenter.py +84 -0
  170. cli/commands/knowledge/synthesis/refresh/__init__.py +0 -0
  171. cli/commands/knowledge/synthesis/refresh/command.py +42 -0
  172. cli/commands/knowledge/synthesis/refresh/presenter.py +33 -0
  173. cli/commands/knowledge/synthesis/update/__init__.py +0 -0
  174. cli/commands/knowledge/synthesis/update/command.py +76 -0
  175. cli/commands/knowledge/synthesis/update/presenter.py +41 -0
  176. cli/commands/models/__init__.py +0 -0
  177. cli/commands/models/list/__init__.py +0 -0
  178. cli/commands/models/list/command.py +84 -0
  179. cli/commands/models/list/presenter.py +114 -0
  180. cli/commands/organizations/__init__.py +0 -0
  181. cli/commands/organizations/create/command.py +32 -0
  182. cli/commands/organizations/create/presenter.py +9 -0
  183. cli/commands/pipelines/__init__.py +1 -0
  184. cli/commands/pipelines/approvals/__init__.py +1 -0
  185. cli/commands/pipelines/approvals/decide_command.py +77 -0
  186. cli/commands/pipelines/approvals/get_command.py +44 -0
  187. cli/commands/pipelines/approvals/presenter.py +56 -0
  188. cli/commands/pipelines/costs/__init__.py +1 -0
  189. cli/commands/pipelines/costs/command.py +57 -0
  190. cli/commands/pipelines/costs/daily_command.py +54 -0
  191. cli/commands/pipelines/costs/daily_presenter.py +59 -0
  192. cli/commands/pipelines/costs/presenter.py +37 -0
  193. cli/commands/pipelines/create/__init__.py +1 -0
  194. cli/commands/pipelines/create/command.py +103 -0
  195. cli/commands/pipelines/create/presenter.py +22 -0
  196. cli/commands/pipelines/env_vars/__init__.py +1 -0
  197. cli/commands/pipelines/env_vars/command.py +51 -0
  198. cli/commands/pipelines/env_vars/presenter.py +16 -0
  199. cli/commands/pipelines/execute/__init__.py +1 -0
  200. cli/commands/pipelines/execute/command.py +142 -0
  201. cli/commands/pipelines/execute/presenter.py +47 -0
  202. cli/commands/pipelines/executions/__init__.py +1 -0
  203. cli/commands/pipelines/executions/costs/__init__.py +1 -0
  204. cli/commands/pipelines/executions/costs/command.py +48 -0
  205. cli/commands/pipelines/executions/costs/presenter.py +29 -0
  206. cli/commands/pipelines/executions/costs_by_model/__init__.py +1 -0
  207. cli/commands/pipelines/executions/costs_by_model/command.py +50 -0
  208. cli/commands/pipelines/executions/costs_by_model/presenter.py +78 -0
  209. cli/commands/pipelines/executions/costs_by_step/__init__.py +1 -0
  210. cli/commands/pipelines/executions/costs_by_step/command.py +50 -0
  211. cli/commands/pipelines/executions/costs_by_step/presenter.py +72 -0
  212. cli/commands/pipelines/executions/get_command.py +38 -0
  213. cli/commands/pipelines/executions/list_command.py +123 -0
  214. cli/commands/pipelines/executions/presenter.py +131 -0
  215. cli/commands/pipelines/executions/rerun_command.py +41 -0
  216. cli/commands/pipelines/executions/update/__init__.py +1 -0
  217. cli/commands/pipelines/executions/update/command.py +110 -0
  218. cli/commands/pipelines/executions/update/presenter.py +28 -0
  219. cli/commands/pipelines/get/__init__.py +1 -0
  220. cli/commands/pipelines/get/command.py +33 -0
  221. cli/commands/pipelines/get/presenter.py +48 -0
  222. cli/commands/pipelines/list/__init__.py +1 -0
  223. cli/commands/pipelines/list/command.py +53 -0
  224. cli/commands/pipelines/list/presenter.py +66 -0
  225. cli/commands/pipelines/schedules/__init__.py +1 -0
  226. cli/commands/pipelines/schedules/create_command.py +119 -0
  227. cli/commands/pipelines/schedules/create_presenter.py +35 -0
  228. cli/commands/pipelines/schedules/delete_command.py +52 -0
  229. cli/commands/pipelines/schedules/env_vars_command.py +59 -0
  230. cli/commands/pipelines/schedules/env_vars_presenter.py +16 -0
  231. cli/commands/pipelines/schedules/get_command.py +38 -0
  232. cli/commands/pipelines/schedules/list_command.py +33 -0
  233. cli/commands/pipelines/schedules/once_command.py +90 -0
  234. cli/commands/pipelines/schedules/once_presenter.py +30 -0
  235. cli/commands/pipelines/schedules/presenter.py +104 -0
  236. cli/commands/pipelines/schedules/update_command.py +139 -0
  237. cli/commands/pipelines/schedules/update_presenter.py +29 -0
  238. cli/commands/render/__init__.py +0 -0
  239. cli/commands/render/html_to_image/__init__.py +0 -0
  240. cli/commands/render/html_to_image/command.py +170 -0
  241. cli/commands/schemas/__init__.py +0 -0
  242. cli/commands/schemas/create/__init__.py +0 -0
  243. cli/commands/schemas/create/command.py +122 -0
  244. cli/commands/schemas/create/presenter.py +53 -0
  245. cli/commands/schemas/delete/command.py +45 -0
  246. cli/commands/schemas/delete/presenter.py +9 -0
  247. cli/commands/schemas/get/__init__.py +0 -0
  248. cli/commands/schemas/get/command.py +56 -0
  249. cli/commands/schemas/get/presenter.py +129 -0
  250. cli/commands/schemas/list/__init__.py +0 -0
  251. cli/commands/schemas/list/command.py +64 -0
  252. cli/commands/schemas/list/presenter.py +133 -0
  253. cli/commands/schemas/update/__init__.py +0 -0
  254. cli/commands/schemas/update/command.py +369 -0
  255. cli/commands/schemas/update/presenter.py +53 -0
  256. cli/commands/sessions/__init__.py +1 -0
  257. cli/commands/sessions/delete/__init__.py +1 -0
  258. cli/commands/sessions/delete/command.py +47 -0
  259. cli/commands/sessions/delete/presenter.py +10 -0
  260. cli/commands/sessions/get/__init__.py +1 -0
  261. cli/commands/sessions/get/command.py +42 -0
  262. cli/commands/sessions/get/presenter.py +59 -0
  263. cli/commands/sessions/list/__init__.py +1 -0
  264. cli/commands/sessions/list/command.py +61 -0
  265. cli/commands/sessions/list/presenter.py +68 -0
  266. cli/commands/sessions/messages/__init__.py +1 -0
  267. cli/commands/sessions/messages/command.py +78 -0
  268. cli/commands/sessions/messages/presenter.py +79 -0
  269. cli/commands/shared_flags.py +500 -0
  270. cli/commands/sync/__init__.py +0 -0
  271. cli/commands/sync/command.py +45 -0
  272. cli/commands/sync/presenter.py +49 -0
  273. cli/commands/tags/__init__.py +1 -0
  274. cli/commands/tags/create/__init__.py +1 -0
  275. cli/commands/tags/create/command.py +60 -0
  276. cli/commands/tags/delete/__init__.py +1 -0
  277. cli/commands/tags/delete/command.py +47 -0
  278. cli/commands/tags/delete/presenter.py +10 -0
  279. cli/commands/tags/get/command.py +31 -0
  280. cli/commands/tags/get/presenter.py +23 -0
  281. cli/commands/tags/list/__init__.py +1 -0
  282. cli/commands/tags/list/command.py +52 -0
  283. cli/commands/tags/list/presenter.py +49 -0
  284. cli/commands/tags/update/command.py +64 -0
  285. cli/commands/tags/update/presenter.py +9 -0
  286. cli/commands/templates/__init__.py +0 -0
  287. cli/commands/templates/create/__init__.py +0 -0
  288. cli/commands/templates/create/command.py +152 -0
  289. cli/commands/templates/create/presenter.py +86 -0
  290. cli/commands/templates/delete/__init__.py +0 -0
  291. cli/commands/templates/delete/command.py +47 -0
  292. cli/commands/templates/delete/presenter.py +16 -0
  293. cli/commands/templates/get/__init__.py +0 -0
  294. cli/commands/templates/get/command.py +52 -0
  295. cli/commands/templates/get/presenter.py +233 -0
  296. cli/commands/templates/get_by_version/command.py +32 -0
  297. cli/commands/templates/get_by_version/presenter.py +30 -0
  298. cli/commands/templates/list/__init__.py +1 -0
  299. cli/commands/templates/list/command.py +102 -0
  300. cli/commands/templates/list/presenter.py +93 -0
  301. cli/commands/templates/render/__init__.py +0 -0
  302. cli/commands/templates/render/command.py +115 -0
  303. cli/commands/templates/render/presenter.py +276 -0
  304. cli/commands/templates/update/__init__.py +0 -0
  305. cli/commands/templates/update/command.py +199 -0
  306. cli/commands/templates/update/presenter.py +94 -0
  307. cli/commands/templates/version/__init__.py +1 -0
  308. cli/commands/templates/version/command.py +116 -0
  309. cli/commands/templates/version/presenter.py +100 -0
  310. cli/commands/tool_configs/__init__.py +0 -0
  311. cli/commands/tool_configs/create/__init__.py +0 -0
  312. cli/commands/tool_configs/create/command.py +118 -0
  313. cli/commands/tool_configs/create/presenter.py +53 -0
  314. cli/commands/tool_configs/delete/__init__.py +0 -0
  315. cli/commands/tool_configs/delete/command.py +47 -0
  316. cli/commands/tool_configs/delete/presenter.py +18 -0
  317. cli/commands/tool_configs/get/__init__.py +0 -0
  318. cli/commands/tool_configs/get/command.py +31 -0
  319. cli/commands/tool_configs/get/presenter.py +62 -0
  320. cli/commands/tool_configs/list/__init__.py +0 -0
  321. cli/commands/tool_configs/list/command.py +59 -0
  322. cli/commands/tool_configs/list/presenter.py +60 -0
  323. cli/commands/tool_configs/update/__init__.py +0 -0
  324. cli/commands/tool_configs/update/command.py +128 -0
  325. cli/commands/tool_configs/update/presenter.py +53 -0
  326. cli/commands/tools/__init__.py +1 -0
  327. cli/commands/tools/get/__init__.py +1 -0
  328. cli/commands/tools/get/command.py +42 -0
  329. cli/commands/tools/get/presenter.py +45 -0
  330. cli/commands/tools/list/__init__.py +1 -0
  331. cli/commands/tools/list/command.py +56 -0
  332. cli/commands/tools/list/presenter.py +44 -0
  333. cli/commands/users/__init__.py +0 -0
  334. cli/commands/users/create/command.py +53 -0
  335. cli/commands/users/create/presenter.py +9 -0
  336. cli/commands/whoami/__init__.py +0 -0
  337. cli/commands/whoami/command.py +42 -0
  338. cli/infrastructure/__init__.py +0 -0
  339. cli/infrastructure/auth_storage.py +71 -0
  340. cli/infrastructure/client_factory.py +36 -0
  341. cli/infrastructure/command.py +75 -0
  342. cli/infrastructure/config.py +188 -0
  343. cli/infrastructure/console.py +27 -0
  344. cli/infrastructure/editor.py +138 -0
  345. cli/infrastructure/error_display.py +178 -0
  346. cli/infrastructure/field_extractor.py +360 -0
  347. cli/infrastructure/file_content.py +210 -0
  348. cli/infrastructure/filter_parser.py +256 -0
  349. cli/infrastructure/formatters/__init__.py +0 -0
  350. cli/infrastructure/formatters/base.py +99 -0
  351. cli/infrastructure/formatters/compact_formatter.py +245 -0
  352. cli/infrastructure/formatters/json_formatter.py +84 -0
  353. cli/infrastructure/formatters/lines_formatter.py +102 -0
  354. cli/infrastructure/formatting/__init__.py +0 -0
  355. cli/infrastructure/formatting/fields.py +193 -0
  356. cli/infrastructure/forms/__init__.py +0 -0
  357. cli/infrastructure/forms/agent_picker.py +123 -0
  358. cli/infrastructure/forms/agent_tool_editor.py +384 -0
  359. cli/infrastructure/forms/agent_tools_manager.py +212 -0
  360. cli/infrastructure/forms/base_picker.py +469 -0
  361. cli/infrastructure/forms/components.py +126 -0
  362. cli/infrastructure/forms/json_schema_builder.py +149 -0
  363. cli/infrastructure/forms/model_picker.py +134 -0
  364. cli/infrastructure/forms/parsers.py +173 -0
  365. cli/infrastructure/forms/resolution_modal.py +302 -0
  366. cli/infrastructure/forms/schema_picker.py +137 -0
  367. cli/infrastructure/forms/tag_management_modal.py +103 -0
  368. cli/infrastructure/forms/tag_picker.py +207 -0
  369. cli/infrastructure/forms/template_picker.py +131 -0
  370. cli/infrastructure/forms/tool_config_picker.py +130 -0
  371. cli/infrastructure/forms/tool_picker.py +103 -0
  372. cli/infrastructure/injection/__init__.py +0 -0
  373. cli/infrastructure/injection/parser.py +302 -0
  374. cli/infrastructure/injection/resolver.py +399 -0
  375. cli/infrastructure/kv_parser.py +130 -0
  376. cli/infrastructure/local_storage.py +227 -0
  377. cli/infrastructure/macro_parser.py +215 -0
  378. cli/infrastructure/output.py +192 -0
  379. cli/infrastructure/provider_setup.py +81 -0
  380. cli/infrastructure/renderers/__init__.py +0 -0
  381. cli/infrastructure/renderers/entity_renderer.py +77 -0
  382. cli/infrastructure/renderers/list_renderer.py +114 -0
  383. cli/infrastructure/scope_utils.py +47 -0
  384. cli/infrastructure/spinner.py +101 -0
  385. cli/infrastructure/tui/__init__.py +0 -0
  386. cli/infrastructure/tui/clipboard.py +41 -0
  387. cli/infrastructure/tui/formatters.py +105 -0
  388. cli/infrastructure/tui/preview.py +14 -0
  389. cli/infrastructure/tui/selectable.py +198 -0
  390. cli/infrastructure/validation/__init__.py +0 -0
  391. cli/infrastructure/validation/tag_validation.py +74 -0
  392. cli/main.py +759 -0
  393. cli/tui/__init__.py +0 -0
  394. cli/tui/app.py +199 -0
  395. cli/tui/app_store.py +73 -0
  396. cli/tui/chat/__init__.py +0 -0
  397. cli/tui/chat/commands/__init__.py +0 -0
  398. cli/tui/chat/commands/base.py +65 -0
  399. cli/tui/chat/commands/create_session.py +135 -0
  400. cli/tui/chat/commands/load_session.py +119 -0
  401. cli/tui/chat/commands/regenerate.py +120 -0
  402. cli/tui/chat/commands/reload_session.py +63 -0
  403. cli/tui/chat/commands/send_message.py +190 -0
  404. cli/tui/chat/commands/undo.py +66 -0
  405. cli/tui/chat/editor.py +71 -0
  406. cli/tui/chat/messages.py +223 -0
  407. cli/tui/chat/pane.py +141 -0
  408. cli/tui/chat/renderers/__init__.py +0 -0
  409. cli/tui/chat/renderers/base.py +72 -0
  410. cli/tui/chat/renderers/markdown.py +250 -0
  411. cli/tui/chat/renderers/plain.py +83 -0
  412. cli/tui/chat/screen.py +1155 -0
  413. cli/tui/chat/services/__init__.py +0 -0
  414. cli/tui/chat/services/injection.py +386 -0
  415. cli/tui/chat/services/name_generator.py +256 -0
  416. cli/tui/chat/slash_commands.py +424 -0
  417. cli/tui/chat/store.py +280 -0
  418. cli/tui/chat/types.py +220 -0
  419. cli/tui/chat/widgets/__init__.py +0 -0
  420. cli/tui/chat/widgets/chat_header.py +75 -0
  421. cli/tui/chat/widgets/chat_input.py +362 -0
  422. cli/tui/chat/widgets/injection_popup.py +161 -0
  423. cli/tui/chat/widgets/message_display.py +287 -0
  424. cli/tui/chat/widgets/session_sidebar.py +214 -0
  425. cli/tui/chat/widgets/welcome_screen.py +290 -0
  426. cli/tui/screens/__init__.py +0 -0
  427. cli/tui/screens/agents.py +344 -0
  428. cli/tui/screens/base.py +301 -0
  429. cli/tui/screens/content.py +508 -0
  430. cli/tui/screens/dashboard.py +89 -0
  431. cli/tui/screens/models.py +96 -0
  432. cli/tui/screens/nav_screen.py +186 -0
  433. cli/tui/screens/schemas.py +522 -0
  434. cli/tui/screens/templates.py +734 -0
  435. cli/tui/screens/tool_configs.py +335 -0
  436. cli/tui/styles/__init__.py +0 -0
  437. cli/tui/widgets/__init__.py +0 -0
  438. cli/tui/widgets/agent_create_modal.py +139 -0
  439. cli/tui/widgets/agent_form_modal.py +659 -0
  440. cli/tui/widgets/agent_update_modal.py +299 -0
  441. cli/tui/widgets/base_form_modal.py +77 -0
  442. cli/tui/widgets/confirm_modal.py +75 -0
  443. cli/tui/widgets/help_modal.py +145 -0
  444. cli/tui/widgets/new_session_modal.py +328 -0
  445. cli/tui/widgets/schema_create_modal.py +271 -0
  446. cli/tui/widgets/schema_update_modal.py +188 -0
  447. cli/tui/widgets/status_footer.py +147 -0
  448. cli/tui/widgets/template_create_modal.py +502 -0
  449. cli/tui/widgets/template_update_modal.py +308 -0
  450. cli/tui/widgets/tool_config_create_modal.py +216 -0
  451. cli/tui/widgets/tool_config_update_modal.py +208 -0
@@ -0,0 +1,290 @@
1
+ """Welcome screen widget for the chat interface.
2
+
3
+ Displays quick access to frequently used agents and recent sessions,
4
+ allowing users to start chatting with minimal friction.
5
+ """
6
+
7
+ from datetime import datetime, timezone
8
+ from typing import TYPE_CHECKING, Any
9
+ from uuid import UUID
10
+
11
+ from textual import on, work
12
+ from textual.app import ComposeResult
13
+ from textual.binding import Binding
14
+ from textual.containers import Vertical
15
+ from textual.widget import Widget
16
+ from textual.widgets import OptionList, Static
17
+ from textual.widgets.option_list import Option
18
+
19
+ from alloy_runtime_sdk.api_client.client import ApiClient
20
+ from alloy_runtime_types.dtos.agents import AgentSummary
21
+ from alloy_runtime_types.dtos.sessions import SessionSummary
22
+
23
+ from cli.tui.chat.messages import AgentQuickSelected, RecentSessionSelected
24
+
25
+ if TYPE_CHECKING:
26
+ from cli.tui.app import AlloyRuntimeApp
27
+
28
+
29
+ class WelcomeScreen(Widget):
30
+ """Welcome screen with quick access to agents and recent sessions.
31
+
32
+ Features:
33
+ - Shows top agents by usage (named session count)
34
+ - Shows recent named sessions for quick resumption
35
+ - Number keys (1-9) for instant agent selection
36
+ - Keyboard navigation with arrow keys
37
+
38
+ Posts Messages:
39
+ - AgentQuickSelected: When user selects an agent
40
+ - RecentSessionSelected: When user selects a recent session
41
+ """
42
+
43
+ app: "AlloyRuntimeApp"
44
+
45
+ BINDINGS = [
46
+ Binding("1", "select_agent(0)", "Agent 1", show=False),
47
+ Binding("2", "select_agent(1)", "Agent 2", show=False),
48
+ Binding("3", "select_agent(2)", "Agent 3", show=False),
49
+ Binding("4", "select_agent(3)", "Agent 4", show=False),
50
+ Binding("5", "select_agent(4)", "Agent 5", show=False),
51
+ Binding("6", "select_agent(5)", "Agent 6", show=False),
52
+ Binding("7", "select_agent(6)", "Agent 7", show=False),
53
+ Binding("8", "select_agent(7)", "Agent 8", show=False),
54
+ Binding("9", "select_agent(8)", "Agent 9", show=False),
55
+ ]
56
+
57
+ DEFAULT_CSS = """
58
+ WelcomeScreen {
59
+ width: 100%;
60
+ height: auto;
61
+ padding: 1 2;
62
+ }
63
+
64
+ WelcomeScreen #welcome-title {
65
+ text-align: center;
66
+ text-style: bold;
67
+ margin-bottom: 1;
68
+ }
69
+
70
+ WelcomeScreen .section-title {
71
+ margin-top: 1;
72
+ margin-bottom: 0;
73
+ text-style: bold;
74
+ color: $accent;
75
+ }
76
+
77
+ WelcomeScreen .section-divider {
78
+ color: $text-muted;
79
+ margin-bottom: 0;
80
+ }
81
+
82
+ WelcomeScreen #agents-list {
83
+ height: auto;
84
+ max-height: 12;
85
+ margin-bottom: 1;
86
+ background: transparent;
87
+ border: none;
88
+ }
89
+
90
+ WelcomeScreen #sessions-list {
91
+ height: auto;
92
+ max-height: 6;
93
+ margin-bottom: 1;
94
+ background: transparent;
95
+ border: none;
96
+ }
97
+
98
+ WelcomeScreen .empty-hint {
99
+ color: $text-muted;
100
+ text-style: italic;
101
+ padding: 0 1;
102
+ }
103
+
104
+ WelcomeScreen #footer-hints {
105
+ margin-top: 1;
106
+ color: $text-muted;
107
+ }
108
+ """
109
+
110
+ def __init__(self, **kwargs: Any) -> None:
111
+ super().__init__(**kwargs)
112
+ self._agents: list[AgentSummary] = []
113
+ self._sessions: list[SessionSummary] = []
114
+ self._loading = True
115
+
116
+ def compose(self) -> ComposeResult:
117
+ with Vertical():
118
+ yield Static("Welcome to Alloy Runtime", id="welcome-title")
119
+
120
+ yield Static("Quick Start", classes="section-title")
121
+ yield Static("─" * 40, classes="section-divider")
122
+ yield OptionList(id="agents-list")
123
+
124
+ yield Static("Recent Sessions", classes="section-title")
125
+ yield Static("─" * 40, classes="section-divider")
126
+ yield OptionList(id="sessions-list")
127
+
128
+ yield Static(
129
+ "[dim][Ctrl+N][/] New session [dim]/[/] Search sessions [dim][?][/] Help",
130
+ id="footer-hints",
131
+ )
132
+
133
+ def on_mount(self) -> None:
134
+ """Load agents and sessions when mounted."""
135
+ self._load_data()
136
+
137
+ @work(exclusive=True)
138
+ async def _load_data(self) -> None:
139
+ """Load frequently used agents and recent sessions."""
140
+ try:
141
+ client = await self._get_client()
142
+
143
+ # Load agents sorted by usage (last 90 days)
144
+ agents_response = await client.list_agents(
145
+ sort_by="usage",
146
+ usage_days=90,
147
+ limit=7,
148
+ )
149
+ self._agents = list(agents_response.agents)
150
+
151
+ # Load recent named sessions
152
+ sessions_response = await client.list_sessions(
153
+ named_only=True,
154
+ limit=5,
155
+ )
156
+ self._sessions = list(sessions_response.sessions)
157
+
158
+ self._loading = False
159
+ self._update_agents_list()
160
+ self._update_sessions_list()
161
+
162
+ except Exception as e:
163
+ self._loading = False
164
+ self._show_error(str(e))
165
+
166
+ async def _get_client(self) -> ApiClient:
167
+ """Get the shared server client."""
168
+ return await self.app.store.get_client()
169
+
170
+ def _update_agents_list(self) -> None:
171
+ """Update the agents option list."""
172
+ agents_list = self.query_one("#agents-list", OptionList)
173
+ agents_list.clear_options()
174
+
175
+ if not self._agents:
176
+ agents_list.add_option(
177
+ Option(
178
+ "[dim italic]No agents yet. Press Ctrl+N to start.[/]",
179
+ disabled=True,
180
+ )
181
+ )
182
+ return
183
+
184
+ for i, agent in enumerate(self._agents):
185
+ # Format: [1] Agent Name (N chats) - description
186
+ number = i + 1
187
+ usage_text = ""
188
+ if agent.usage_count is not None and agent.usage_count > 0:
189
+ usage_text = f" ({agent.usage_count} chat{'s' if agent.usage_count != 1 else ''})"
190
+
191
+ desc_text = ""
192
+ if agent.description:
193
+ desc = (
194
+ agent.description[:40] + "..."
195
+ if len(agent.description) > 40
196
+ else agent.description
197
+ )
198
+ desc_text = f" [dim]- {desc}[/]"
199
+
200
+ label = (
201
+ f"[bold cyan][{number}][/] {agent.name}[dim]{usage_text}[/]{desc_text}"
202
+ )
203
+ agents_list.add_option(Option(label, id=str(agent.id)))
204
+
205
+ def _update_sessions_list(self) -> None:
206
+ """Update the sessions option list."""
207
+ sessions_list = self.query_one("#sessions-list", OptionList)
208
+ sessions_list.clear_options()
209
+
210
+ if not self._sessions:
211
+ sessions_list.add_option(
212
+ Option(
213
+ "[dim italic]No recent sessions[/]",
214
+ disabled=True,
215
+ )
216
+ )
217
+ return
218
+
219
+ for session in self._sessions:
220
+ # Format: Session Name - time ago
221
+ name = session.name or "Untitled"
222
+ if len(name) > 35:
223
+ name = name[:32] + "..."
224
+
225
+ time_ago = self._format_time_ago(session.last_activity_at)
226
+ label = f" {name} [dim]{time_ago}[/]"
227
+ sessions_list.add_option(Option(label, id=str(session.id)))
228
+
229
+ def _format_time_ago(self, dt: datetime) -> str:
230
+ """Format datetime as human-readable time ago."""
231
+ now = datetime.now(timezone.utc)
232
+ if dt.tzinfo is None:
233
+ dt = dt.replace(tzinfo=timezone.utc)
234
+
235
+ delta = now - dt
236
+ seconds = delta.total_seconds()
237
+
238
+ if seconds < 60:
239
+ return "just now"
240
+ elif seconds < 3600:
241
+ mins = int(seconds / 60)
242
+ return f"{mins}m ago"
243
+ elif seconds < 86400:
244
+ hours = int(seconds / 3600)
245
+ return f"{hours}h ago"
246
+ elif seconds < 604800:
247
+ days = int(seconds / 86400)
248
+ return f"{days}d ago"
249
+ else:
250
+ weeks = int(seconds / 604800)
251
+ return f"{weeks}w ago"
252
+
253
+ def _show_error(self, error: str) -> None:
254
+ """Show error in the agents list."""
255
+ agents_list = self.query_one("#agents-list", OptionList)
256
+ agents_list.clear_options()
257
+ agents_list.add_option(Option(f"[red]Error loading: {error}[/]", disabled=True))
258
+
259
+ def action_select_agent(self, index: int) -> None:
260
+ """Select an agent by number key (0-indexed)."""
261
+ if index < len(self._agents):
262
+ agent = self._agents[index]
263
+ self.post_message(AgentQuickSelected(agent.id, agent.name))
264
+
265
+ @on(OptionList.OptionSelected, "#agents-list")
266
+ def on_agent_selected(self, event: OptionList.OptionSelected) -> None:
267
+ """Handle agent selection from option list."""
268
+ if event.option.id is None:
269
+ return
270
+
271
+ agent_id = UUID(event.option.id)
272
+ # Find the agent name
273
+ for agent in self._agents:
274
+ if agent.id == agent_id:
275
+ self.post_message(AgentQuickSelected(agent_id, agent.name))
276
+ break
277
+
278
+ @on(OptionList.OptionSelected, "#sessions-list")
279
+ def on_session_selected(self, event: OptionList.OptionSelected) -> None:
280
+ """Handle session selection from option list."""
281
+ if event.option.id is None:
282
+ return
283
+
284
+ session_id = UUID(event.option.id)
285
+ self.post_message(RecentSessionSelected(session_id))
286
+
287
+ def refresh_data(self) -> None:
288
+ """Refresh the agents and sessions data."""
289
+ self._loading = True
290
+ self._load_data()
File without changes
@@ -0,0 +1,344 @@
1
+ """Agents screen for browsing and managing AI agents.
2
+
3
+ This screen provides:
4
+ - Search and browse agents
5
+ - View agent details in preview panel
6
+ - Create new agents
7
+ - Copy agent names
8
+ """
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from textual import on, work
13
+ from textual.app import ComposeResult
14
+ from textual.binding import Binding
15
+ from textual.containers import Horizontal, Vertical, VerticalScroll
16
+ from textual.widgets import Button, DataTable, Input, Static
17
+
18
+ from alloy_runtime_types.dtos.agents import AgentSummary
19
+
20
+ from cli.infrastructure.scope_utils import format_scope_label
21
+ from cli.infrastructure.tui.clipboard import copy_to_clipboard
22
+ from cli.infrastructure.tui.formatters import format_datetime, truncate_text
23
+ from cli.tui.screens.base import BrowserWidget
24
+ from cli.tui.screens.nav_screen import NavScreen
25
+ from cli.tui.widgets.agent_create_modal import AgentCreateModal
26
+ from cli.tui.widgets.agent_form_modal import AgentFormResult
27
+ from cli.tui.widgets.agent_update_modal import AgentUpdateModal
28
+ from cli.tui.widgets.confirm_modal import ConfirmModal
29
+
30
+ if TYPE_CHECKING:
31
+ from cli.tui.app import AlloyRuntimeApp
32
+
33
+
34
+ class AgentsWidget(BrowserWidget[AgentSummary]):
35
+ """Widget for browsing and managing agents."""
36
+
37
+ app: "AlloyRuntimeApp"
38
+
39
+ TITLE = "Agents"
40
+ TABLE_COLUMNS = ["Name", "Scope", "Models", "Updated"]
41
+
42
+ def compose(self) -> ComposeResult:
43
+ """Compose the browser layout with a New Agent button."""
44
+ with Vertical(id="browser-root"):
45
+ with Vertical(id="search-container"):
46
+ with Horizontal(id="search-row"):
47
+ yield Input(
48
+ placeholder=f"Search {self.TITLE.lower()}...",
49
+ id="search-input",
50
+ )
51
+ yield Button("+ New", id="new-agent-btn", variant="primary")
52
+ yield Static("Loading...", id="status-bar")
53
+
54
+ with Horizontal(id="main-container"):
55
+ with Vertical(id="list-container"):
56
+ yield DataTable(id="data-table", cursor_type="row")
57
+
58
+ with Vertical(id="detail-container"):
59
+ yield Static("Preview", id="preview-title")
60
+ with VerticalScroll(id="preview-scroll"):
61
+ yield Static(
62
+ "Select an item to view details",
63
+ id="preview-content",
64
+ )
65
+
66
+ @on(Button.Pressed, "#new-agent-btn")
67
+ def _on_new_agent_btn_pressed(self, event: Button.Pressed) -> None:
68
+ """Handle New Agent button press."""
69
+ self.open_new_agent_modal()
70
+
71
+ async def _fetch_items(self, query: str) -> tuple[list[AgentSummary], int]:
72
+ """Fetch agents from the API."""
73
+ client = await self._ensure_client()
74
+ response = await client.list_agents(
75
+ search=query if query else None,
76
+ limit=50,
77
+ )
78
+
79
+ # Update agent cache for cross-screen use (e.g., chat agent picker)
80
+ self.app.store.agent_cache = list(response.agents)
81
+
82
+ return response.agents, response.total
83
+
84
+ def _format_row(self, item: AgentSummary) -> tuple[str, ...]:
85
+ """Format an agent as a table row."""
86
+ return (
87
+ truncate_text(item.name, max_length=30),
88
+ format_scope_label(
89
+ str(item.organization_id) if item.organization_id else None,
90
+ str(item.user_id) if item.user_id else None,
91
+ ),
92
+ f"{item.model_count} models",
93
+ format_datetime(item.updated_at),
94
+ )
95
+
96
+ def _update_preview(self, item: AgentSummary) -> None:
97
+ """Update the preview pane with agent details."""
98
+ preview = self.query_one("#preview-content", Static)
99
+
100
+ scope = format_scope_label(
101
+ str(item.organization_id) if item.organization_id else None,
102
+ str(item.user_id) if item.user_id else None,
103
+ )
104
+
105
+ created = format_datetime(item.created_at)
106
+ updated = format_datetime(item.updated_at)
107
+
108
+ system_instruction_id = (
109
+ str(item.system_instruction_version_id)
110
+ if item.system_instruction_version_id
111
+ else "-"
112
+ )
113
+ input_schema_id = str(item.input_schema_id) if item.input_schema_id else "-"
114
+ output_schema_id = str(item.output_schema_id) if item.output_schema_id else "-"
115
+
116
+ preview_text = f"""[bold]Agent ID:[/] {item.id}
117
+ [bold]Name:[/] {item.name}
118
+ [bold]Scope:[/] {scope}
119
+ [bold]Models:[/] {item.model_count}
120
+
121
+ [bold]Description:[/]
122
+ {item.description or "(no description)"}
123
+
124
+ [bold]System Instruction Version ID:[/]
125
+ {system_instruction_id}
126
+
127
+ [bold]Input Schema ID:[/]
128
+ {input_schema_id}
129
+
130
+ [bold]Output Schema ID:[/]
131
+ {output_schema_id}
132
+
133
+ [bold]Created:[/] {created}
134
+ [bold]Updated:[/] {updated}"""
135
+
136
+ preview.update(preview_text)
137
+
138
+ # =========================================================================
139
+ # Public methods for parent Screen to call
140
+ # =========================================================================
141
+
142
+ def copy_agent_name(self) -> None:
143
+ """Copy the agent name to clipboard."""
144
+ agent = self.get_selected_item()
145
+ if not agent:
146
+ self.app.notify("No agent selected", severity="warning")
147
+ return
148
+
149
+ copy_to_clipboard(self.app, agent.name, "Agent name")
150
+
151
+ def open_new_agent_modal(self) -> None:
152
+ """Open the agent creation modal."""
153
+ self._open_agent_create_modal()
154
+
155
+ def open_edit_agent_modal(self) -> None:
156
+ """Open the agent update modal for the selected agent."""
157
+ agent = self.get_selected_item()
158
+ if not agent:
159
+ self.app.notify("No agent selected", severity="warning")
160
+ return
161
+ self._open_agent_update_modal(agent)
162
+
163
+ def delete_selected_agent(self) -> None:
164
+ """Delete the currently selected agent."""
165
+ agent = self.get_selected_item()
166
+ if not agent:
167
+ self.app.notify("No agent selected", severity="warning")
168
+ return
169
+
170
+ agent_name = agent.name
171
+ self.app.push_screen(
172
+ ConfirmModal(
173
+ title="Delete Agent",
174
+ message=f"Delete agent '{agent_name}'? This cannot be undone.",
175
+ ),
176
+ lambda confirmed: self._on_delete_confirmed(confirmed, agent_name),
177
+ )
178
+
179
+ # =========================================================================
180
+ # Internal async operations
181
+ # =========================================================================
182
+
183
+ @work(exclusive=True)
184
+ async def _open_agent_create_modal(self) -> None:
185
+ """Open the agent creation modal."""
186
+ try:
187
+ client = await self._ensure_client()
188
+ self.app.push_screen(
189
+ AgentCreateModal(client=client),
190
+ self._on_agent_created,
191
+ )
192
+ except Exception as e:
193
+ self.app.notify(f"Failed to open agent creator: {e}", severity="error")
194
+
195
+ def _on_agent_created(self, result: AgentFormResult | None) -> None:
196
+ """Callback when agent creation modal is dismissed."""
197
+ if result is not None:
198
+ self.app.notify(
199
+ f"Created agent: {result.agent_name}",
200
+ severity="information",
201
+ )
202
+ # Refresh the agent list
203
+ self._do_search(self._last_search)
204
+
205
+ @work(exclusive=True)
206
+ async def _open_agent_update_modal(self, agent_summary: AgentSummary) -> None:
207
+ """Fetch full agent details and open the update modal."""
208
+ try:
209
+ client = await self._ensure_client()
210
+ # Fetch full agent details (summary doesn't have all fields)
211
+ agent = await client.get_agent(str(agent_summary.id))
212
+ self.app.push_screen(
213
+ AgentUpdateModal(client=client, agent=agent),
214
+ self._on_agent_updated,
215
+ )
216
+ except Exception as e:
217
+ self.app.notify(f"Failed to open agent editor: {e}", severity="error")
218
+
219
+ def _on_agent_updated(self, result: AgentFormResult | None) -> None:
220
+ """Callback when agent update modal is dismissed."""
221
+ if result is not None:
222
+ self.app.notify(
223
+ f"Updated agent: {result.agent_name}",
224
+ severity="information",
225
+ )
226
+ # Refresh the agent list
227
+ self._do_search(self._last_search)
228
+
229
+ def _on_delete_confirmed(self, confirmed: bool | None, agent_name: str) -> None:
230
+ """Callback when delete confirmation modal is dismissed."""
231
+ if confirmed:
232
+ self._execute_delete(agent_name)
233
+
234
+ @work(exclusive=True)
235
+ async def _execute_delete(self, agent_name: str) -> None:
236
+ """Execute the delete operation."""
237
+ try:
238
+ client = await self._ensure_client()
239
+ await client.delete_agent(agent_name)
240
+
241
+ self.app.notify(
242
+ f"Agent '{agent_name}' deleted successfully",
243
+ severity="information",
244
+ )
245
+ # Refresh the agent list
246
+ self._do_search(self._last_search)
247
+
248
+ except Exception as e:
249
+ error_msg = str(e)
250
+ if "pipeline" in error_msg.lower():
251
+ self.app.notify(
252
+ "Cannot delete: agent is referenced by pipelines",
253
+ severity="error",
254
+ )
255
+ else:
256
+ self.app.notify(f"Failed to delete agent: {e}", severity="error")
257
+
258
+
259
+ class AgentsScreen(NavScreen):
260
+ """Screen for browsing and managing agents.
261
+
262
+ This is a proper Textual Screen that wraps the AgentsWidget
263
+ and provides keybindings that work regardless of focus.
264
+ """
265
+
266
+ app: "AlloyRuntimeApp"
267
+
268
+ SCREEN_ID = "agents"
269
+
270
+ DEFAULT_CSS = """
271
+ AgentsScreen #screen-root {
272
+ height: 1fr;
273
+ }
274
+
275
+ AgentsScreen .browser-widget {
276
+ height: 100%;
277
+ width: 100%;
278
+ }
279
+ """
280
+
281
+ # Screen-specific bindings (Ctrl+ prefixed)
282
+ # NavScreen.BINDINGS provides global navigation (c, a, t, s, m, d, ?)
283
+ BINDINGS = NavScreen.BINDINGS + [
284
+ Binding("ctrl+f", "focus_search", "Search", show=True),
285
+ Binding("ctrl+r", "refresh", "Refresh", show=True),
286
+ Binding("ctrl+y", "copy_name", "Copy Name", show=True),
287
+ Binding("ctrl+n", "new_agent", "New", show=True),
288
+ Binding("ctrl+e", "edit_agent", "Edit", show=True),
289
+ Binding("ctrl+d", "delete_agent", "Delete", show=True),
290
+ Binding("escape", "focus_table", "Focus List", show=False),
291
+ Binding("enter", "copy_name", "Copy Name", show=False),
292
+ ]
293
+
294
+ def compose_content(self) -> ComposeResult:
295
+ """Compose the screen content."""
296
+ yield AgentsWidget(id="agents-widget")
297
+
298
+ def on_screen_resume(self) -> None:
299
+ """Focus the data table when screen becomes active."""
300
+ self.call_after_refresh(self._focus_table)
301
+
302
+ def _focus_table(self) -> None:
303
+ """Focus the data table."""
304
+ try:
305
+ widget = self.query_one("#agents-widget", AgentsWidget)
306
+ widget.focus_table()
307
+ except Exception:
308
+ pass
309
+
310
+ def _get_widget(self) -> AgentsWidget:
311
+ """Get the agents widget."""
312
+ return self.query_one("#agents-widget", AgentsWidget)
313
+
314
+ # =========================================================================
315
+ # Actions (keybinding handlers)
316
+ # =========================================================================
317
+
318
+ def action_focus_search(self) -> None:
319
+ """Focus the search input."""
320
+ self._get_widget().focus_search()
321
+
322
+ def action_focus_table(self) -> None:
323
+ """Focus the data table."""
324
+ self._get_widget().focus_table()
325
+
326
+ def action_refresh(self) -> None:
327
+ """Refresh the current search results."""
328
+ self._get_widget().refresh_data()
329
+
330
+ def action_copy_name(self) -> None:
331
+ """Copy the agent name to clipboard."""
332
+ self._get_widget().copy_agent_name()
333
+
334
+ def action_new_agent(self) -> None:
335
+ """Open the agent creation modal."""
336
+ self._get_widget().open_new_agent_modal()
337
+
338
+ def action_edit_agent(self) -> None:
339
+ """Open the agent update modal."""
340
+ self._get_widget().open_edit_agent_modal()
341
+
342
+ def action_delete_agent(self) -> None:
343
+ """Delete the selected agent."""
344
+ self._get_widget().delete_selected_agent()