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,508 @@
1
+ """Content screen for browsing and managing content parts.
2
+
3
+ This screen provides:
4
+ - Search and browse AI-generated content parts
5
+ - View content details in preview panel
6
+ - Manage tags on content
7
+ - Copy content text/JSON
8
+ - Open content in external editor
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import subprocess
14
+ import tempfile
15
+ from typing import TYPE_CHECKING, Any
16
+ from uuid import UUID
17
+
18
+ from textual import on, work
19
+ from textual.app import ComposeResult
20
+ from textual.binding import Binding
21
+ from textual.containers import Horizontal, Vertical, VerticalScroll
22
+ from textual.widgets import DataTable, Input, Static
23
+
24
+ from alloy_runtime_types.dtos.content import UpdateContentPartRequest
25
+ from alloy_runtime_types.enums.content_enums import ContentSortField
26
+
27
+ from cli.infrastructure.forms.tag_management_modal import TagManagementModal
28
+ from cli.infrastructure.tui.clipboard import copy_to_clipboard
29
+ from cli.tui.widgets.confirm_modal import ConfirmModal
30
+ from cli.infrastructure.tui.formatters import (
31
+ format_datetime,
32
+ format_tags_list,
33
+ truncate_text,
34
+ )
35
+ from cli.tui.screens.base import BrowserWidget
36
+ from cli.tui.screens.nav_screen import NavScreen
37
+ from cli.tui.screens.models import ContentPartView
38
+
39
+ if TYPE_CHECKING:
40
+ from cli.tui.app import AlloyRuntimeApp
41
+
42
+
43
+ class ContentWidget(BrowserWidget[ContentPartView]):
44
+ """Widget for browsing and managing content parts."""
45
+
46
+ app: "AlloyRuntimeApp"
47
+
48
+ TITLE = "Content"
49
+ TABLE_COLUMNS = ["Type", "Content", "Tags", "Model", "Updated"]
50
+
51
+ def __init__(self, **kwargs: Any) -> None:
52
+ """Initialize the content widget."""
53
+ super().__init__(**kwargs)
54
+ self._pending_tag_update_content_id: str | None = None
55
+ # Additional filter state for three-input search
56
+ self._last_tag_filter: str = ""
57
+ self._last_external_id: str = ""
58
+
59
+ def compose(self) -> ComposeResult:
60
+ """Compose the browser layout with three search inputs."""
61
+ with Vertical(id="browser-root"):
62
+ with Vertical(id="search-container"):
63
+ with Horizontal(id="search-row"):
64
+ yield Input(
65
+ placeholder="Search content...",
66
+ id="search-input",
67
+ )
68
+ yield Input(
69
+ placeholder="Tag filter (e.g. code.*)",
70
+ id="tag-input",
71
+ )
72
+ yield Input(
73
+ placeholder="External ID",
74
+ id="external-id-input",
75
+ )
76
+ yield Static("Loading...", id="status-bar")
77
+
78
+ with Horizontal(id="main-container"):
79
+ with Vertical(id="list-container"):
80
+ yield DataTable(id="data-table", cursor_type="row")
81
+
82
+ with Vertical(id="detail-container"):
83
+ yield Static("Preview", id="preview-title")
84
+ with VerticalScroll(id="preview-scroll"):
85
+ yield Static(
86
+ "Select an item to view details",
87
+ id="preview-content",
88
+ )
89
+
90
+ @on(Input.Changed, "#tag-input")
91
+ def _on_tag_input_changed(self, event: Input.Changed) -> None:
92
+ """Handle tag filter input changes."""
93
+ tag_filter = event.value.strip()
94
+ if tag_filter != self._last_tag_filter:
95
+ self._last_tag_filter = tag_filter
96
+ self._trigger_search()
97
+
98
+ @on(Input.Changed, "#external-id-input")
99
+ def _on_external_id_input_changed(self, event: Input.Changed) -> None:
100
+ """Handle external ID input changes."""
101
+ external_id = event.value.strip()
102
+ if external_id != self._last_external_id:
103
+ self._last_external_id = external_id
104
+ self._trigger_search()
105
+
106
+ def _trigger_search(self) -> None:
107
+ """Trigger a search with all current filter values."""
108
+ self._do_search(self._last_search)
109
+
110
+ async def _fetch_items(self, query: str) -> tuple[list[ContentPartView], int]:
111
+ """Fetch content parts from the API with all filters."""
112
+ client = await self._ensure_client()
113
+ response = await client.list_content_parts(
114
+ search=query if query else None,
115
+ tag_path=self._last_tag_filter if self._last_tag_filter else None,
116
+ external_id=self._last_external_id if self._last_external_id else None,
117
+ sort_by=ContentSortField.UPDATED_AT,
118
+ limit=50,
119
+ )
120
+
121
+ items = [ContentPartView.from_response(part) for part in response.content_parts]
122
+ return items, response.total_count
123
+
124
+ def _format_row(self, item: ContentPartView) -> tuple[str, ...]:
125
+ """Format a content part as a table row."""
126
+ tags_dicts = [{"tag_path": t.tag_path} for t in item.tags]
127
+ return (
128
+ item.content_type_id,
129
+ self._format_content_preview(item),
130
+ format_tags_list(tags_dicts, max_display=2),
131
+ self._format_model_info(item),
132
+ format_datetime(item.updated_at),
133
+ )
134
+
135
+ def _format_content_preview(self, item: ContentPartView) -> str:
136
+ """Format content for table display."""
137
+ if item.content_text:
138
+ text = item.content_text.replace("\n", " ").strip()
139
+ return truncate_text(text, max_length=40)
140
+
141
+ if item.content_structured:
142
+ try:
143
+ json_str = json.dumps(item.content_structured, ensure_ascii=False)
144
+ return truncate_text(json_str, max_length=40)
145
+ except (TypeError, ValueError):
146
+ return "{...}"
147
+
148
+ return "-"
149
+
150
+ def _format_model_info(self, item: ContentPartView) -> str:
151
+ """Format model info for table display."""
152
+ if not item.execution_metadata:
153
+ return "-"
154
+
155
+ provider = item.execution_metadata.provider_key
156
+ model = item.execution_metadata.provider_model_name
157
+
158
+ if provider and model:
159
+ if len(model) > 15:
160
+ model = model[:12] + "..."
161
+ return f"{provider}/{model}"
162
+ elif model:
163
+ return model[:15] if len(model) > 15 else model
164
+ elif provider:
165
+ return provider
166
+ return "-"
167
+
168
+ def _update_preview(self, item: ContentPartView) -> None:
169
+ """Update the preview pane with content details."""
170
+ preview = self.query_one("#preview-content", Static)
171
+
172
+ storage = "structured" if item.content_structured else "text"
173
+ created_at = format_datetime(item.created_at)
174
+
175
+ if item.content_structured:
176
+ try:
177
+ content_display = json.dumps(item.content_structured, indent=2)
178
+ except (TypeError, ValueError):
179
+ content_display = str(item.content_structured)
180
+ elif item.content_text:
181
+ content_display = item.content_text
182
+ else:
183
+ content_display = "(empty)"
184
+
185
+ tags_display = ", ".join(t.tag_path for t in item.tags) if item.tags else "-"
186
+
187
+ if item.execution_metadata:
188
+ model_info = (
189
+ f"{item.execution_metadata.provider_key}/"
190
+ f"{item.execution_metadata.provider_model_name}"
191
+ )
192
+ else:
193
+ model_info = "-"
194
+
195
+ # Display external_id prominently if present
196
+ external_id_display = item.external_id or "-"
197
+
198
+ preview_text = f"""[bold]External ID:[/] {external_id_display}
199
+ [bold]ID:[/] {item.id}
200
+ [bold]Type:[/] {item.content_type_id}
201
+ [bold]Storage:[/] {storage}
202
+ [bold]Model:[/] {model_info}
203
+ [bold]Tags:[/] {tags_display}
204
+ [bold]Created:[/] {created_at}
205
+
206
+ [bold]Content:[/]
207
+ {content_display}"""
208
+
209
+ preview.update(preview_text)
210
+
211
+ # =========================================================================
212
+ # Public methods for parent Screen to call
213
+ # =========================================================================
214
+
215
+ def copy_content_id(self) -> None:
216
+ """Copy the selected content ID to clipboard.
217
+
218
+ Prefers external_id if available, otherwise falls back to the internal UUID.
219
+ """
220
+ part = self.get_selected_item()
221
+ if not part:
222
+ self.app.notify("No content selected", severity="warning")
223
+ return
224
+
225
+ # Prefer external_id, fallback to internal id
226
+ if part.external_id:
227
+ copy_to_clipboard(self.app, part.external_id, "External ID")
228
+ else:
229
+ copy_to_clipboard(self.app, str(part.id), "ID")
230
+
231
+ def copy_content(self) -> None:
232
+ """Copy the selected content to clipboard."""
233
+ part = self.get_selected_item()
234
+ if not part:
235
+ self.app.notify("No content to copy", severity="warning")
236
+ return
237
+
238
+ if part.content_structured:
239
+ try:
240
+ content = json.dumps(
241
+ part.content_structured, indent=2, ensure_ascii=False
242
+ )
243
+ except (TypeError, ValueError):
244
+ content = str(part.content_structured)
245
+ elif part.content_text:
246
+ content = part.content_text
247
+ else:
248
+ self.app.notify("Content is empty", severity="warning")
249
+ return
250
+
251
+ preview_str = content[:50].replace("\n", " ")
252
+ if len(content) > 50:
253
+ preview_str += "..."
254
+ copy_to_clipboard(self.app, content, f"content: {preview_str}")
255
+
256
+ def manage_tags(self) -> None:
257
+ """Open tag management modal for selected content."""
258
+ part = self.get_selected_item()
259
+ if not part:
260
+ self.app.notify("No content to tag", severity="warning")
261
+ return
262
+
263
+ self._pending_tag_update_content_id = part.id
264
+
265
+ # Convert tags to dict format expected by modal
266
+ current_tags = [{"tag_path": t.tag_path} for t in part.tags]
267
+ self._show_tag_management_modal(str(part.id), current_tags)
268
+
269
+ def delete_content(self) -> None:
270
+ """Delete the currently selected content part."""
271
+ part = self.get_selected_item()
272
+ if not part:
273
+ self.app.notify("No content selected", severity="warning")
274
+ return
275
+
276
+ # Create a preview of the content for the confirmation message
277
+ if part.content_text:
278
+ preview = part.content_text[:50].replace("\n", " ")
279
+ if len(part.content_text) > 50:
280
+ preview += "..."
281
+ elif part.content_structured:
282
+ preview = f"[{part.content_type_id}]"
283
+ else:
284
+ preview = "(empty)"
285
+
286
+ self.app.push_screen(
287
+ ConfirmModal(
288
+ title="Delete Content",
289
+ message=f"Delete content '{preview}'? This cannot be undone.",
290
+ ),
291
+ lambda confirmed: self._on_delete_confirmed(confirmed, part.id),
292
+ )
293
+
294
+ def _on_delete_confirmed(self, confirmed: bool | None, content_id: str) -> None:
295
+ """Callback when delete confirmation modal is dismissed."""
296
+ if confirmed:
297
+ self._execute_delete(content_id)
298
+
299
+ @work(exclusive=True)
300
+ async def _execute_delete(self, content_id: str) -> None:
301
+ """Execute the delete operation."""
302
+ try:
303
+ client = await self._ensure_client()
304
+ response = await client.delete_content_part(UUID(content_id))
305
+
306
+ # Build notification message with cascade info
307
+ messages = ["Content deleted"]
308
+ if response.deleted_message_id:
309
+ messages.append("orphaned message removed")
310
+ if response.deleted_session_id:
311
+ messages.append("orphaned session removed")
312
+
313
+ self.app.notify(
314
+ ", ".join(messages),
315
+ severity="information",
316
+ )
317
+ # Refresh the content list
318
+ self._do_search(self._last_search)
319
+
320
+ except Exception as e:
321
+ self.app.notify(f"Failed to delete content: {e}", severity="error")
322
+
323
+ def open_in_editor(self) -> None:
324
+ """Open content in external editor (read-only view)."""
325
+ part = self.get_selected_item()
326
+ if not part:
327
+ self.app.notify("No content to open", severity="warning")
328
+ return
329
+
330
+ if part.content_structured:
331
+ try:
332
+ content = json.dumps(
333
+ part.content_structured, indent=2, ensure_ascii=False
334
+ )
335
+ suffix = ".json"
336
+ except (TypeError, ValueError):
337
+ content = str(part.content_structured)
338
+ suffix = ".txt"
339
+ elif part.content_text:
340
+ content = part.content_text
341
+ suffix = ".txt"
342
+ else:
343
+ self.app.notify("Content is empty", severity="warning")
344
+ return
345
+
346
+ editor = os.environ.get("EDITOR") or os.environ.get("VISUAL") or "vi"
347
+
348
+ try:
349
+ with tempfile.NamedTemporaryFile(
350
+ mode="w",
351
+ suffix=suffix,
352
+ prefix="content_",
353
+ delete=False,
354
+ encoding="utf-8",
355
+ ) as f:
356
+ f.write(content)
357
+ temp_path = f.name
358
+
359
+ with self.app.suspend():
360
+ subprocess.run([editor, temp_path], check=False)
361
+
362
+ try:
363
+ os.unlink(temp_path)
364
+ except OSError:
365
+ pass
366
+
367
+ self.app.notify("Editor closed", severity="information")
368
+
369
+ except Exception as e:
370
+ self.app.notify(f"Failed to open editor: {e}", severity="error")
371
+
372
+ # =========================================================================
373
+ # Internal async operations
374
+ # =========================================================================
375
+
376
+ @work(exclusive=True)
377
+ async def _show_tag_management_modal(
378
+ self, content_part_id: str, current_tags: list[dict[str, Any]]
379
+ ) -> None:
380
+ """Show the tag management modal (async to get client)."""
381
+ client = await self._ensure_client()
382
+ modal = TagManagementModal(
383
+ client=client,
384
+ content_part_id=content_part_id,
385
+ current_tags=current_tags,
386
+ )
387
+ self.app.push_screen(modal, self._on_tags_updated)
388
+
389
+ def _on_tags_updated(self, result: list[str] | None) -> None:
390
+ """Handle tag update modal result."""
391
+ if result is not None:
392
+ self._save_tags(result)
393
+
394
+ @work(exclusive=True)
395
+ async def _save_tags(self, tag_paths: list[str]) -> None:
396
+ """Save updated tags to the server."""
397
+ if not self._pending_tag_update_content_id:
398
+ self.app.notify("No content selected for tag update", severity="error")
399
+ return
400
+
401
+ content_id = UUID(self._pending_tag_update_content_id)
402
+
403
+ try:
404
+ client = await self._ensure_client()
405
+ request = UpdateContentPartRequest(
406
+ content_text=None, content_structured=None, tag_paths=tag_paths
407
+ )
408
+ await client.update_content_part(content_id, request)
409
+
410
+ self.app.notify("Tags updated successfully", severity="information")
411
+ self._do_search(self._last_search)
412
+
413
+ except Exception as e:
414
+ self.app.notify(f"Failed to update tags: {e}", severity="error")
415
+ finally:
416
+ self._pending_tag_update_content_id = None
417
+
418
+
419
+ class ContentScreen(NavScreen):
420
+ """Screen for browsing and managing content parts.
421
+
422
+ This is a proper Textual Screen that wraps the ContentWidget
423
+ and provides keybindings that work regardless of focus.
424
+ """
425
+
426
+ app: "AlloyRuntimeApp"
427
+
428
+ SCREEN_ID = "content"
429
+
430
+ DEFAULT_CSS = """
431
+ ContentScreen #screen-root {
432
+ height: 1fr;
433
+ }
434
+
435
+ ContentScreen .browser-widget {
436
+ height: 100%;
437
+ width: 100%;
438
+ }
439
+ """
440
+
441
+ # Screen-specific bindings (Ctrl+ prefixed)
442
+ BINDINGS = NavScreen.BINDINGS + [
443
+ Binding("ctrl+f", "focus_search", "Search", show=True),
444
+ Binding("ctrl+r", "refresh", "Refresh", show=True),
445
+ Binding("ctrl+y", "copy_content", "Copy Content", show=True),
446
+ Binding("ctrl+i", "copy_id", "Copy ID", show=True),
447
+ Binding("ctrl+t", "manage_tags", "Tags", show=True),
448
+ Binding("ctrl+e", "open_in_editor", "View", show=True),
449
+ Binding("ctrl+d", "delete_content", "Delete", show=True),
450
+ Binding("escape", "focus_table", "Focus List", show=False),
451
+ Binding("enter", "copy_content", "Copy Content", show=False),
452
+ ]
453
+
454
+ def compose_content(self) -> ComposeResult:
455
+ """Compose the screen content."""
456
+ yield ContentWidget(id="content-widget")
457
+
458
+ def on_screen_resume(self) -> None:
459
+ """Focus the data table when screen becomes active."""
460
+ self.call_after_refresh(self._focus_table)
461
+
462
+ def _focus_table(self) -> None:
463
+ """Focus the data table."""
464
+ try:
465
+ widget = self.query_one("#content-widget", ContentWidget)
466
+ widget.focus_table()
467
+ except Exception:
468
+ pass
469
+
470
+ def _get_widget(self) -> ContentWidget:
471
+ """Get the content widget."""
472
+ return self.query_one("#content-widget", ContentWidget)
473
+
474
+ # =========================================================================
475
+ # Actions (keybinding handlers)
476
+ # =========================================================================
477
+
478
+ def action_focus_search(self) -> None:
479
+ """Focus the search input."""
480
+ self._get_widget().focus_search()
481
+
482
+ def action_focus_table(self) -> None:
483
+ """Focus the data table."""
484
+ self._get_widget().focus_table()
485
+
486
+ def action_refresh(self) -> None:
487
+ """Refresh the current search results."""
488
+ self._get_widget().refresh_data()
489
+
490
+ def action_copy_id(self) -> None:
491
+ """Copy the content ID to clipboard."""
492
+ self._get_widget().copy_content_id()
493
+
494
+ def action_copy_content(self) -> None:
495
+ """Copy the content to clipboard."""
496
+ self._get_widget().copy_content()
497
+
498
+ def action_manage_tags(self) -> None:
499
+ """Open tag management modal."""
500
+ self._get_widget().manage_tags()
501
+
502
+ def action_open_in_editor(self) -> None:
503
+ """Open content in external editor."""
504
+ self._get_widget().open_in_editor()
505
+
506
+ def action_delete_content(self) -> None:
507
+ """Delete the selected content."""
508
+ self._get_widget().delete_content()
@@ -0,0 +1,89 @@
1
+ """Dashboard screen for quick navigation.
2
+
3
+ A minimal, keyboard-first landing screen that allows instant navigation
4
+ with single keystrokes. Shown on app launch and accessible via keybinding.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ from textual.app import ComposeResult
10
+ from textual.binding import Binding
11
+ from textual.containers import Container
12
+ from textual.screen import Screen
13
+ from textual.widgets import Static
14
+
15
+ if TYPE_CHECKING:
16
+ from cli.tui.app import AlloyRuntimeApp
17
+
18
+
19
+ DASHBOARD_CONTENT = r"""
20
+ _ ___ ____ _ _ _
21
+ / \ |_ _| / ___| |__ __ _| |_| |_ ___ _ __
22
+ / _ \ | | | | | '_ \ / _` | __| __/ _ \ '__|
23
+ / ___ \ | | | |___| | | | (_| | |_| || __/ |
24
+ /_/ \_\___| \____|_| |_|\__,_|\__|\__\___|_|
25
+
26
+
27
+ [C] Chat start a conversation
28
+ [A] Agents browse & manage
29
+ [T] Templates prompt library
30
+ [M] Content media objects
31
+ [S] Schemas output structures
32
+
33
+ [?] help [Q] quit
34
+ """
35
+
36
+
37
+ class DashboardScreen(Screen[str]):
38
+ """Quick-launch dashboard for instant navigation.
39
+
40
+ Displays on app launch and allows single-keystroke navigation to any
41
+ main section. Returns the selected screen ID when dismissed.
42
+ """
43
+
44
+ app: "AlloyRuntimeApp"
45
+
46
+ BINDINGS = [
47
+ Binding("c", "goto('chat')", "Chat", show=False),
48
+ Binding("a", "goto('agents')", "Agents", show=False),
49
+ Binding("t", "goto('templates')", "Templates", show=False),
50
+ Binding("m", "goto('content')", "Content", show=False),
51
+ Binding("s", "goto('schemas')", "Schemas", show=False),
52
+ Binding("q", "quit", "Quit", show=False),
53
+ Binding("question_mark", "show_help", "Help", show=False),
54
+ ]
55
+
56
+ DEFAULT_CSS = """
57
+ DashboardScreen {
58
+ align: center middle;
59
+ }
60
+
61
+ DashboardScreen > Container {
62
+ width: auto;
63
+ height: auto;
64
+ }
65
+
66
+ DashboardScreen Static {
67
+ width: auto;
68
+ height: auto;
69
+ text-align: center;
70
+ }
71
+ """
72
+
73
+ def compose(self) -> ComposeResult:
74
+ with Container():
75
+ yield Static(DASHBOARD_CONTENT, id="dashboard-content")
76
+
77
+ def action_goto(self, screen_id: str) -> None:
78
+ """Navigate to a screen by dismissing with the screen ID."""
79
+ self.dismiss(screen_id)
80
+
81
+ def action_quit(self) -> None:
82
+ """Quit the application."""
83
+ self.app.exit()
84
+
85
+ def action_show_help(self) -> None:
86
+ """Show help modal."""
87
+ from cli.tui.widgets.help_modal import HelpModal
88
+
89
+ self.app.push_screen(HelpModal())
@@ -0,0 +1,96 @@
1
+ """Pydantic models for TUI screen data types.
2
+
3
+ This module contains view models used by screens to represent
4
+ data in a structured, type-safe manner.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Any, cast
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ def _tag_list_factory() -> list["TagView"]:
14
+ return []
15
+
16
+
17
+ class TagView(BaseModel):
18
+ """View model for a tag."""
19
+
20
+ tag_path: str
21
+
22
+
23
+ class ExecutionMetadataView(BaseModel):
24
+ """View model for execution metadata."""
25
+
26
+ external_id: str | None = None
27
+ provider_key: str = ""
28
+ provider_model_name: str = ""
29
+
30
+
31
+ class ContentPartView(BaseModel):
32
+ """View model for content parts in the Content browser.
33
+
34
+ This provides a flattened, UI-friendly representation of
35
+ ContentPartItemResponse for use in the ContentScreen.
36
+ """
37
+
38
+ id: str = Field(description="Content part UUID as string")
39
+ content_type_id: str = Field(description="Content type (text, code, json, etc.)")
40
+ content_text: str | None = Field(
41
+ default=None, description="Plain text content (null for structured/media types)"
42
+ )
43
+ content_structured: dict[str, Any] | None = Field(
44
+ default=None, description="Structured JSON content (null for text types)"
45
+ )
46
+ tags: list[TagView] = Field(
47
+ default_factory=_tag_list_factory,
48
+ description="Associated tags",
49
+ )
50
+ execution_metadata: ExecutionMetadataView | None = Field(
51
+ default=None, description="Execution metadata"
52
+ )
53
+ created_at: datetime | None = Field(default=None, description="Creation timestamp")
54
+ updated_at: datetime | None = Field(
55
+ default=None, description="Last update timestamp"
56
+ )
57
+
58
+ @property
59
+ def external_id(self) -> str | None:
60
+ """Get the external_id from execution metadata if available."""
61
+ if self.execution_metadata:
62
+ return self.execution_metadata.external_id
63
+ return None
64
+
65
+ @classmethod
66
+ def from_response(cls, part: Any) -> "ContentPartView":
67
+ """Create a ContentPartView from a ContentPartItemResponse.
68
+
69
+ Args:
70
+ part: A ContentPartItemResponse from the server client
71
+
72
+ Returns:
73
+ A ContentPartView instance
74
+ """
75
+ raw_tags = cast(list[Any], part.tags or [])
76
+ tags: list[TagView] = []
77
+ for tag in raw_tags:
78
+ tags.append(TagView(tag_path=cast(str, tag.tag_path)))
79
+ exec_meta = None
80
+ if part.execution_metadata:
81
+ exec_meta = ExecutionMetadataView(
82
+ external_id=part.execution_metadata.external_id,
83
+ provider_key=part.execution_metadata.provider_key,
84
+ provider_model_name=part.execution_metadata.provider_model_name,
85
+ )
86
+
87
+ return cls(
88
+ id=str(part.id),
89
+ content_type_id=part.content_type_id,
90
+ content_text=part.content_text,
91
+ content_structured=part.content_structured,
92
+ tags=tags,
93
+ execution_metadata=exec_meta,
94
+ created_at=part.created_at,
95
+ updated_at=part.updated_at,
96
+ )