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,223 @@
1
+ """Textual Messages for inter-widget communication in the chat module.
2
+
3
+ These messages enable loose coupling between widgets. Child widgets post
4
+ messages that bubble up to parents, allowing the ChatScreen orchestrator
5
+ to coordinate actions without tight coupling.
6
+ """
7
+
8
+ from uuid import UUID
9
+
10
+ from textual.message import Message
11
+
12
+ from alloy_runtime_types.dtos.sessions import MessageResponse, SessionSummary
13
+
14
+
15
+ # =============================================================================
16
+ # Session Events
17
+ # =============================================================================
18
+
19
+
20
+ class SessionSelected(Message):
21
+ """User selected a session from the sidebar."""
22
+
23
+ def __init__(self, session_id: UUID) -> None:
24
+ self.session_id = session_id
25
+ super().__init__()
26
+
27
+
28
+ class SessionCreated(Message):
29
+ """A new session was successfully created."""
30
+
31
+ def __init__(
32
+ self,
33
+ session_id: UUID,
34
+ agent_id: UUID | None,
35
+ agent_name: str | None,
36
+ ) -> None:
37
+ self.session_id = session_id
38
+ self.agent_id = agent_id
39
+ self.agent_name = agent_name
40
+ super().__init__()
41
+
42
+
43
+ class SessionsLoaded(Message):
44
+ """Sessions list was loaded/refreshed."""
45
+
46
+ def __init__(self, sessions: list[SessionSummary]) -> None:
47
+ self.sessions = sessions
48
+ super().__init__()
49
+
50
+
51
+ class SessionsLoadError(Message):
52
+ """Failed to load sessions."""
53
+
54
+ def __init__(self, error: str) -> None:
55
+ self.error = error
56
+ super().__init__()
57
+
58
+
59
+ # =============================================================================
60
+ # Message Events
61
+ # =============================================================================
62
+
63
+
64
+ class MessagesLoaded(Message):
65
+ """Messages for current session were loaded."""
66
+
67
+ def __init__(self, messages: list[MessageResponse]) -> None:
68
+ self.messages = messages
69
+ super().__init__()
70
+
71
+
72
+ class SendMessageRequested(Message):
73
+ """User wants to send a message."""
74
+
75
+ def __init__(self, content: str) -> None:
76
+ self.content = content
77
+ super().__init__()
78
+
79
+
80
+ class RegenerateRequested(Message):
81
+ """User wants to regenerate the last response."""
82
+
83
+ pass
84
+
85
+
86
+ class UndoRequested(Message):
87
+ """User wants to undo the last conversation turn."""
88
+
89
+ pass
90
+
91
+
92
+ # =============================================================================
93
+ # Streaming Events
94
+ # =============================================================================
95
+
96
+
97
+ class StreamingStarted(Message):
98
+ """Response streaming has begun."""
99
+
100
+ def __init__(self, execution_id: UUID) -> None:
101
+ self.execution_id = execution_id
102
+ super().__init__()
103
+
104
+
105
+ class StreamingChunk(Message):
106
+ """A chunk of streamed content received."""
107
+
108
+ def __init__(self, content: str, is_reasoning: bool = False) -> None:
109
+ self.content = content
110
+ self.is_reasoning = is_reasoning
111
+ super().__init__()
112
+
113
+
114
+ class StreamingCompleted(Message):
115
+ """Response streaming finished successfully."""
116
+
117
+ def __init__(self, full_content: str) -> None:
118
+ self.full_content = full_content
119
+ super().__init__()
120
+
121
+
122
+ class StreamingError(Message):
123
+ """Error occurred during streaming."""
124
+
125
+ def __init__(self, error: str) -> None:
126
+ self.error = error
127
+ super().__init__()
128
+
129
+
130
+ # =============================================================================
131
+ # UI Events
132
+ # =============================================================================
133
+
134
+
135
+ class FocusInputRequested(Message):
136
+ """Request to focus the chat input."""
137
+
138
+ pass
139
+
140
+
141
+ class FocusSessionSearchRequested(Message):
142
+ """Request to focus the session search input."""
143
+
144
+ pass
145
+
146
+
147
+ class CopyToClipboardRequested(Message):
148
+ """Request to copy content to clipboard."""
149
+
150
+ def __init__(self, content: str, label: str = "Content") -> None:
151
+ self.content = content
152
+ self.label = label
153
+ super().__init__()
154
+
155
+
156
+ class NewSessionRequested(Message):
157
+ """User wants to create a new session."""
158
+
159
+ pass
160
+
161
+
162
+ class OpenEditorRequested(Message):
163
+ """User wants to open the current input in $EDITOR."""
164
+
165
+ def __init__(self, current_text: str) -> None:
166
+ self.current_text = current_text
167
+ super().__init__()
168
+
169
+
170
+ class EditorContentReturned(Message):
171
+ """Content was returned from the external editor."""
172
+
173
+ def __init__(self, content: str) -> None:
174
+ self.content = content
175
+ super().__init__()
176
+
177
+
178
+ # =============================================================================
179
+ # Slash Command Events
180
+ # =============================================================================
181
+
182
+
183
+ class SlashCommandExecuted(Message):
184
+ """A slash command was executed by the user.
185
+
186
+ Some commands are handled directly (e.g., /clear, /copy), while others
187
+ signal actions to be performed by the screen (e.g., /new, /undo).
188
+ """
189
+
190
+ def __init__(self, command_name: str, args: str = "") -> None:
191
+ self.command_name = command_name
192
+ self.args = args
193
+ super().__init__()
194
+
195
+
196
+ class UnknownSlashCommand(Message):
197
+ """User entered an unknown slash command."""
198
+
199
+ def __init__(self, command_text: str) -> None:
200
+ self.command_text = command_text
201
+ super().__init__()
202
+
203
+
204
+ # =============================================================================
205
+ # Welcome Screen Events
206
+ # =============================================================================
207
+
208
+
209
+ class AgentQuickSelected(Message):
210
+ """User selected an agent from quick start list for immediate session creation."""
211
+
212
+ def __init__(self, agent_id: UUID, agent_name: str) -> None:
213
+ self.agent_id = agent_id
214
+ self.agent_name = agent_name
215
+ super().__init__()
216
+
217
+
218
+ class RecentSessionSelected(Message):
219
+ """User selected a recent session from the welcome screen."""
220
+
221
+ def __init__(self, session_id: UUID) -> None:
222
+ self.session_id = session_id
223
+ super().__init__()
cli/tui/chat/pane.py ADDED
@@ -0,0 +1,141 @@
1
+ """ChatPane - an individual chat pane with its own session/model.
2
+
3
+ This widget encapsulates the main chat area (header, messages, input) and can be
4
+ used standalone or in a split-pane layout for comparing different models.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from textual.app import ComposeResult
10
+ from textual.containers import Vertical
11
+ from textual.widget import Widget
12
+
13
+ from cli.tui.chat.store import ChatStore
14
+ from cli.tui.chat.widgets.chat_header import ChatHeader
15
+ from cli.tui.chat.widgets.chat_input import ChatInput
16
+ from cli.tui.chat.widgets.injection_popup import InjectionSuggestionPopup
17
+ from cli.tui.chat.widgets.message_display import MessageDisplay
18
+
19
+ if TYPE_CHECKING:
20
+ pass
21
+
22
+
23
+ class ChatPane(Widget):
24
+ """Individual chat pane with its own session/model.
25
+
26
+ This widget contains the main chat interface components:
27
+ - ChatHeader: Shows current session/model info
28
+ - MessageDisplay: Shows message history and streaming content
29
+ - ChatInput: Text input for composing messages
30
+
31
+ Each pane has its own ChatStore, allowing independent sessions
32
+ or the same session with different views.
33
+
34
+ Usage:
35
+ # Single pane
36
+ pane = ChatPane(store=my_store, pane_id="main")
37
+
38
+ # Split panes for comparison
39
+ left_pane = ChatPane(store=left_store, pane_id="left")
40
+ right_pane = ChatPane(store=right_store, pane_id="right")
41
+ """
42
+
43
+ DEFAULT_CSS = """
44
+ ChatPane {
45
+ width: 1fr;
46
+ height: 100%;
47
+ }
48
+
49
+ ChatPane.focused-pane {
50
+ border: tall $accent;
51
+ }
52
+
53
+ ChatPane.unfocused-pane {
54
+ border: tall $surface-darken-2;
55
+ }
56
+
57
+ ChatPane #pane-container {
58
+ height: 100%;
59
+ }
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ store: ChatStore,
65
+ pane_id: str = "main",
66
+ show_border: bool = False,
67
+ **kwargs: Any,
68
+ ) -> None:
69
+ """Initialize the chat pane.
70
+
71
+ Args:
72
+ store: ChatStore instance for state management.
73
+ pane_id: Unique identifier for this pane (e.g., "left", "right", "main").
74
+ show_border: Whether to show a border around the pane (for split view).
75
+ """
76
+ super().__init__(**kwargs)
77
+ self._store = store
78
+ self._pane_id = pane_id
79
+ self._show_border = show_border
80
+
81
+ @property
82
+ def pane_id(self) -> str:
83
+ """Get the pane identifier."""
84
+ return self._pane_id
85
+
86
+ @property
87
+ def store(self) -> ChatStore:
88
+ """Get the pane's ChatStore."""
89
+ return self._store
90
+
91
+ def compose(self) -> ComposeResult:
92
+ """Compose the pane layout."""
93
+ with Vertical(id="pane-container"):
94
+ yield ChatHeader(self._store)
95
+ yield MessageDisplay(self._store)
96
+ # Popup is positioned here (above ChatInput) so it can float over messages
97
+ yield InjectionSuggestionPopup(id="injection-popup")
98
+ yield ChatInput(self._store)
99
+
100
+ def on_mount(self) -> None:
101
+ """Apply border styling based on configuration."""
102
+ if self._show_border:
103
+ self.add_class("unfocused-pane")
104
+
105
+ def set_focused(self, focused: bool) -> None:
106
+ """Update the pane's focused state (visual indicator).
107
+
108
+ Args:
109
+ focused: Whether this pane is the active/focused pane.
110
+ """
111
+ if not self._show_border:
112
+ return
113
+
114
+ if focused:
115
+ self.remove_class("unfocused-pane")
116
+ self.add_class("focused-pane")
117
+ else:
118
+ self.remove_class("focused-pane")
119
+ self.add_class("unfocused-pane")
120
+
121
+ def set_show_border(self, show_border: bool) -> None:
122
+ """Enable or disable border styling for the pane."""
123
+ self._show_border = show_border
124
+ if not show_border:
125
+ self.remove_class("focused-pane")
126
+ self.remove_class("unfocused-pane")
127
+
128
+ def focus_input(self) -> None:
129
+ """Focus the chat input in this pane."""
130
+ chat_input = self.query_one(ChatInput)
131
+ chat_input.focus_input()
132
+
133
+ def clear_input(self) -> None:
134
+ """Clear the chat input in this pane."""
135
+ chat_input = self.query_one(ChatInput)
136
+ chat_input.clear_input()
137
+
138
+ def get_input_text(self) -> str:
139
+ """Get the current text from the chat input."""
140
+ chat_input = self.query_one(ChatInput)
141
+ return chat_input.get_text()
File without changes
@@ -0,0 +1,72 @@
1
+ """Base utilities for message renderers.
2
+
3
+ Provides common functionality used by all renderer implementations.
4
+ """
5
+
6
+ from alloy_runtime_types.dtos.sessions import MessageResponse
7
+
8
+ from cli.tui.chat.types import (
9
+ extract_message_text,
10
+ extract_message_text_for_display,
11
+ format_time,
12
+ )
13
+
14
+
15
+ def format_user_header(msg: MessageResponse, *, include_time: bool = True) -> str:
16
+ """Format a user message header.
17
+
18
+ Args:
19
+ msg: The message to format.
20
+ include_time: Whether to include the timestamp.
21
+
22
+ Returns:
23
+ Formatted header string.
24
+ """
25
+ if include_time:
26
+ return f"You [{format_time(msg.created_at)}]:"
27
+ return "You:"
28
+
29
+
30
+ def format_assistant_header(msg: MessageResponse, *, include_time: bool = True) -> str:
31
+ """Format an assistant message header.
32
+
33
+ Args:
34
+ msg: The message to format.
35
+ include_time: Whether to include the timestamp.
36
+
37
+ Returns:
38
+ Formatted header string.
39
+ """
40
+ if include_time:
41
+ return f"Assistant [{format_time(msg.created_at)}]:"
42
+ return "Assistant:"
43
+
44
+
45
+ def get_message_body(msg: MessageResponse) -> str:
46
+ """Extract the body text from a message.
47
+
48
+ Args:
49
+ msg: The message to extract text from.
50
+
51
+ Returns:
52
+ Message body text.
53
+ """
54
+ return extract_message_text(msg)
55
+
56
+
57
+ def get_message_body_for_display(msg: MessageResponse) -> tuple[str, bool]:
58
+ """Extract the body text from a message, truncating long content.
59
+
60
+ Long content (e.g., from @fragment() injections) is truncated to prevent
61
+ overwhelming the TUI. The first/last 50 chars are shown with a truncation
62
+ indicator.
63
+
64
+ Args:
65
+ msg: The message to extract text from.
66
+
67
+ Returns:
68
+ Tuple of (body_text, was_truncated)
69
+ - body_text: The message body, possibly truncated
70
+ - was_truncated: True if any content was truncated
71
+ """
72
+ return extract_message_text_for_display(msg)
@@ -0,0 +1,250 @@
1
+ """Markdown renderer for chat messages.
2
+
3
+ Renders messages with Rich markdown formatting for display in Textual widgets.
4
+ Uses Rich's Markdown class to properly render markdown syntax (headers, code blocks,
5
+ lists, etc.) in assistant responses.
6
+ """
7
+
8
+ import re
9
+ from datetime import datetime
10
+
11
+ from rich.console import Group, RenderableType
12
+ from rich.markdown import Markdown
13
+ from rich.text import Text
14
+
15
+ from alloy_runtime_types.dtos.sessions import MessageResponse
16
+
17
+ from cli.tui.chat.renderers.base import (
18
+ get_message_body,
19
+ get_message_body_for_display,
20
+ )
21
+ from cli.tui.chat.types import extract_thinking_content, format_time
22
+
23
+ # Spinner frames for streaming indicator
24
+ SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
25
+
26
+ # Color for thinking/reasoning blocks
27
+ THINKING_COLOR = "#bb9af7" # tokyo-night purple accent
28
+
29
+ # Color for truncated content indicator
30
+ TRUNCATED_COLOR = "#f7768e" # tokyo-night red/pink for attention
31
+
32
+
33
+ class MarkdownRenderer:
34
+ """Renders messages with Rich markdown formatting.
35
+
36
+ Features:
37
+ - User messages with muted styling
38
+ - Assistant messages rendered as proper markdown (code blocks, headers, etc.)
39
+ - Agent/model name in header
40
+ - Timestamps on each message
41
+ - Spinner during streaming
42
+ - Optimistic user message display
43
+ - Thinking/reasoning blocks with distinctive styling
44
+
45
+ This is the default renderer for the chat interface. It uses Rich's Markdown
46
+ class to properly render markdown syntax in assistant responses.
47
+ """
48
+
49
+ def __init__(self) -> None:
50
+ self._spinner_frame = 0
51
+
52
+ def render(
53
+ self,
54
+ messages: tuple[MessageResponse, ...],
55
+ streaming_content: str = "",
56
+ streaming_thinking_content: str = "",
57
+ pending_user_message: str | None = None,
58
+ assistant_name: str = "Assistant",
59
+ is_streaming: bool = False,
60
+ thinking_collapsed: bool = False,
61
+ ) -> Group:
62
+ """Render messages to a Rich Group with proper markdown formatting.
63
+
64
+ Args:
65
+ messages: Tuple of message responses to render.
66
+ streaming_content: Partial content being streamed (appended to end).
67
+ streaming_thinking_content: Partial thinking/reasoning content being streamed.
68
+ pending_user_message: User message shown optimistically before server confirms.
69
+ assistant_name: Name to display for assistant (agent name or model).
70
+ is_streaming: Whether currently streaming a response.
71
+ thinking_collapsed: Whether thinking blocks should be collapsed.
72
+
73
+ Returns:
74
+ Rich Group containing formatted renderables suitable for display.
75
+ """
76
+ renderables: list[RenderableType] = []
77
+
78
+ # Colors: user messages in dim/muted, assistant in accent color
79
+ # Using colors that work well with tokyo-night theme
80
+ user_color = "dim white"
81
+ assistant_color = "#7aa2f7" # tokyo-night blue accent
82
+
83
+ for msg in messages:
84
+ time_str = format_time(msg.created_at)
85
+
86
+ if msg.role == "user":
87
+ # Only truncate user messages (template injections are user-initiated)
88
+ body, was_truncated = get_message_body_for_display(msg)
89
+ header = Text(f"You [{time_str}]:", style=f"bold {user_color}")
90
+ renderables.append(header)
91
+ # Style truncated content differently
92
+ if was_truncated:
93
+ renderables.append(self._style_truncated_content(body))
94
+ else:
95
+ renderables.append(Text(body))
96
+ else:
97
+ # Never truncate assistant messages - show full response
98
+ body = self._get_full_message_body(msg)
99
+
100
+ # For assistant messages, check for thinking content
101
+ thinking_content = extract_thinking_content(msg)
102
+ if thinking_content:
103
+ # Render thinking block (not streaming, use collapsed setting)
104
+ renderables.append(
105
+ self._render_thinking_block_renderable(
106
+ thinking_content,
107
+ is_streaming=False,
108
+ collapsed=thinking_collapsed,
109
+ )
110
+ )
111
+ renderables.append(Text(""))
112
+
113
+ header = Text(
114
+ f"{assistant_name} [{time_str}]:", style=f"bold {assistant_color}"
115
+ )
116
+ renderables.append(header)
117
+ # Render assistant body as proper markdown
118
+ renderables.append(Markdown(body))
119
+
120
+ renderables.append(Text(""))
121
+
122
+ # Show pending user message (optimistic UI)
123
+ if pending_user_message:
124
+ now_str = datetime.now().strftime("%H:%M")
125
+ header = Text(f"You [{now_str}]:", style=f"bold {user_color}")
126
+ renderables.append(header)
127
+ renderables.append(Text(pending_user_message))
128
+ renderables.append(Text(""))
129
+
130
+ # Show streaming thinking/reasoning block
131
+ if streaming_thinking_content:
132
+ renderables.append(
133
+ self._render_thinking_block_renderable(
134
+ streaming_thinking_content, is_streaming, thinking_collapsed
135
+ )
136
+ )
137
+ renderables.append(Text(""))
138
+
139
+ # Show streaming response with spinner
140
+ if is_streaming or streaming_content:
141
+ spinner = self._get_spinner() if is_streaming else ""
142
+ if streaming_content:
143
+ header = Text()
144
+ header.append(f"{assistant_name}:", style=f"bold {assistant_color}")
145
+ header.append(f" {spinner}", style="yellow")
146
+ renderables.append(header)
147
+ # Render streaming content as markdown too
148
+ renderables.append(Markdown(streaming_content))
149
+ elif not streaming_thinking_content:
150
+ # Only show "Thinking..." if we don't have actual thinking content
151
+ header = Text()
152
+ header.append(f"{assistant_name}:", style=f"bold {assistant_color}")
153
+ header.append(f" {spinner}", style="yellow")
154
+ renderables.append(header)
155
+ renderables.append(Text("Waiting for response...", style="dim"))
156
+
157
+ return Group(*renderables)
158
+
159
+ def _render_thinking_block_renderable(
160
+ self,
161
+ content: str,
162
+ is_streaming: bool = False,
163
+ collapsed: bool = False,
164
+ ) -> Text:
165
+ """Render a thinking/reasoning block with distinctive styling.
166
+
167
+ Args:
168
+ content: The thinking content to render.
169
+ is_streaming: Whether the thinking is still streaming.
170
+ collapsed: Whether to show collapsed view.
171
+
172
+ Returns:
173
+ Rich Text object for the thinking block.
174
+ """
175
+ spinner = self._get_spinner() if is_streaming else ""
176
+
177
+ if collapsed:
178
+ # Show collapsed indicator with line count
179
+ line_count = content.count("\n") + 1
180
+ result = Text()
181
+ result.append(
182
+ f"◆ Thinking ({line_count} lines)... {spinner}", style=THINKING_COLOR
183
+ )
184
+ result.append(" (expand with /thinking)", style="dim")
185
+ return result
186
+
187
+ # Full thinking block with border styling
188
+ lines: list[str] = []
189
+ lines.append(f"◆ Thinking {spinner}")
190
+
191
+ # Indent and dim the thinking content
192
+ for line in content.split("\n"):
193
+ lines.append(f"│ {line}")
194
+
195
+ result = Text()
196
+ result.append(lines[0] + "\n", style=f"bold {THINKING_COLOR}")
197
+ for line in lines[1:]:
198
+ result.append(line + "\n", style="dim italic")
199
+
200
+ return result
201
+
202
+ def _get_spinner(self) -> str:
203
+ """Get the current spinner frame and advance to next."""
204
+ frame = SPINNER_FRAMES[self._spinner_frame % len(SPINNER_FRAMES)]
205
+ self._spinner_frame += 1
206
+ return frame
207
+
208
+ def _get_full_message_body(self, msg: MessageResponse) -> str:
209
+ """Get the full message body without truncation.
210
+
211
+ Used for assistant messages which should never be truncated.
212
+
213
+ Args:
214
+ msg: The message to extract body from
215
+
216
+ Returns:
217
+ Full message body text
218
+ """
219
+ return get_message_body(msg)
220
+
221
+ def _style_truncated_content(self, body: str) -> Text:
222
+ """Style content that contains truncation indicators.
223
+
224
+ Highlights the truncation indicator (e.g., "[1,234 chars truncated]")
225
+ in a distinctive color so users can clearly see content was truncated.
226
+
227
+ Args:
228
+ body: The message body with truncation indicator(s)
229
+
230
+ Returns:
231
+ Rich Text object with styled truncation indicators
232
+ """
233
+ result = Text()
234
+ # Pattern to match truncation indicators like "... [1,234 chars truncated] ..."
235
+ pattern = r"(\.\.\. \[[\d,]+ chars truncated\] \.\.\.)"
236
+
237
+ last_end = 0
238
+ for match in re.finditer(pattern, body):
239
+ # Add text before the match
240
+ if match.start() > last_end:
241
+ result.append(body[last_end : match.start()])
242
+ # Add the truncation indicator with styling
243
+ result.append(match.group(1), style=f"bold {TRUNCATED_COLOR}")
244
+ last_end = match.end()
245
+
246
+ # Add remaining text after last match
247
+ if last_end < len(body):
248
+ result.append(body[last_end:])
249
+
250
+ return result