@pheem49/mint 1.17.0 → 1.18.0

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 (421) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +2 -2
  3. package/README.md +8 -2
  4. package/Release_Note.md +222 -71
  5. package/crates/mint-cli/src/agent/diff_render.rs +66 -44
  6. package/crates/mint-cli/src/agent/live_status.rs +21 -4
  7. package/crates/mint-cli/src/agent/markdown_render.rs +342 -5
  8. package/crates/mint-cli/src/agent.rs +25 -10
  9. package/crates/mint-cli/src/commands/agent.rs +4 -92
  10. package/crates/mint-cli/src/commands/browser.rs +2 -15
  11. package/crates/mint-cli/src/commands/integrations.rs +3 -3
  12. package/crates/mint-cli/src/commands/mod.rs +0 -3
  13. package/crates/mint-cli/src/commands/tailnet_web.rs +1 -0
  14. package/crates/mint-cli/src/interactive/chat_tui/dialog.rs +572 -0
  15. package/crates/mint-cli/src/interactive/chat_tui/mod.rs +1427 -0
  16. package/crates/mint-cli/src/interactive/chat_tui/render.rs +1524 -0
  17. package/crates/mint-cli/src/interactive/chat_tui/selection.rs +401 -0
  18. package/crates/mint-cli/src/interactive/chat_tui/state.rs +1833 -0
  19. package/crates/mint-cli/src/interactive/chat_tui/tests.rs +3414 -0
  20. package/crates/mint-cli/src/interactive/chat_tui/thought.rs +202 -0
  21. package/crates/mint-cli/src/interactive/chat_tui/transcript.rs +728 -0
  22. package/crates/mint-cli/src/interactive/chat_tui/watermark.rs +148 -0
  23. package/crates/mint-cli/src/interactive/command_ui.rs +53 -5
  24. package/crates/mint-cli/src/interactive/composer.rs +186 -0
  25. package/crates/mint-cli/src/interactive/format.rs +78 -3
  26. package/crates/mint-cli/src/interactive/input_box.rs +61 -1
  27. package/crates/mint-cli/src/interactive/resume_picker.rs +367 -25
  28. package/crates/mint-cli/src/interactive/slash_commands.rs +648 -371
  29. package/crates/mint-cli/src/interactive/tool_activity.rs +93 -20
  30. package/crates/mint-cli/src/interactive.rs +37 -0
  31. package/crates/mint-cli/src/main.rs +6 -3
  32. package/crates/mint-cli/src/markdown.rs +2 -37
  33. package/crates/mint-cli/src/onboard.rs +700 -282
  34. package/crates/mint-cli/src/setup.rs +1342 -624
  35. package/crates/mint-cli/src/terminal_theme.rs +159 -35
  36. package/crates/mint-cli/tests/library_intake.rs +1 -0
  37. package/crates/mint-core/src/agent/chat/anthropic.rs +318 -0
  38. package/crates/mint-core/src/agent/chat/custom.rs +149 -0
  39. package/crates/mint-core/src/agent/chat/gemini.rs +518 -0
  40. package/crates/mint-core/src/agent/chat/mod.rs +857 -0
  41. package/crates/mint-core/src/agent/chat/ollama.rs +321 -0
  42. package/crates/mint-core/src/agent/chat/openai.rs +582 -0
  43. package/crates/mint-core/src/agent/chat/streaming.rs +469 -0
  44. package/crates/mint-core/src/agent/chat/tests.rs +1479 -0
  45. package/crates/mint-core/src/agent/memory.rs +56 -2
  46. package/crates/mint-core/src/agent/tasks.rs +2 -2
  47. package/crates/mint-core/src/api_server/routes/chat.rs +24 -2
  48. package/crates/mint-core/src/api_server/routes/gemini_live.rs +14 -1
  49. package/crates/mint-core/src/api_server/routes/html_preview.rs +1 -8
  50. package/crates/mint-core/src/api_server/routes/sessions.rs +21 -0
  51. package/crates/mint-core/src/avatar_bridge.rs +0 -22
  52. package/crates/mint-core/src/browser/cdp.rs +10 -4
  53. package/crates/mint-core/src/browser/headless.rs +582 -0
  54. package/crates/mint-core/src/browser/input.rs +7 -62
  55. package/crates/mint-core/src/browser/interact.rs +0 -14
  56. package/crates/mint-core/src/browser/mod.rs +8 -29
  57. package/crates/mint-core/src/browser/navigate.rs +2 -36
  58. package/crates/mint-core/src/browser/runtime.rs +2 -4
  59. package/crates/mint-core/src/browser/session.rs +50 -466
  60. package/crates/mint-core/src/browser/workflow.rs +142 -32
  61. package/crates/mint-core/src/cron/store.rs +2 -2
  62. package/crates/mint-core/src/integrations/mcp.rs +6 -8
  63. package/crates/mint-core/src/lib.rs +0 -5
  64. package/crates/mint-core/src/media/gemini_live.rs +636 -86
  65. package/crates/mint-core/src/media/library/migration.rs +26 -0
  66. package/crates/mint-core/src/media/library/tests.rs +1 -1
  67. package/crates/mint-core/src/media/library/thumbnails.rs +8 -2
  68. package/crates/mint-core/src/media/pictures.rs +1 -1
  69. package/crates/mint-core/src/media/tool_images.rs +92 -1
  70. package/crates/mint-core/src/mods/manager.rs +101 -35
  71. package/crates/mint-core/src/orchestration/context_render.rs +441 -24
  72. package/crates/mint-core/src/orchestration/decision_parsing.rs +531 -16
  73. package/crates/mint-core/src/orchestration/full_skill_result_tests.rs +12 -0
  74. package/crates/mint-core/src/orchestration/loop_guard.rs +145 -0
  75. package/crates/mint-core/src/orchestration/mcp_outcome_tests.rs +864 -0
  76. package/crates/mint-core/src/orchestration/memory_skill.rs +88 -28
  77. package/crates/mint-core/src/orchestration/mod.rs +320 -4249
  78. package/crates/mint-core/src/orchestration/reliability.rs +67 -27
  79. package/crates/mint-core/src/orchestration/review_regressions.rs +5 -0
  80. package/crates/mint-core/src/orchestration/tests.rs +1665 -0
  81. package/crates/mint-core/src/orchestration/tools/dispatch.rs +967 -0
  82. package/crates/mint-core/src/orchestration/tools/files.rs +1 -0
  83. package/crates/mint-core/src/orchestration/tools/mod.rs +5 -1
  84. package/crates/mint-core/src/orchestration/tools/planning.rs +102 -42
  85. package/crates/mint-core/src/orchestration/tools/shell.rs +129 -58
  86. package/crates/mint-core/src/orchestration/truncation.rs +170 -13
  87. package/crates/mint-core/src/orchestration/types.rs +648 -0
  88. package/crates/mint-core/src/orchestration/verification.rs +0 -3
  89. package/crates/mint-core/src/orchestration/workspace_helpers.rs +24 -6
  90. package/crates/mint-core/src/prompts/agent.rs +52 -76
  91. package/crates/mint-core/src/prompts/tool_catalog.rs +8 -151
  92. package/crates/mint-core/src/search/files.rs +20 -0
  93. package/crates/mint-core/src/slash/mod.rs +228 -265
  94. package/crates/mint-core/src/slash/models.rs +6 -1
  95. package/crates/mint-core/src/system/bg_shell.rs +1 -0
  96. package/crates/mint-core/src/system/config.rs +176 -34
  97. package/crates/mint-mod-runtime/src/lib.rs +37 -23
  98. package/out/web/assets/ArtifactPreviewPanel-Cx6BVOOF.js +2 -0
  99. package/out/web/assets/CalculationCard-D2E74u5q.js +1 -0
  100. package/out/web/assets/CliSessionsView-ZbdtSjvK.css +1 -0
  101. package/out/web/assets/{CliSessionsView-MI8kXMAb.js → CliSessionsView-z5i6qGyb.js} +1 -1
  102. package/out/web/assets/CodeReviewPage-BK2bOScd.css +1 -0
  103. package/out/web/assets/CodeReviewPage-Dp6zHuHu.js +2 -0
  104. package/out/web/assets/{CommandPalette-BD8SlTE5.js → CommandPalette-DaKlzbKs.js} +1 -1
  105. package/out/web/assets/ImageGenCard-BMCJHP9O.js +1 -0
  106. package/out/web/assets/ImageSearchCard-Cld_SOUg.js +1 -0
  107. package/out/web/assets/{ImageStudioPanel-BP7N4rLr.js → ImageStudioPanel-B5N7TjeB.js} +1 -1
  108. package/out/web/assets/LibraryPage-CMB89rJ0.css +1 -0
  109. package/out/web/assets/LibraryPage-DbZEi4W_.js +2 -0
  110. package/out/web/assets/LinkedFoldersView-9B9DIPoZ.js +1 -0
  111. package/out/web/assets/{McpRegistryPicker-CoS83vtc.js → McpRegistryPicker-DnXPlzI7.js} +1 -1
  112. package/out/web/assets/McpServersView-CiNBNUl4.js +1 -0
  113. package/out/web/assets/MermaidCard-BBDNEXbY.js +2 -0
  114. package/out/web/assets/MintDashboard-C_UYPoNV.js +119 -0
  115. package/out/web/assets/MintDashboard-DTd8C_c8.css +1 -0
  116. package/out/web/assets/PlanCard-C2x5pajq.js +1 -0
  117. package/out/web/assets/{PluginsView-jBYmcKh4.js → PluginsView-DoTg9MVa.js} +1 -1
  118. package/out/web/assets/RewindModal-B5oowrR0.js +1 -0
  119. package/out/web/assets/{ScheduledTasksView-5uw9-V9H.js → ScheduledTasksView-BD5DVPkZ.js} +1 -1
  120. package/out/web/assets/{SelectField-DXBQtrUK.js → SelectField-Cy6CghrU.js} +1 -1
  121. package/out/web/assets/SettingsWindow-BYl_oCFh.js +4 -0
  122. package/out/web/assets/SettingsWindow-Ckcgnjt8.css +1 -0
  123. package/out/web/assets/{SkillsView-C-gp9J7m.js → SkillsView-CIeWwq5z.js} +1 -1
  124. package/out/web/assets/StockCard-DxUciKyp.js +1 -0
  125. package/out/web/assets/ToolSurfacePage-qBgs_VHq.css +1 -0
  126. package/out/web/assets/ToolSurfacePage-saVbVrkE.js +38 -0
  127. package/out/web/assets/{UiFeatureCard-6BwxTOCe.js → UiFeatureCard-CdY0hGWy.js} +1 -1
  128. package/out/web/assets/{UiGridCard-DvZcLNxb.js → UiGridCard-BBK3I5hC.js} +1 -1
  129. package/out/web/assets/UiMockupWidget-CpR9EDuc.js +1 -0
  130. package/out/web/assets/{VeoStudioPanel-DOj7_WR3.css → VeoStudioPanel-9kU9YLEN.css} +1 -1
  131. package/out/web/assets/{VeoStudioPanel-DfsOJKv0.js → VeoStudioPanel-Co6-wEWM.js} +2 -2
  132. package/out/web/assets/WeatherCard-Buzi7Vfu.js +1 -0
  133. package/out/web/assets/WorkspacePdfPreview-Bi8Zzyir.js +2 -0
  134. package/out/web/assets/{abnfDiagram-VCTEODGH-BOo3Ckng.js → abnfDiagram-VCTEODGH-CMfR95N2.js} +1 -1
  135. package/out/web/assets/{architecture-7GRP2DOG-ZOwheW3n.js → architecture-7GRP2DOG-DhAM4HyO.js} +1 -1
  136. package/out/web/assets/{architectureDiagram-5GKGNRK7-DC2FDzVd.js → architectureDiagram-5GKGNRK7-Yk3TUJ_F.js} +1 -1
  137. package/out/web/assets/{blockDiagram-I7D4REHJ-BrKFWdAH.js → blockDiagram-I7D4REHJ-X9fBgJik.js} +1 -1
  138. package/out/web/assets/{c4Diagram-7LVT6UL2-BHTHK1Yd.js → c4Diagram-7LVT6UL2-Bk9x7N-8.js} +1 -1
  139. package/out/web/assets/{chunk-4HAMMTFA-BYFkZwNj.js → chunk-4HAMMTFA-Dx5fk1_0.js} +1 -1
  140. package/out/web/assets/{chunk-75Z2AOVW-BSKfSKSA.js → chunk-75Z2AOVW-BszTQoM4.js} +1 -1
  141. package/out/web/assets/{chunk-DU6HZSFF-vIhsiW6n.js → chunk-DU6HZSFF-DK-As3rk.js} +1 -1
  142. package/out/web/assets/{chunk-F27PBJKO-BlYpW9xq.js → chunk-F27PBJKO-LmncZZIS.js} +1 -1
  143. package/out/web/assets/{chunk-GMAD6QVW-BIBz_Ug5.js → chunk-GMAD6QVW-DSQ_sDRJ.js} +1 -1
  144. package/out/web/assets/{chunk-GVQU2GXP-NPSHxeqC.js → chunk-GVQU2GXP-Cno3ulje.js} +1 -1
  145. package/out/web/assets/{chunk-IMKFNOWR-YlJK8_Fp.js → chunk-IMKFNOWR-bIzfY9Jc.js} +1 -1
  146. package/out/web/assets/{chunk-L3NEJ4N5-DMYi4-af.js → chunk-L3NEJ4N5-Dl6eOa0D.js} +1 -1
  147. package/out/web/assets/{chunk-OSK3NFVY-Begd3-jA.js → chunk-OSK3NFVY-BaUGJ-cM.js} +1 -1
  148. package/out/web/assets/{chunk-P2QGCYS3-D1I4xFHN.js → chunk-P2QGCYS3-_066ndhH.js} +1 -1
  149. package/out/web/assets/{chunk-POPQ4Y6H-CafZZzNE.js → chunk-POPQ4Y6H-YT1AKhTv.js} +1 -1
  150. package/out/web/assets/{chunk-PWAF6VOD-Dwh6_Lnt.js → chunk-PWAF6VOD-CrEgDsxh.js} +1 -1
  151. package/out/web/assets/{chunk-SHT3W25Y-B0XrCSEb.js → chunk-SHT3W25Y-_KwaRLOl.js} +1 -1
  152. package/out/web/assets/{chunk-SVP7TREG-CxLsddxC.js → chunk-SVP7TREG-BQ1qb32L.js} +1 -1
  153. package/out/web/assets/{chunk-TICWLB2K-y156QInb.js → chunk-TICWLB2K-BDyehCtg.js} +1 -1
  154. package/out/web/assets/classDiagram-ZZMXUADV-BoSpzRCJ.js +1 -0
  155. package/out/web/assets/classDiagram-v2-VYDZK3BY-BoSpzRCJ.js +1 -0
  156. package/out/web/assets/{config-DqXJXeZM.css → config-2suVFPZg.css} +1 -1
  157. package/out/web/assets/config-CkRCdkxn.js +2 -0
  158. package/out/web/assets/{cynefin-OW5HDTMX-SMVvrLf-.js → cynefin-OW5HDTMX-Dl_31aaN.js} +1 -1
  159. package/out/web/assets/{cynefinDiagram-5FMLGOSQ-rZznN24T.js → cynefinDiagram-5FMLGOSQ-Bpir8jha.js} +1 -1
  160. package/out/web/assets/{dagre-GXQ25YYZ-BOToydFu.js → dagre-GXQ25YYZ-D9xu3nsq.js} +1 -1
  161. package/out/web/assets/{diagram-S7CK7UJ4-TMVaGQEJ.js → diagram-S7CK7UJ4-DeveoEeB.js} +1 -1
  162. package/out/web/assets/{diagram-UQ7AKVKN-C2JK-GP2.js → diagram-UQ7AKVKN-p4xWRESV.js} +1 -1
  163. package/out/web/assets/{diagram-VSXAHHWV-Cw8VSi2K.js → diagram-VSXAHHWV-C7qz9NDF.js} +1 -1
  164. package/out/web/assets/{diagram-VX7I27RA-DXp4fKk9.js → diagram-VX7I27RA-DRiCa3Vd.js} +1 -1
  165. package/out/web/assets/{diagram-Z3DM3KII-CzSicYiW.js → diagram-Z3DM3KII-Bt7zduQX.js} +1 -1
  166. package/out/web/assets/{ebnfDiagram-PWID7BFC-Dqj-zqPu.js → ebnfDiagram-PWID7BFC-DV9WpwdR.js} +1 -1
  167. package/out/web/assets/{erDiagram-RLTQ6QDP-DZwFZAuD.js → erDiagram-RLTQ6QDP-BJ_nwG2m.js} +1 -1
  168. package/out/web/assets/{eventmodeling-NTZA5JFV-C0O-Lvgp.js → eventmodeling-NTZA5JFV-BTBMrfVa.js} +1 -1
  169. package/out/web/assets/flowDiagram-HODETNUW-Cdlq3cJL.js +1 -0
  170. package/out/web/assets/{ganttDiagram-EL5Y4UJY-Da4ULXTl.js → ganttDiagram-EL5Y4UJY-DQLdsXrC.js} +1 -1
  171. package/out/web/assets/{gitGraph-4MIJSDKK-B9wNFZIP.js → gitGraph-4MIJSDKK-CL1OEOQ_.js} +1 -1
  172. package/out/web/assets/{gitGraphDiagram-WWUBYQGX-RjrhB0MP.js → gitGraphDiagram-WWUBYQGX-8cSi42H7.js} +1 -1
  173. package/out/web/assets/index-BocxnrDG.js +168 -0
  174. package/out/web/assets/index-yeh_r55a.css +1 -0
  175. package/out/web/assets/{info-A6RAGUB7-B3dfCpeY.js → info-A6RAGUB7-C1zsX5sc.js} +1 -1
  176. package/out/web/assets/{infoDiagram-27XIBGKW-QVWkLVx7.js → infoDiagram-27XIBGKW-DsRYtIyI.js} +1 -1
  177. package/out/web/assets/{ishikawaDiagram-5VMMS53U-CLFNIRI9.js → ishikawaDiagram-5VMMS53U-B2crwIXj.js} +1 -1
  178. package/out/web/assets/{journeyDiagram-3NMN7TZE-fgOQadYF.js → journeyDiagram-3NMN7TZE-BTH-GyVR.js} +1 -1
  179. package/out/web/assets/{kanban-definition-UXKFOSKX-G3Q_9g9O.js → kanban-definition-UXKFOSKX-BeRfCnuA.js} +1 -1
  180. package/out/web/assets/{mermaid-parser.core-v3Jm7nco.js → mermaid-parser.core-BJwgCBBc.js} +3 -3
  181. package/out/web/assets/{mermaid.core-DTEjLUA1.js → mermaid.core-CsDDodN8.js} +4 -4
  182. package/out/web/assets/{mindmap-definition-YA3MSWOX-C_D42_fF.js → mindmap-definition-YA3MSWOX-B42efOQ1.js} +1 -1
  183. package/out/web/assets/{modelManager-BPLycQrN.js → modelManager-B3ZFMh8v.js} +2 -2
  184. package/out/web/assets/{oauthManager-BUK7ic7q.js → oauthManager-Ch5Du2fF.js} +1 -1
  185. package/out/web/assets/{packet-AYTQ26CC-B-R-SHlF.js → packet-AYTQ26CC-B6xUyE6r.js} +1 -1
  186. package/out/web/assets/{pegDiagram-XKGWAZYB-Da2q_giz.js → pegDiagram-XKGWAZYB-1LabVycL.js} +1 -1
  187. package/out/web/assets/{pie-WAS4IAKB-BC8F0N7-.js → pie-WAS4IAKB-DJwo-nf_.js} +1 -1
  188. package/out/web/assets/{pieDiagram-E7YTZNPT-Iy9REJNC.js → pieDiagram-E7YTZNPT-icmAqzy4.js} +1 -1
  189. package/out/web/assets/{quadrantDiagram-AXDQQJYC-ztsiYw7h.js → quadrantDiagram-AXDQQJYC-CZ-wEx41.js} +1 -1
  190. package/out/web/assets/{radar-RG4KPBEZ-B_1P8Z_z.js → radar-RG4KPBEZ-D7d-Xriw.js} +1 -1
  191. package/out/web/assets/{railroad-74A4TZTK-DuTqczSo.js → railroad-74A4TZTK-viuhdYbs.js} +1 -1
  192. package/out/web/assets/{railroad-abnf-HS5TGJTU-Df29WLmp.js → railroad-abnf-HS5TGJTU-DNXiCNea.js} +1 -1
  193. package/out/web/assets/{railroad-ebnf-LZEXJU2U-ePFMo-qp.js → railroad-ebnf-LZEXJU2U-nkSu1-0C.js} +1 -1
  194. package/out/web/assets/{railroad-peg-WCYAUIDC-DYXLW4_E.js → railroad-peg-WCYAUIDC-BhbMeQzz.js} +1 -1
  195. package/out/web/assets/{railroadDiagram-O6MQD6OU-LStnZf2S.js → railroadDiagram-O6MQD6OU-CLpG1jUX.js} +1 -1
  196. package/out/web/assets/{requirementDiagram-BXWQKSXE-DmzonMH-.js → requirementDiagram-BXWQKSXE-kRDrvWrc.js} +1 -1
  197. package/out/web/assets/{sankeyDiagram-P5KCCOFB-B8vjmUU1.js → sankeyDiagram-P5KCCOFB-DxAwmq4x.js} +1 -1
  198. package/out/web/assets/{sequenceDiagram-WJ2MYXX4-CTnplOAX.js → sequenceDiagram-WJ2MYXX4-aLgY-6Wz.js} +1 -1
  199. package/out/web/assets/{stateDiagram-D77RDMKH-BFrIaLdI.js → stateDiagram-D77RDMKH-D5QWfOhp.js} +1 -1
  200. package/out/web/assets/stateDiagram-v2-MP3YSRHH-D6OiZoAP.js +1 -0
  201. package/out/web/assets/{swimlanes-42K2YHIH-BXGngUNU.js → swimlanes-42K2YHIH-CRs5UyA-.js} +1 -1
  202. package/out/web/assets/swimlanesDiagram-VR7AAH4N-UgEXIsV0.js +8 -0
  203. package/out/web/assets/syntaxHighlight-C-wAGT84.js +3 -0
  204. package/out/web/assets/{timeline-definition-24CTP7MA-CegH7n43.js → timeline-definition-24CTP7MA-Denqd9qb.js} +1 -1
  205. package/out/web/assets/{treeView-Q6P3EWNA-DTpJv2Tw.js → treeView-Q6P3EWNA-0my3Z3s4.js} +1 -1
  206. package/out/web/assets/{treemap-WGGIJYW6-i8M9iJU6.js → treemap-WGGIJYW6-B_EjkLYq.js} +1 -1
  207. package/out/web/assets/{vennDiagram-4TSXK5OY-Dpzo1S8U.js → vennDiagram-4TSXK5OY-DxVg3Ewr.js} +1 -1
  208. package/out/web/assets/{wardley-WFR3VGLG-BrNsgtfw.js → wardley-WFR3VGLG-CGD-Tj4p.js} +1 -1
  209. package/out/web/assets/{wardleyDiagram-VM6X3IG4-lQ92LFE1.js → wardleyDiagram-VM6X3IG4-CmfS39Xm.js} +1 -1
  210. package/out/web/assets/{xychartDiagram-S5SC5T6Z-BaOaEoBd.js → xychartDiagram-S5SC5T6Z-BVM9bKis.js} +1 -1
  211. package/out/web/index-web.html +2 -2
  212. package/out/web/index.html +2 -2
  213. package/package.json +2 -2
  214. package/shared-modules/CODEOWNERS +3 -3
  215. package/shared-modules/SDL2/SDL2_mixer.json +27 -0
  216. package/shared-modules/appstream/README.md +13 -0
  217. package/shared-modules/appstream/appstream-compose.json +111 -0
  218. package/shared-modules/dbus-glib/dbus-glib.json +2 -2
  219. package/shared-modules/libportal/libportal.json +23 -0
  220. package/shared-modules/linux-audio/fluidsynth2-static.json +2 -2
  221. package/shared-modules/linux-audio/fluidsynth2.json +2 -2
  222. package/shared-modules/linux-audio/libinstpatch.json +2 -2
  223. package/shared-modules/linux-audio/liblo-static.json +3 -0
  224. package/shared-modules/luajit/luajit.json +2 -2
  225. package/shared-modules/squashfs-tools/squashfs-tools.json +0 -3
  226. package/slash-commands.json +7 -2
  227. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  228. package/src/renderer/shared/agentProgress.ts +51 -3
  229. package/src/renderer/shared/components/Settings/AutomationTab.tsx +2 -56
  230. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +15 -1
  231. package/src/renderer/shared/components/Settings/GeneralTab.tsx +25 -12
  232. package/src/renderer/shared/components/Settings/ThemeTab.test.tsx +45 -0
  233. package/src/renderer/shared/components/Settings/ThemeTab.tsx +106 -3
  234. package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +67 -17
  235. package/src/renderer/shared/components/activity/AgentActivityTable.tsx +376 -134
  236. package/src/renderer/shared/components/activity/AskUserActivityCard.test.tsx +122 -0
  237. package/src/renderer/shared/components/activity/AskUserActivityCard.tsx +70 -0
  238. package/src/renderer/shared/components/activity/McpToolResults.tsx +39 -2
  239. package/src/renderer/shared/components/activity/SubagentDagView.tsx +7 -7
  240. package/src/renderer/shared/components/activity/ViewedImages.tsx +41 -2
  241. package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +3 -3
  242. package/src/renderer/shared/components/app/CommandPalette.tsx +7 -7
  243. package/src/renderer/shared/components/app/DesktopTitlebar.tsx +2 -2
  244. package/src/renderer/shared/components/app/MintDashboard.tsx +27 -17
  245. package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +3 -3
  246. package/src/renderer/shared/components/cards/CalculationCard.tsx +3 -3
  247. package/src/renderer/shared/components/cards/ImageGenCard.tsx +2 -2
  248. package/src/renderer/shared/components/cards/ImageSearchCard.tsx +2 -2
  249. package/src/renderer/shared/components/cards/MermaidCard.tsx +1 -1
  250. package/src/renderer/shared/components/cards/PlanCard.test.tsx +41 -0
  251. package/src/renderer/shared/components/cards/PlanCard.tsx +32 -0
  252. package/src/renderer/shared/components/cards/ReleaseNotesCard.test.tsx +29 -0
  253. package/src/renderer/shared/components/cards/ReleaseNotesCard.tsx +137 -0
  254. package/src/renderer/shared/components/cards/StockCard.tsx +7 -7
  255. package/src/renderer/shared/components/cards/ToolResultCards.tsx +11 -3
  256. package/src/renderer/shared/components/cards/UiMockupWidget.tsx +4 -4
  257. package/src/renderer/shared/components/cards/WeatherCard.tsx +5 -5
  258. package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +11 -0
  259. package/src/renderer/shared/components/chat/ApprovalCard.tsx +21 -2
  260. package/src/renderer/shared/components/chat/ArtifactCards.test.tsx +69 -0
  261. package/src/renderer/shared/components/chat/ArtifactCards.tsx +238 -0
  262. package/src/renderer/shared/components/chat/AttachedDocumentCard.test.tsx +39 -0
  263. package/src/renderer/shared/components/chat/AttachedDocumentCard.tsx +220 -0
  264. package/src/renderer/shared/components/chat/ChatCodeBlock.test.tsx +33 -0
  265. package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +15 -1
  266. package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +40 -2
  267. package/src/renderer/shared/components/chat/ChatMessageItem.tsx +180 -34
  268. package/src/renderer/shared/components/chat/ChatModeSelector.css +227 -0
  269. package/src/renderer/shared/components/chat/ChatModeSelector.test.tsx +45 -0
  270. package/src/renderer/shared/components/chat/ChatModeSelector.tsx +198 -0
  271. package/src/renderer/shared/components/chat/ChatPanel.test.tsx +22 -0
  272. package/src/renderer/shared/components/chat/ChatPanel.tsx +227 -53
  273. package/src/renderer/shared/components/chat/DiffReviewModal.tsx +6 -6
  274. package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +142 -1
  275. package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +45 -3
  276. package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +42 -14
  277. package/src/renderer/shared/components/chat/RewindModal.tsx +10 -10
  278. package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +14 -0
  279. package/src/renderer/shared/components/chat/SourcesBlock.test.tsx +53 -0
  280. package/src/renderer/shared/components/chat/SourcesBlock.tsx +236 -90
  281. package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +45 -1
  282. package/src/renderer/shared/components/chat/TurnFileChanges.tsx +202 -169
  283. package/src/renderer/shared/components/extensions/CliSessionsView.tsx +5 -4
  284. package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +3 -3
  285. package/src/renderer/shared/components/studio/LibraryPage.tsx +48 -5
  286. package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +125 -13
  287. package/src/renderer/shared/components/workspace/CodeReviewPage.test.tsx +50 -0
  288. package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +77 -16
  289. package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +78 -22
  290. package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +13 -0
  291. package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +48 -28
  292. package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +266 -47
  293. package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +18 -6
  294. package/src/renderer/shared/components/workspace/background-terminals.css +11 -11
  295. package/src/renderer/shared/constants/config.ts +1 -5
  296. package/src/renderer/shared/constants/models.test.ts +51 -0
  297. package/src/renderer/shared/constants/models.ts +139 -32
  298. package/src/renderer/shared/conversation/lifecycle.test.ts +91 -3
  299. package/src/renderer/shared/conversation/lifecycle.ts +104 -5
  300. package/src/renderer/shared/conversation/progressiveStream.test.ts +83 -0
  301. package/src/renderer/shared/conversation/progressiveStream.ts +219 -0
  302. package/src/renderer/shared/css/agent-observability.css +133 -0
  303. package/src/renderer/shared/css/chat-ui-cards.css +13 -13
  304. package/src/renderer/shared/css/code-review.css +63 -34
  305. package/src/renderer/shared/css/code-sessions.css +47 -18
  306. package/src/renderer/shared/css/collapsed-paste.css +1 -1
  307. package/src/renderer/shared/css/error-notice.css +1 -1
  308. package/src/renderer/shared/css/file-changes.css +165 -80
  309. package/src/renderer/shared/css/library.css +105 -10
  310. package/src/renderer/shared/css/management-views.css +1 -1
  311. package/src/renderer/shared/css/mcp-results.css +5 -5
  312. package/src/renderer/shared/css/model-selector.css +18 -18
  313. package/src/renderer/shared/css/mods.css +7 -7
  314. package/src/renderer/shared/css/notification-notice.css +2 -2
  315. package/src/renderer/shared/css/queued-messages.css +5 -5
  316. package/src/renderer/shared/css/settings/general.css +18 -13
  317. package/src/renderer/shared/css/settings/profile.css +5 -5
  318. package/src/renderer/shared/css/settings/theme.css +5 -5
  319. package/src/renderer/shared/css/slash-choice.css +2 -2
  320. package/src/renderer/shared/css/slash-suggestions.css +3 -3
  321. package/src/renderer/shared/css/sources-block.css +41 -2
  322. package/src/renderer/shared/css/tools-panel.css +2 -2
  323. package/src/renderer/shared/css/ui-tailwind.css +8 -0
  324. package/src/renderer/shared/css/veo-studio.css +2 -2
  325. package/src/renderer/shared/css/viewed-images.css +7 -7
  326. package/src/renderer/shared/platform.ts +2 -1
  327. package/src/renderer/shared/theme/themeManager.test.ts +17 -0
  328. package/src/renderer/shared/theme/themeManager.ts +21 -4
  329. package/src/renderer/shared/theme/themes.ts +54 -0
  330. package/src/renderer/shared/theme/tokens.css +6 -4
  331. package/src/renderer/shared/types.ts +1 -1
  332. package/src/renderer/shared/utils/agentActivity.test.ts +468 -2
  333. package/src/renderer/shared/utils/agentActivity.ts +602 -35
  334. package/src/renderer/shared/utils/artifactBrowser.test.ts +37 -32
  335. package/src/renderer/shared/utils/artifactBrowser.ts +17 -19
  336. package/src/renderer/shared/utils/attachedDocument.test.ts +73 -0
  337. package/src/renderer/shared/utils/attachedDocument.ts +49 -0
  338. package/src/renderer/shared/utils/fileIcons.test.ts +59 -0
  339. package/src/renderer/shared/utils/fileIcons.ts +149 -16
  340. package/src/renderer/shared/utils/markdown.tsx +72 -19
  341. package/src/renderer/shared/utils/markdown.webImages.test.tsx +16 -0
  342. package/src/renderer/shared/utils/mcpResults.test.tsx +29 -0
  343. package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
  344. package/src/renderer/shared/utils/syntaxHighlight.test.tsx +11 -1
  345. package/src/renderer/shared/utils/syntaxHighlight.tsx +51 -3
  346. package/src/renderer/shared/utils/useGeminiLiveVoice.ts +324 -51
  347. package/src/renderer/shared/utils/workspaceDrag.test.ts +153 -0
  348. package/src/renderer/shared/utils/workspaceDrag.ts +142 -0
  349. package/src/renderer/shared/utils/workspaceMedia.ts +10 -2
  350. package/src/renderer/src/App.tsx +14 -8
  351. package/src/renderer/src/agentProgress.ts +1 -0
  352. package/src/renderer/src/components/ScreenPicker.css +1 -1
  353. package/src/renderer/src/components/WorkspacePanel.tsx +6 -5
  354. package/src/renderer/src/css/app-shell.css +4 -4
  355. package/src/renderer/src/css/chat.css +1261 -241
  356. package/src/renderer/src/css/desktop-tools.css +11 -11
  357. package/src/renderer/src/css/sidebar-workspace.css +2 -2
  358. package/src/renderer/src/css/tool-surfaces.css +12 -12
  359. package/src/renderer/src/index.css +38 -20
  360. package/src/renderer/src/tauri.ts +22 -2
  361. package/src/renderer/src-web/App.tsx +14 -0
  362. package/src/renderer/src-web/agentProgress.ts +1 -0
  363. package/src/renderer/src-web/css/app-shell.css +4 -4
  364. package/src/renderer/src-web/css/chat.css +1278 -190
  365. package/src/renderer/src-web/css/mobile.css +29 -13
  366. package/src/renderer/src-web/css/settings/mobile.css +2 -2
  367. package/src/renderer/src-web/css/sidebar-workspace.css +1 -1
  368. package/src/renderer/src-web/index.css +38 -20
  369. package/src/renderer/src-web/tauri.ts +22 -2
  370. package/src/renderer/tests/browserArtifacts.browser.tsx +1 -1
  371. package/src/renderer/tests/library.browser.tsx +3 -0
  372. package/src/renderer/tests/liveVoice.browser.html +1 -0
  373. package/src/renderer/tests/workspaceDrop.browser.tsx +1 -1
  374. package/src-tauri/src/app/lifecycle.rs +1 -14
  375. package/src-tauri/src/background/headless.rs +14 -8
  376. package/src-tauri/src/commands/conversation.rs +38 -0
  377. package/src-tauri/src/native/mod.rs +0 -1
  378. package/src-tauri/src/native/system.rs +4 -47
  379. package/crates/mint-cli/src/interactive/chat_tui.rs +0 -8855
  380. package/crates/mint-core/src/agent/chat.rs +0 -4394
  381. package/crates/mint-core/src/browser/lifecycle.rs +0 -291
  382. package/crates/mint-core/src/browser/logging.rs +0 -20
  383. package/crates/mint-core/src/orchestration/tools/browser.rs +0 -31
  384. package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +0 -2
  385. package/out/web/assets/CalculationCard-CRjP3E1j.js +0 -1
  386. package/out/web/assets/CliSessionsView-C3Br2v5Y.css +0 -1
  387. package/out/web/assets/CodeReviewPage-CUJceQme.css +0 -1
  388. package/out/web/assets/CodeReviewPage-VCUgPla_.js +0 -2
  389. package/out/web/assets/ImageGenCard-BCXxkVJX.js +0 -1
  390. package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +0 -1
  391. package/out/web/assets/LibraryPage-DAxUTAg7.css +0 -1
  392. package/out/web/assets/LibraryPage-WlviRdR8.js +0 -2
  393. package/out/web/assets/LinkedFoldersView-CKRgroWK.js +0 -1
  394. package/out/web/assets/McpServersView-D3wHnuHg.js +0 -1
  395. package/out/web/assets/MermaidCard-NqrHdyJW.js +0 -2
  396. package/out/web/assets/MintDashboard-B2DJsDDO.css +0 -1
  397. package/out/web/assets/MintDashboard-COw_xol7.js +0 -63
  398. package/out/web/assets/RewindModal-DempuDod.js +0 -1
  399. package/out/web/assets/SettingsWindow-2YP3yj7s.js +0 -4
  400. package/out/web/assets/SettingsWindow-Dz8LQVgD.css +0 -1
  401. package/out/web/assets/StockCard-BDMCe7mx.js +0 -1
  402. package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +0 -38
  403. package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +0 -1
  404. package/out/web/assets/UiMockupWidget-DD73QWg1.js +0 -1
  405. package/out/web/assets/WeatherCard-CPucIfq8.js +0 -1
  406. package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +0 -2
  407. package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +0 -1
  408. package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +0 -1
  409. package/out/web/assets/config-CvIO5hkn.js +0 -2
  410. package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +0 -1
  411. package/out/web/assets/index-BmpulIkg.js +0 -168
  412. package/out/web/assets/index-CkvBwhX_.css +0 -1
  413. package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +0 -1
  414. package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +0 -8
  415. package/shared-modules/libsecret/libsecret.json +0 -23
  416. package/shared-modules/libusb/libusb.json +0 -27
  417. package/shared-modules/lzo/lzo.json +0 -27
  418. package/src/renderer/src/components/BrowserShell.tsx +0 -108
  419. package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +0 -91
  420. package/src/renderer/src/components/browser-shell.css +0 -185
  421. package/src-tauri/src/native/browser.rs +0 -252
@@ -1,660 +1,1378 @@
1
- const ANSI_BLUE: &str = crate::terminal_theme::ANSI_BLUE;
2
-
3
1
  use anyhow::Result;
4
- use crossterm::event::{self, Event, KeyCode};
5
- use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
6
- use mint_core::{load_config, native_plugins, save_config};
7
- use std::io::{self, Write};
8
-
9
- struct ToolOption {
10
- name: &'static str,
11
- key: &'static str,
12
- enabled: bool,
2
+ use crossterm::ExecutableCommand;
3
+ use crossterm::cursor::{Hide, Show};
4
+ use crossterm::event::{self, Event, KeyCode, KeyModifiers};
5
+ use crossterm::terminal::{
6
+ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
7
+ };
8
+ use ratatui::{
9
+ Terminal,
10
+ backend::{Backend, CrosstermBackend},
11
+ layout::{Alignment, Constraint, Direction, Layout, Rect},
12
+ style::{Color, Modifier, Style},
13
+ text::{Line, Span},
14
+ widgets::{Block, BorderType, Borders, List, ListItem, ListState, Paragraph},
15
+ };
16
+ use std::collections::HashSet;
17
+ use std::time::Duration;
18
+ use unicode_width::UnicodeWidthStr;
19
+
20
+ use crate::terminal_theme::{
21
+ ACCENT, BLUE, MUTED, SELECTION_BACKGROUND, SELECTION_TEXT, SUBTLE, TEXT,
22
+ };
23
+ use mint_core::{MintConfig, load_config, native_plugins, save_config};
24
+
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ // Types & Data Structures
27
+ // ─────────────────────────────────────────────────────────────────────────────
28
+
29
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
30
+ pub enum SetupStep {
31
+ Tools,
32
+ Plugins,
33
+ Launch,
13
34
  }
14
35
 
15
- pub async fn run() -> Result<Option<String>> {
16
- let mut config = load_config()?;
17
-
18
- let mut options = vec![
19
- ToolOption {
20
- name: "list_files (List Workspace Files)",
21
- key: "list_files",
22
- enabled: !config.disabled_tools.contains(&"list_files".to_string()),
23
- },
24
- ToolOption {
25
- name: "read_file (Read File Content)",
26
- key: "read_file",
27
- enabled: !config.disabled_tools.contains(&"read_file".to_string()),
28
- },
29
- ToolOption {
30
- name: "search_code (Search Code Text)",
31
- key: "search_code",
32
- enabled: !config.disabled_tools.contains(&"search_code".to_string()),
33
- },
34
- ToolOption {
35
- name: "symbols (Index/Search Symbols)",
36
- key: "symbols",
37
- enabled: !config.disabled_tools.contains(&"symbols".to_string()),
38
- },
39
- ToolOption {
40
- name: "semantic_index (Semantic Indexing)",
41
- key: "semantic_index",
42
- enabled: !config
43
- .disabled_tools
44
- .contains(&"semantic_index".to_string()),
45
- },
46
- ToolOption {
47
- name: "semantic_search (Semantic Search)",
48
- key: "semantic_search",
49
- enabled: !config
50
- .disabled_tools
51
- .contains(&"semantic_search".to_string()),
52
- },
53
- ToolOption {
54
- name: "knowledge_search (Search Local Knowledge)",
55
- key: "knowledge_search",
56
- enabled: !config
57
- .disabled_tools
58
- .contains(&"knowledge_search".to_string()),
59
- },
60
- ToolOption {
61
- name: "web_search (Search Web)",
62
- key: "web_search",
63
- enabled: !config.disabled_tools.contains(&"web_search".to_string()),
64
- },
65
- ToolOption {
66
- name: "image_search (Search Images)",
67
- key: "image_search",
68
- enabled: !config.disabled_tools.contains(&"image_search".to_string()),
69
- },
70
- ToolOption {
71
- name: "weather (Check Weather)",
72
- key: "weather",
73
- enabled: !config.disabled_tools.contains(&"weather".to_string()),
74
- },
75
- ToolOption {
76
- name: "stock (Check Stock Prices)",
77
- key: "stock",
78
- enabled: !config.disabled_tools.contains(&"stock".to_string()),
79
- },
80
- ToolOption {
81
- name: "calculation (Perform Calculations)",
82
- key: "calculation",
83
- enabled: !config.disabled_tools.contains(&"calculation".to_string()),
84
- },
85
- ToolOption {
86
- name: "memory_recall (Recall Long-term Memory)",
87
- key: "memory_recall",
88
- enabled: !config.disabled_tools.contains(&"memory_recall".to_string()),
89
- },
90
- ToolOption {
91
- name: "git_status (Read Git Status)",
92
- key: "git_status",
93
- enabled: !config.disabled_tools.contains(&"git_status".to_string()),
94
- },
95
- ToolOption {
96
- name: "git_diff (Read Git Diff)",
97
- key: "git_diff",
98
- enabled: !config.disabled_tools.contains(&"git_diff".to_string()),
99
- },
100
- ToolOption {
101
- name: "git_log (Read Git Log)",
102
- key: "git_log",
103
- enabled: !config.disabled_tools.contains(&"git_log".to_string()),
104
- },
105
- ToolOption {
106
- name: "git_branch (Read Git Branch)",
107
- key: "git_branch",
108
- enabled: !config.disabled_tools.contains(&"git_branch".to_string()),
109
- },
110
- ToolOption {
111
- name: "create_plan (Create Task Plan)",
112
- key: "create_plan",
113
- enabled: !config.disabled_tools.contains(&"create_plan".to_string()),
114
- },
115
- ToolOption {
116
- name: "update_plan (Update Task Plan)",
117
- key: "update_plan",
118
- enabled: !config.disabled_tools.contains(&"update_plan".to_string()),
119
- },
120
- ToolOption {
121
- name: "request_user_approval (Request User Approval)",
122
- key: "request_user_approval",
123
- enabled: !config
124
- .disabled_tools
125
- .contains(&"request_user_approval".to_string()),
126
- },
127
- ToolOption {
128
- name: "ask_user (Ask User)",
129
- key: "ask_user",
130
- enabled: !config.disabled_tools.contains(&"ask_user".to_string()),
131
- },
132
- ToolOption {
133
- name: "detect_project (Detect Project Type)",
134
- key: "detect_project",
135
- enabled: !config
136
- .disabled_tools
137
- .contains(&"detect_project".to_string()),
138
- },
139
- ToolOption {
140
- name: "list_tests (List Tests)",
141
- key: "list_tests",
142
- enabled: !config.disabled_tools.contains(&"list_tests".to_string()),
143
- },
144
- ToolOption {
145
- name: "read_diagnostics (Read Diagnostics)",
146
- key: "read_diagnostics",
147
- enabled: !config
148
- .disabled_tools
149
- .contains(&"read_diagnostics".to_string()),
150
- },
151
- ToolOption {
152
- name: "view_image (View Image)",
153
- key: "view_image",
154
- enabled: !config.disabled_tools.contains(&"view_image".to_string()),
155
- },
156
- ToolOption {
157
- name: "note_write (Write Notes)",
158
- key: "note_write",
159
- enabled: !config.disabled_tools.contains(&"note_write".to_string()),
160
- },
161
- ToolOption {
162
- name: "run_plugin (Run Native Plugins)",
163
- key: "run_plugin",
164
- enabled: !config.disabled_tools.contains(&"run_plugin".to_string()),
165
- },
166
- ToolOption {
167
- name: "mcp_tool (Call MCP Tools)",
168
- key: "mcp_tool",
169
- enabled: !config.disabled_tools.contains(&"mcp_tool".to_string()),
170
- },
171
- ToolOption {
172
- name: "mcp_list_tools (List MCP Server Tools)",
173
- key: "mcp_list_tools",
174
- enabled: !config
175
- .disabled_tools
176
- .contains(&"mcp_list_tools".to_string()),
177
- },
178
- ToolOption {
179
- name: "run_shell (Run Shell Commands)",
180
- key: "run_shell",
181
- enabled: !config.disabled_tools.contains(&"run_shell".to_string()),
182
- },
183
- ToolOption {
184
- name: "verify (Run Verification Checks)",
185
- key: "verify",
186
- enabled: !config.disabled_tools.contains(&"verify".to_string()),
187
- },
188
- ToolOption {
189
- name: "apply_patch (Patch Files)",
190
- key: "apply_patch",
191
- enabled: !config.disabled_tools.contains(&"apply_patch".to_string()),
192
- },
193
- ToolOption {
194
- name: "write_file (Write Files)",
195
- key: "write_file",
196
- enabled: !config.disabled_tools.contains(&"write_file".to_string()),
197
- },
198
- ToolOption {
199
- name: "video_trim (Trim Video)",
200
- key: "video_trim",
201
- enabled: !config.disabled_tools.contains(&"video_trim".to_string()),
202
- },
203
- ToolOption {
204
- name: "video_remove_silence (Remove Silence from Video)",
205
- key: "video_remove_silence",
206
- enabled: !config
207
- .disabled_tools
208
- .contains(&"video_remove_silence".to_string()),
209
- },
210
- ToolOption {
211
- name: "video_resize (Resize Video)",
212
- key: "video_resize",
213
- enabled: !config.disabled_tools.contains(&"video_resize".to_string()),
214
- },
215
- ToolOption {
216
- name: "video_merge (Merge Videos)",
217
- key: "video_merge",
218
- enabled: !config.disabled_tools.contains(&"video_merge".to_string()),
219
- },
220
- ToolOption {
221
- name: "video_export (Export Video)",
222
- key: "video_export",
223
- enabled: !config.disabled_tools.contains(&"video_export".to_string()),
224
- },
225
- ToolOption {
226
- name: "video_extract_audio (Extract Audio from Video)",
227
- key: "video_extract_audio",
228
- enabled: !config
229
- .disabled_tools
230
- .contains(&"video_extract_audio".to_string()),
231
- },
232
- ToolOption {
233
- name: "speech_transcribe (Transcribe Speech)",
234
- key: "speech_transcribe",
235
- enabled: !config
236
- .disabled_tools
237
- .contains(&"speech_transcribe".to_string()),
238
- },
239
- ToolOption {
240
- name: "subtitle_generate (Generate Subtitles)",
241
- key: "subtitle_generate",
242
- enabled: !config
243
- .disabled_tools
244
- .contains(&"subtitle_generate".to_string()),
245
- },
246
- ToolOption {
247
- name: "subtitle_translate (Translate Subtitles)",
248
- key: "subtitle_translate",
249
- enabled: !config
250
- .disabled_tools
251
- .contains(&"subtitle_translate".to_string()),
252
- },
253
- ToolOption {
254
- name: "subtitle_burn (Burn Subtitles into Video)",
255
- key: "subtitle_burn",
256
- enabled: !config.disabled_tools.contains(&"subtitle_burn".to_string()),
257
- },
258
- ToolOption {
259
- name: "timeline_reorder (Reorder Video Timeline)",
260
- key: "timeline_reorder",
261
- enabled: !config
262
- .disabled_tools
263
- .contains(&"timeline_reorder".to_string()),
264
- },
265
- ToolOption {
266
- name: "effect_zoom_on_speaker (Auto-zoom on Speaker)",
267
- key: "effect_zoom_on_speaker",
268
- enabled: !config
269
- .disabled_tools
270
- .contains(&"effect_zoom_on_speaker".to_string()),
271
- },
272
- ToolOption {
273
- name: "audio_duck_music (Duck Background Music)",
274
- key: "audio_duck_music",
275
- enabled: !config
276
- .disabled_tools
277
- .contains(&"audio_duck_music".to_string()),
278
- },
279
- ToolOption {
280
- name: "make_shorts (Generate Short Clips)",
281
- key: "make_shorts",
282
- enabled: !config.disabled_tools.contains(&"make_shorts".to_string()),
283
- },
284
- ToolOption {
285
- name: "generate_image (Generate Image)",
286
- key: "generate_image",
287
- enabled: !config
288
- .disabled_tools
289
- .contains(&"generate_image".to_string()),
290
- },
291
- ToolOption {
292
- name: "generate_video (Generate Video)",
293
- key: "generate_video",
294
- enabled: !config
295
- .disabled_tools
296
- .contains(&"generate_video".to_string()),
297
- },
298
- ];
299
-
300
- let mut cursor = 0;
301
- redraw_phase_1(&options, cursor);
302
- enable_raw_mode()?;
36
+ impl SetupStep {
37
+ pub fn index(&self) -> usize {
38
+ match self {
39
+ Self::Tools => 0,
40
+ Self::Plugins => 1,
41
+ Self::Launch => 2,
42
+ }
43
+ }
44
+ }
303
45
 
304
- loop {
305
- match event::poll(std::time::Duration::from_millis(100)) {
306
- Ok(true) => {
307
- if let Event::Key(key_event) = event::read()?
308
- && key_event.kind == event::KeyEventKind::Press
309
- {
310
- let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
311
- && key_event
312
- .modifiers
313
- .contains(crossterm::event::KeyModifiers::CONTROL);
314
- if is_ctrl_c {
315
- disable_raw_mode()?;
316
- println!("\n\x1b[31mSetup cancelled.\x1b[0m");
317
- return Ok(None);
318
- }
46
+ #[derive(Debug, Clone)]
47
+ pub struct ToolItem {
48
+ pub category: &'static str,
49
+ pub name: &'static str,
50
+ pub key: &'static str,
51
+ pub description: &'static str,
52
+ pub enabled: bool,
53
+ }
319
54
 
320
- match key_event.code {
321
- KeyCode::Up => {
322
- if cursor > 0 {
323
- cursor -= 1;
324
- } else {
325
- cursor = options.len() - 1;
326
- }
327
- disable_raw_mode()?;
328
- redraw_phase_1(&options, cursor);
329
- enable_raw_mode()?;
330
- }
331
- KeyCode::Down => {
332
- if cursor < options.len() - 1 {
333
- cursor += 1;
334
- } else {
335
- cursor = 0;
336
- }
337
- disable_raw_mode()?;
338
- redraw_phase_1(&options, cursor);
339
- enable_raw_mode()?;
340
- }
341
- KeyCode::Char(' ') => {
342
- options[cursor].enabled = !options[cursor].enabled;
343
- disable_raw_mode()?;
344
- redraw_phase_1(&options, cursor);
345
- enable_raw_mode()?;
346
- }
347
- KeyCode::Char('a') => {
348
- for opt in &mut options {
349
- opt.enabled = true;
350
- }
351
- disable_raw_mode()?;
352
- redraw_phase_1(&options, cursor);
353
- enable_raw_mode()?;
354
- }
355
- KeyCode::Char('i') => {
356
- for opt in &mut options {
357
- opt.enabled = !opt.enabled;
358
- }
359
- disable_raw_mode()?;
360
- redraw_phase_1(&options, cursor);
361
- enable_raw_mode()?;
362
- }
363
- KeyCode::Enter => {
364
- break;
365
- }
366
- _ => {}
367
- }
55
+ #[derive(Debug, Clone)]
56
+ pub struct PluginItem {
57
+ pub name: &'static str,
58
+ pub description: &'static str,
59
+ pub enabled: bool,
60
+ }
61
+
62
+ #[derive(Debug, Clone)]
63
+ pub struct LaunchTargetItem {
64
+ pub title: &'static str,
65
+ pub key: Option<&'static str>,
66
+ pub description: &'static str,
67
+ }
68
+
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+ // Setup Wizard State & Engine
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+
73
+ pub struct SetupWizard {
74
+ pub config: MintConfig,
75
+ pub step: SetupStep,
76
+
77
+ // Step 1: Agent Tools
78
+ pub tools: Vec<ToolItem>,
79
+ pub tool_cursor: usize,
80
+
81
+ // Step 2: Native Plugins
82
+ pub plugins: Vec<PluginItem>,
83
+ pub plugin_cursor: usize,
84
+
85
+ // Step 3: Launch Target
86
+ pub launch_options: Vec<LaunchTargetItem>,
87
+ pub launch_cursor: usize,
88
+ }
89
+
90
+ impl SetupWizard {
91
+ pub fn new(config: MintConfig) -> Self {
92
+ let tools = vec![
93
+ // 📁 Files & Workspace
94
+ ToolItem {
95
+ category: "Files & Workspace",
96
+ name: "list_files",
97
+ key: "list_files",
98
+ description: "List files and directories in the workspace",
99
+ enabled: !config.disabled_tools.contains(&"list_files".to_string()),
100
+ },
101
+ ToolItem {
102
+ category: "Files & Workspace",
103
+ name: "read_file",
104
+ key: "read_file",
105
+ description: "Read file contents (text, code, documents)",
106
+ enabled: !config.disabled_tools.contains(&"read_file".to_string()),
107
+ },
108
+ ToolItem {
109
+ category: "Files & Workspace",
110
+ name: "write_file",
111
+ key: "write_file",
112
+ description: "Create new files or overwrite existing files",
113
+ enabled: !config.disabled_tools.contains(&"write_file".to_string()),
114
+ },
115
+ ToolItem {
116
+ category: "Files & Workspace",
117
+ name: "apply_patch",
118
+ key: "apply_patch",
119
+ description: "Apply surgical edits and patch diffs to files",
120
+ enabled: !config.disabled_tools.contains(&"apply_patch".to_string()),
121
+ },
122
+ // 💻 Code & Repository
123
+ ToolItem {
124
+ category: "Code & Repository",
125
+ name: "search_code",
126
+ key: "search_code",
127
+ description: "Search workspace code text using regex/patterns",
128
+ enabled: !config.disabled_tools.contains(&"search_code".to_string()),
129
+ },
130
+ ToolItem {
131
+ category: "Code & Repository",
132
+ name: "symbols",
133
+ key: "symbols",
134
+ description: "Index and search code symbols and definitions",
135
+ enabled: !config.disabled_tools.contains(&"symbols".to_string()),
136
+ },
137
+ ToolItem {
138
+ category: "Code & Repository",
139
+ name: "git_status",
140
+ key: "git_status",
141
+ description: "Read git repository status and branch state",
142
+ enabled: !config.disabled_tools.contains(&"git_status".to_string()),
143
+ },
144
+ ToolItem {
145
+ category: "Code & Repository",
146
+ name: "git_diff",
147
+ key: "git_diff",
148
+ description: "Inspect working tree and commit diffs",
149
+ enabled: !config.disabled_tools.contains(&"git_diff".to_string()),
150
+ },
151
+ ToolItem {
152
+ category: "Code & Repository",
153
+ name: "git_log",
154
+ key: "git_log",
155
+ description: "Read git commit history logs",
156
+ enabled: !config.disabled_tools.contains(&"git_log".to_string()),
157
+ },
158
+ ToolItem {
159
+ category: "Code & Repository",
160
+ name: "git_branch",
161
+ key: "git_branch",
162
+ description: "List, inspect, and switch git branches",
163
+ enabled: !config.disabled_tools.contains(&"git_branch".to_string()),
164
+ },
165
+ ToolItem {
166
+ category: "Code & Repository",
167
+ name: "detect_project",
168
+ key: "detect_project",
169
+ description: "Detect project type, stack, and workspace manifest",
170
+ enabled: !config
171
+ .disabled_tools
172
+ .contains(&"detect_project".to_string()),
173
+ },
174
+ ToolItem {
175
+ category: "Code & Repository",
176
+ name: "list_tests",
177
+ key: "list_tests",
178
+ description: "Discover and enumerate automated test suites",
179
+ enabled: !config.disabled_tools.contains(&"list_tests".to_string()),
180
+ },
181
+ ToolItem {
182
+ category: "Code & Repository",
183
+ name: "read_diagnostics",
184
+ key: "read_diagnostics",
185
+ description: "Read compiler diagnostics and linter errors",
186
+ enabled: !config
187
+ .disabled_tools
188
+ .contains(&"read_diagnostics".to_string()),
189
+ },
190
+ // 🔍 Search & Knowledge
191
+ ToolItem {
192
+ category: "Search & Knowledge",
193
+ name: "web_search",
194
+ key: "web_search",
195
+ description: "Search the web for up-to-date documentation & answers",
196
+ enabled: !config.disabled_tools.contains(&"web_search".to_string()),
197
+ },
198
+ ToolItem {
199
+ category: "Search & Knowledge",
200
+ name: "image_search",
201
+ key: "image_search",
202
+ description: "Search for images on the web",
203
+ enabled: !config.disabled_tools.contains(&"image_search".to_string()),
204
+ },
205
+ ToolItem {
206
+ category: "Search & Knowledge",
207
+ name: "semantic_search",
208
+ key: "semantic_search",
209
+ description: "Semantic natural language search over indexed code",
210
+ enabled: !config
211
+ .disabled_tools
212
+ .contains(&"semantic_search".to_string()),
213
+ },
214
+ ToolItem {
215
+ category: "Search & Knowledge",
216
+ name: "semantic_index",
217
+ key: "semantic_index",
218
+ description: "Build local vector embeddings for semantic search",
219
+ enabled: !config
220
+ .disabled_tools
221
+ .contains(&"semantic_index".to_string()),
222
+ },
223
+ ToolItem {
224
+ category: "Search & Knowledge",
225
+ name: "knowledge_search",
226
+ key: "knowledge_search",
227
+ description: "Search local knowledge base documents and notes",
228
+ enabled: !config
229
+ .disabled_tools
230
+ .contains(&"knowledge_search".to_string()),
231
+ },
232
+ // 🧠 Productivity & Utilities
233
+ ToolItem {
234
+ category: "Productivity & Utilities",
235
+ name: "weather",
236
+ key: "weather",
237
+ description: "Check real-time weather conditions and forecasts",
238
+ enabled: !config.disabled_tools.contains(&"weather".to_string()),
239
+ },
240
+ ToolItem {
241
+ category: "Productivity & Utilities",
242
+ name: "stock",
243
+ key: "stock",
244
+ description: "Query real-time stock and market prices",
245
+ enabled: !config.disabled_tools.contains(&"stock".to_string()),
246
+ },
247
+ ToolItem {
248
+ category: "Productivity & Utilities",
249
+ name: "calculation",
250
+ key: "calculation",
251
+ description: "Perform math, currency, and unit calculations",
252
+ enabled: !config.disabled_tools.contains(&"calculation".to_string()),
253
+ },
254
+ ToolItem {
255
+ category: "Productivity & Utilities",
256
+ name: "memory_recall",
257
+ key: "memory_recall",
258
+ description: "Recall persistent long-term memories across sessions",
259
+ enabled: !config.disabled_tools.contains(&"memory_recall".to_string()),
260
+ },
261
+ ToolItem {
262
+ category: "Productivity & Utilities",
263
+ name: "note_write",
264
+ key: "note_write",
265
+ description: "Write and save notes to linked memory folders",
266
+ enabled: !config.disabled_tools.contains(&"note_write".to_string()),
267
+ },
268
+ // 🛡️ Planning & Safety
269
+ ToolItem {
270
+ category: "Planning & Safety",
271
+ name: "create_plan",
272
+ key: "create_plan",
273
+ description: "Create structured multi-step task plans and milestones",
274
+ enabled: !config.disabled_tools.contains(&"create_plan".to_string()),
275
+ },
276
+ ToolItem {
277
+ category: "Planning & Safety",
278
+ name: "update_plan",
279
+ key: "update_plan",
280
+ description: "Update task plan progress and step completion",
281
+ enabled: !config.disabled_tools.contains(&"update_plan".to_string()),
282
+ },
283
+ ToolItem {
284
+ category: "Planning & Safety",
285
+ name: "request_user_approval",
286
+ key: "request_user_approval",
287
+ description: "Request explicit user approval for risky actions",
288
+ enabled: !config
289
+ .disabled_tools
290
+ .contains(&"request_user_approval".to_string()),
291
+ },
292
+ ToolItem {
293
+ category: "Planning & Safety",
294
+ name: "ask_user",
295
+ key: "ask_user",
296
+ description: "Ask the user interactive clarifying questions",
297
+ enabled: !config.disabled_tools.contains(&"ask_user".to_string()),
298
+ },
299
+ ToolItem {
300
+ category: "Planning & Safety",
301
+ name: "verify",
302
+ key: "verify",
303
+ description: "Run verification checks and automated assertions",
304
+ enabled: !config.disabled_tools.contains(&"verify".to_string()),
305
+ },
306
+ // ⚙️ Execution & MCP
307
+ ToolItem {
308
+ category: "Execution & MCP",
309
+ name: "run_shell",
310
+ key: "run_shell",
311
+ description: "Execute terminal commands under safety policy checks",
312
+ enabled: !config.disabled_tools.contains(&"run_shell".to_string()),
313
+ },
314
+ ToolItem {
315
+ category: "Execution & MCP",
316
+ name: "run_plugin",
317
+ key: "run_plugin",
318
+ description: "Execute native Mint plugin operations",
319
+ enabled: !config.disabled_tools.contains(&"run_plugin".to_string()),
320
+ },
321
+ ToolItem {
322
+ category: "Execution & MCP",
323
+ name: "mcp_tool",
324
+ key: "mcp_tool",
325
+ description: "Invoke configured Model Context Protocol (MCP) tools",
326
+ enabled: !config.disabled_tools.contains(&"mcp_tool".to_string()),
327
+ },
328
+ ToolItem {
329
+ category: "Execution & MCP",
330
+ name: "mcp_list_tools",
331
+ key: "mcp_list_tools",
332
+ description: "Discover available MCP server tools",
333
+ enabled: !config
334
+ .disabled_tools
335
+ .contains(&"mcp_list_tools".to_string()),
336
+ },
337
+ // 🎬 Media & Audio/Video
338
+ ToolItem {
339
+ category: "Media & Audio/Video",
340
+ name: "view_image",
341
+ key: "view_image",
342
+ description: "Inspect and analyze image attachments with deduplication",
343
+ enabled: !config.disabled_tools.contains(&"view_image".to_string()),
344
+ },
345
+ ToolItem {
346
+ category: "Media & Audio/Video",
347
+ name: "generate_image",
348
+ key: "generate_image",
349
+ description: "Generate images via NanoBanana (Google Gemini)",
350
+ enabled: !config
351
+ .disabled_tools
352
+ .contains(&"generate_image".to_string()),
353
+ },
354
+ ToolItem {
355
+ category: "Media & Audio/Video",
356
+ name: "generate_video",
357
+ key: "generate_video",
358
+ description: "Generate short videos via Google Veo",
359
+ enabled: !config
360
+ .disabled_tools
361
+ .contains(&"generate_video".to_string()),
362
+ },
363
+ ToolItem {
364
+ category: "Media & Audio/Video",
365
+ name: "video_trim",
366
+ key: "video_trim",
367
+ description: "Trim video duration by precise timestamps",
368
+ enabled: !config.disabled_tools.contains(&"video_trim".to_string()),
369
+ },
370
+ ToolItem {
371
+ category: "Media & Audio/Video",
372
+ name: "video_remove_silence",
373
+ key: "video_remove_silence",
374
+ description: "Automatically remove silence and pauses from video",
375
+ enabled: !config
376
+ .disabled_tools
377
+ .contains(&"video_remove_silence".to_string()),
378
+ },
379
+ ToolItem {
380
+ category: "Media & Audio/Video",
381
+ name: "video_resize",
382
+ key: "video_resize",
383
+ description: "Resize video resolution and aspect ratios",
384
+ enabled: !config.disabled_tools.contains(&"video_resize".to_string()),
385
+ },
386
+ ToolItem {
387
+ category: "Media & Audio/Video",
388
+ name: "video_merge",
389
+ key: "video_merge",
390
+ description: "Concatenate and merge multiple video clips",
391
+ enabled: !config.disabled_tools.contains(&"video_merge".to_string()),
392
+ },
393
+ ToolItem {
394
+ category: "Media & Audio/Video",
395
+ name: "video_export",
396
+ key: "video_export",
397
+ description: "Render and export video files with encoding presets",
398
+ enabled: !config.disabled_tools.contains(&"video_export".to_string()),
399
+ },
400
+ ToolItem {
401
+ category: "Media & Audio/Video",
402
+ name: "video_extract_audio",
403
+ key: "video_extract_audio",
404
+ description: "Extract audio tracks (MP3/WAV) from video files",
405
+ enabled: !config
406
+ .disabled_tools
407
+ .contains(&"video_extract_audio".to_string()),
408
+ },
409
+ ToolItem {
410
+ category: "Media & Audio/Video",
411
+ name: "speech_transcribe",
412
+ key: "speech_transcribe",
413
+ description: "Transcribe voice audio to timestamped text",
414
+ enabled: !config
415
+ .disabled_tools
416
+ .contains(&"speech_transcribe".to_string()),
417
+ },
418
+ ToolItem {
419
+ category: "Media & Audio/Video",
420
+ name: "subtitle_generate",
421
+ key: "subtitle_generate",
422
+ description: "Auto-generate SRT and VTT subtitle files",
423
+ enabled: !config
424
+ .disabled_tools
425
+ .contains(&"subtitle_generate".to_string()),
426
+ },
427
+ ToolItem {
428
+ category: "Media & Audio/Video",
429
+ name: "subtitle_translate",
430
+ key: "subtitle_translate",
431
+ description: "Translate subtitles across multiple languages",
432
+ enabled: !config
433
+ .disabled_tools
434
+ .contains(&"subtitle_translate".to_string()),
435
+ },
436
+ ToolItem {
437
+ category: "Media & Audio/Video",
438
+ name: "subtitle_burn",
439
+ key: "subtitle_burn",
440
+ description: "Burn hard subtitles directly into video streams",
441
+ enabled: !config.disabled_tools.contains(&"subtitle_burn".to_string()),
442
+ },
443
+ ToolItem {
444
+ category: "Media & Audio/Video",
445
+ name: "timeline_reorder",
446
+ key: "timeline_reorder",
447
+ description: "Reorder and arrange clips on the video timeline",
448
+ enabled: !config
449
+ .disabled_tools
450
+ .contains(&"timeline_reorder".to_string()),
451
+ },
452
+ ToolItem {
453
+ category: "Media & Audio/Video",
454
+ name: "effect_zoom_on_speaker",
455
+ key: "effect_zoom_on_speaker",
456
+ description: "Dynamic camera auto-zoom on active speaker",
457
+ enabled: !config
458
+ .disabled_tools
459
+ .contains(&"effect_zoom_on_speaker".to_string()),
460
+ },
461
+ ToolItem {
462
+ category: "Media & Audio/Video",
463
+ name: "audio_duck_music",
464
+ key: "audio_duck_music",
465
+ description: "Duck background music volume behind voice tracks",
466
+ enabled: !config
467
+ .disabled_tools
468
+ .contains(&"audio_duck_music".to_string()),
469
+ },
470
+ ToolItem {
471
+ category: "Media & Audio/Video",
472
+ name: "make_shorts",
473
+ key: "make_shorts",
474
+ description: "Generate vertical short clips (9:16) from video",
475
+ enabled: !config.disabled_tools.contains(&"make_shorts".to_string()),
476
+ },
477
+ ];
478
+
479
+ let all_native = native_plugins();
480
+ let allowed_plugins: HashSet<String> = config
481
+ .extra
482
+ .get("allowedNativePlugins")
483
+ .and_then(|v| v.as_array())
484
+ .map(|arr| {
485
+ arr.iter()
486
+ .filter_map(|v| v.as_str().map(|s| s.to_string()))
487
+ .collect()
488
+ })
489
+ .unwrap_or_else(|| {
490
+ vec![
491
+ "dev_tools".to_string(),
492
+ "system_metrics".to_string(),
493
+ "github".to_string(),
494
+ ]
495
+ .into_iter()
496
+ .collect()
497
+ });
498
+
499
+ let plugins = all_native
500
+ .into_iter()
501
+ .map(|p| {
502
+ let has_all_permission = allowed_plugins.contains("*");
503
+ let is_enabled = has_all_permission || allowed_plugins.contains(p.name);
504
+ PluginItem {
505
+ name: p.name,
506
+ description: p.description,
507
+ enabled: is_enabled,
368
508
  }
509
+ })
510
+ .collect();
511
+
512
+ let launch_options = vec![
513
+ LaunchTargetItem {
514
+ title: "1. CLI Interactive Chat Assistant",
515
+ key: Some("cli"),
516
+ description: "Launch the full-screen terminal interactive assistant immediately",
517
+ },
518
+ LaunchTargetItem {
519
+ title: "2. Web Application UI",
520
+ key: Some("web"),
521
+ description: "Start Mint local API server and launch the Vite Web App in your browser",
522
+ },
523
+ LaunchTargetItem {
524
+ title: "3. Desktop Application",
525
+ key: Some("app_link"),
526
+ description: "Open the Mint Desktop app link / download page in default browser",
527
+ },
528
+ LaunchTargetItem {
529
+ title: "4. Save & Exit",
530
+ key: None,
531
+ description: "Save updated configuration and return to shell without launching",
532
+ },
533
+ ];
534
+
535
+ Self {
536
+ config,
537
+ step: SetupStep::Tools,
538
+ tools,
539
+ tool_cursor: 0,
540
+ plugins,
541
+ plugin_cursor: 0,
542
+ launch_options,
543
+ launch_cursor: 0,
544
+ }
545
+ }
546
+
547
+ // ─────────────────────────────────────────────────────────────────────────
548
+ // Save Logic
549
+ // ─────────────────────────────────────────────────────────────────────────
550
+
551
+ pub fn save_changes(&mut self) -> Result<()> {
552
+ let disabled: Vec<String> = self
553
+ .tools
554
+ .iter()
555
+ .filter(|o| !o.enabled)
556
+ .map(|o| o.key.to_string())
557
+ .collect();
558
+ self.config.disabled_tools = disabled;
559
+
560
+ let enabled_natives: Vec<serde_json::Value> = self
561
+ .plugins
562
+ .iter()
563
+ .filter(|o| o.enabled)
564
+ .map(|o| serde_json::Value::String(o.name.to_string()))
565
+ .collect();
566
+
567
+ self.config.extra.insert(
568
+ "allowedNativePlugins".to_string(),
569
+ serde_json::Value::Array(enabled_natives),
570
+ );
571
+
572
+ save_config(&self.config)?;
573
+ Ok(())
574
+ }
575
+
576
+ // ─────────────────────────────────────────────────────────────────────────
577
+ // Render Functions
578
+ // ─────────────────────────────────────────────────────────────────────────
579
+
580
+ pub fn render(&mut self, frame: &mut ratatui::Frame) {
581
+ let area = frame.area();
582
+ let main_chunks = Layout::default()
583
+ .direction(Direction::Vertical)
584
+ .constraints([
585
+ Constraint::Length(3), // Stepper header
586
+ Constraint::Min(10), // Content area
587
+ Constraint::Length(1), // Footer status bar
588
+ ])
589
+ .split(area);
590
+
591
+ self.render_stepper_header(frame, main_chunks[0]);
592
+
593
+ match self.step {
594
+ SetupStep::Tools => self.render_step_1_tools(frame, main_chunks[1]),
595
+ SetupStep::Plugins => self.render_step_2_plugins(frame, main_chunks[1]),
596
+ SetupStep::Launch => self.render_step_3_launch(frame, main_chunks[1]),
597
+ }
598
+
599
+ self.render_footer(frame, main_chunks[2]);
600
+ }
601
+
602
+ fn render_stepper_header(&self, frame: &mut ratatui::Frame, area: Rect) {
603
+ let current_idx = self.step.index();
604
+ let step_spans = vec![
605
+ Self::step_badge("1. Agent Tools", current_idx == 0, current_idx > 0),
606
+ Span::styled(" ── ", Style::default().fg(MUTED)),
607
+ Self::step_badge("2. Native Plugins", current_idx == 1, current_idx > 1),
608
+ Span::styled(" ── ", Style::default().fg(MUTED)),
609
+ Self::step_badge("3. Launch Target", current_idx == 2, false),
610
+ ];
611
+
612
+ let block = Block::default()
613
+ .borders(Borders::ALL)
614
+ .border_type(BorderType::Rounded)
615
+ .border_style(Style::default().fg(SUBTLE))
616
+ .title(Span::styled(
617
+ " Mint Tool & Environment Setup ",
618
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
619
+ ));
620
+
621
+ let paragraph = Paragraph::new(Line::from(step_spans))
622
+ .block(block)
623
+ .alignment(Alignment::Center);
624
+
625
+ frame.render_widget(paragraph, area);
626
+ }
627
+
628
+ fn step_badge(title: &str, is_current: bool, is_done: bool) -> Span<'static> {
629
+ if is_current {
630
+ Span::styled(
631
+ format!(" [{title}] "),
632
+ Style::default()
633
+ .fg(SELECTION_TEXT)
634
+ .bg(SELECTION_BACKGROUND)
635
+ .add_modifier(Modifier::BOLD),
636
+ )
637
+ } else if is_done {
638
+ Span::styled(
639
+ format!(" ✔ {title} "),
640
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
641
+ )
642
+ } else {
643
+ Span::styled(format!(" {title} "), Style::default().fg(MUTED))
644
+ }
645
+ }
646
+
647
+ fn render_step_1_tools(&mut self, frame: &mut ratatui::Frame, area: Rect) {
648
+ let block = Block::default()
649
+ .borders(Borders::ALL)
650
+ .border_type(BorderType::Rounded)
651
+ .border_style(Style::default().fg(SUBTLE))
652
+ .title(Span::styled(
653
+ " Step 1: Agent Tools Manager ",
654
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
655
+ ));
656
+
657
+ let inner = block.inner(area);
658
+ frame.render_widget(block, area);
659
+
660
+ let chunks = Layout::default()
661
+ .direction(Direction::Vertical)
662
+ .margin(1)
663
+ .constraints([
664
+ Constraint::Length(2), // Subtitle
665
+ Constraint::Min(10), // Tools list
666
+ Constraint::Length(2), // Action hint
667
+ ])
668
+ .split(inner);
669
+
670
+ let subtitle = Paragraph::new(
671
+ "Select agent tools to enable or disable for your assistant.\nTools disabled here will not be called during autonomous reasoning sessions."
672
+ ).style(Style::default().fg(MUTED));
673
+ frame.render_widget(subtitle, chunks[0]);
674
+
675
+ let mut list_items = Vec::new();
676
+ let mut cur_category = "";
677
+ let list_inner_width = chunks[1].width.saturating_sub(2) as usize;
678
+
679
+ for (i, tool) in self.tools.iter().enumerate() {
680
+ let is_cursor = i == self.tool_cursor;
681
+ let check_icon = if tool.enabled { " ◉ " } else { " ○ " };
682
+ let cursor_prefix = if is_cursor { " › " } else { " " };
683
+
684
+ let mut lines = Vec::new();
685
+ if tool.category != cur_category {
686
+ cur_category = tool.category;
687
+ lines.push(Line::from(vec![Span::styled(
688
+ format!(" ── {} ──", tool.category),
689
+ Style::default().fg(BLUE).add_modifier(Modifier::BOLD),
690
+ )]));
369
691
  }
370
- Ok(false) => {}
371
- Err(_) => {
372
- break;
692
+
693
+ if is_cursor {
694
+ let check_style = Style::default()
695
+ .fg(if tool.enabled {
696
+ Color::Black
697
+ } else {
698
+ Color::DarkGray
699
+ })
700
+ .bg(SELECTION_BACKGROUND)
701
+ .add_modifier(Modifier::BOLD);
702
+
703
+ let desc_part = format!(" — {}", tool.description);
704
+ let full_row_text = format!("{cursor_prefix}{check_icon}{}{desc_part}", tool.name);
705
+ let visual_width = UnicodeWidthStr::width(full_row_text.as_str());
706
+ let pad_len = list_inner_width.saturating_sub(visual_width);
707
+
708
+ lines.push(Line::from(vec![
709
+ Span::styled(
710
+ cursor_prefix,
711
+ Style::default()
712
+ .fg(SELECTION_TEXT)
713
+ .bg(SELECTION_BACKGROUND)
714
+ .add_modifier(Modifier::BOLD),
715
+ ),
716
+ Span::styled(check_icon, check_style),
717
+ Span::styled(
718
+ tool.name,
719
+ Style::default()
720
+ .fg(SELECTION_TEXT)
721
+ .bg(SELECTION_BACKGROUND)
722
+ .add_modifier(Modifier::BOLD),
723
+ ),
724
+ Span::styled(
725
+ desc_part,
726
+ Style::default().fg(SELECTION_TEXT).bg(SELECTION_BACKGROUND),
727
+ ),
728
+ Span::styled(
729
+ " ".repeat(pad_len),
730
+ Style::default().bg(SELECTION_BACKGROUND),
731
+ ),
732
+ ]));
733
+ list_items.push(ListItem::new(lines));
734
+ } else {
735
+ let name_style = if tool.enabled {
736
+ Style::default().fg(TEXT)
737
+ } else {
738
+ Style::default().fg(MUTED)
739
+ };
740
+ let check_color = if tool.enabled { ACCENT } else { MUTED };
741
+
742
+ lines.push(Line::from(vec![
743
+ Span::raw(cursor_prefix),
744
+ Span::styled(
745
+ check_icon,
746
+ Style::default()
747
+ .fg(check_color)
748
+ .add_modifier(Modifier::BOLD),
749
+ ),
750
+ Span::styled(tool.name, name_style),
751
+ Span::styled(
752
+ format!(" — {}", tool.description),
753
+ Style::default().fg(MUTED),
754
+ ),
755
+ ]));
756
+ list_items.push(ListItem::new(lines));
373
757
  }
374
758
  }
759
+
760
+ let enabled_count = self.tools.iter().filter(|t| t.enabled).count();
761
+ let total_count = self.tools.len();
762
+
763
+ let list_block = Block::default()
764
+ .borders(Borders::ALL)
765
+ .border_type(BorderType::Rounded)
766
+ .border_style(Style::default().fg(TEXT))
767
+ .title(Line::from(vec![
768
+ Span::styled(
769
+ " Agent Tools ",
770
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
771
+ ),
772
+ Span::styled(
773
+ " [ SELECTING ] ",
774
+ Style::default()
775
+ .fg(SELECTION_TEXT)
776
+ .bg(SELECTION_BACKGROUND)
777
+ .add_modifier(Modifier::BOLD),
778
+ ),
779
+ Span::styled(
780
+ format!(" [Enabled: {enabled_count} / {total_count} tools] "),
781
+ Style::default().fg(TEXT),
782
+ ),
783
+ Span::styled(
784
+ " [Space: Toggle | a: All | i: Invert] ",
785
+ Style::default().fg(MUTED),
786
+ ),
787
+ ]));
788
+
789
+ let mut list_state = ListState::default();
790
+ list_state.select(Some(self.tool_cursor));
791
+ frame.render_stateful_widget(
792
+ List::new(list_items).block(list_block),
793
+ chunks[1],
794
+ &mut list_state,
795
+ );
796
+
797
+ let action_hint = Paragraph::new(Line::from(vec![
798
+ Span::styled(" [ Esc: Cancel ] ", Style::default().fg(MUTED)),
799
+ Span::raw(" "),
800
+ Span::styled(
801
+ " [ Enter: Next Step (Native Plugins) ➔ ] ",
802
+ Style::default()
803
+ .fg(SELECTION_TEXT)
804
+ .bg(SELECTION_BACKGROUND)
805
+ .add_modifier(Modifier::BOLD),
806
+ ),
807
+ ]))
808
+ .alignment(Alignment::Right);
809
+ frame.render_widget(action_hint, chunks[2]);
375
810
  }
376
- disable_raw_mode()?;
377
- println!();
378
-
379
- let disabled: Vec<String> = options
380
- .iter()
381
- .filter(|o| !o.enabled)
382
- .map(|o| o.key.to_string())
383
- .collect();
384
-
385
- config.disabled_tools = disabled;
386
- save_config(&config)?;
387
-
388
- println!("\x1b[32mSuccessfully updated agent tool configurations!\x1b[0m\n");
389
-
390
- let all_native = native_plugins();
391
- let allowed_plugins: std::collections::HashSet<String> = config
392
- .extra
393
- .get("allowedNativePlugins")
394
- .and_then(|v| v.as_array())
395
- .map(|arr| {
396
- arr.iter()
397
- .filter_map(|v| v.as_str().map(|s| s.to_string()))
398
- .collect()
399
- })
400
- .unwrap_or_else(|| {
401
- vec![
402
- "dev_tools".to_string(),
403
- "system_metrics".to_string(),
404
- "github".to_string(),
405
- ]
406
- .into_iter()
407
- .collect()
408
- });
409
-
410
- let mut native_options: Vec<ToolOption> = all_native
411
- .iter()
412
- .map(|p| {
413
- let has_all_permission = allowed_plugins.contains("*");
414
- let is_enabled = has_all_permission || allowed_plugins.contains(p.name);
415
- ToolOption {
416
- name: p.name,
417
- key: p.name,
418
- enabled: is_enabled,
419
- }
420
- })
421
- .collect();
422
811
 
423
- let mut native_cursor = 0;
424
- redraw_phase_2(&native_options, native_cursor);
425
- enable_raw_mode()?;
812
+ fn render_step_2_plugins(&mut self, frame: &mut ratatui::Frame, area: Rect) {
813
+ let block = Block::default()
814
+ .borders(Borders::ALL)
815
+ .border_type(BorderType::Rounded)
816
+ .border_style(Style::default().fg(SUBTLE))
817
+ .title(Span::styled(
818
+ " Step 2: Native Plugins Access ",
819
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
820
+ ));
426
821
 
427
- loop {
428
- match event::poll(std::time::Duration::from_millis(100)) {
429
- Ok(true) => {
430
- if let Event::Key(key_event) = event::read()?
431
- && key_event.kind == event::KeyEventKind::Press
432
- {
433
- let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
434
- && key_event
435
- .modifiers
436
- .contains(crossterm::event::KeyModifiers::CONTROL);
437
- if is_ctrl_c {
438
- disable_raw_mode()?;
439
- println!("\n\x1b[31mSetup cancelled.\x1b[0m");
440
- return Ok(None);
441
- }
822
+ let inner = block.inner(area);
823
+ frame.render_widget(block, area);
442
824
 
443
- match key_event.code {
444
- KeyCode::Up => {
445
- if native_cursor > 0 {
446
- native_cursor -= 1;
447
- } else {
448
- native_cursor = native_options.len() - 1;
449
- }
450
- disable_raw_mode()?;
451
- redraw_phase_2(&native_options, native_cursor);
452
- enable_raw_mode()?;
453
- }
454
- KeyCode::Down => {
455
- if native_cursor < native_options.len() - 1 {
456
- native_cursor += 1;
457
- } else {
458
- native_cursor = 0;
459
- }
460
- disable_raw_mode()?;
461
- redraw_phase_2(&native_options, native_cursor);
462
- enable_raw_mode()?;
463
- }
464
- KeyCode::Char(' ') => {
465
- native_options[native_cursor].enabled =
466
- !native_options[native_cursor].enabled;
467
- disable_raw_mode()?;
468
- redraw_phase_2(&native_options, native_cursor);
469
- enable_raw_mode()?;
470
- }
471
- KeyCode::Char('a') => {
472
- for opt in &mut native_options {
473
- opt.enabled = true;
474
- }
475
- disable_raw_mode()?;
476
- redraw_phase_2(&native_options, native_cursor);
477
- enable_raw_mode()?;
478
- }
479
- KeyCode::Char('i') => {
480
- for opt in &mut native_options {
481
- opt.enabled = !opt.enabled;
482
- }
483
- disable_raw_mode()?;
484
- redraw_phase_2(&native_options, native_cursor);
485
- enable_raw_mode()?;
486
- }
487
- KeyCode::Enter => {
488
- break;
489
- }
490
- _ => {}
491
- }
492
- }
825
+ let chunks = Layout::default()
826
+ .direction(Direction::Vertical)
827
+ .margin(1)
828
+ .constraints([
829
+ Constraint::Length(2), // Subtitle
830
+ Constraint::Min(8), // Plugins list
831
+ Constraint::Length(2), // Action hint
832
+ ])
833
+ .split(inner);
834
+
835
+ let subtitle = Paragraph::new(
836
+ "Select which native host plugins are allowed to run.\nPlugins provide extended local access like system diagnostics and developer tools."
837
+ ).style(Style::default().fg(MUTED));
838
+ frame.render_widget(subtitle, chunks[0]);
839
+
840
+ let mut list_items = Vec::new();
841
+ let list_inner_width = chunks[1].width.saturating_sub(2) as usize;
842
+
843
+ for (i, plugin) in self.plugins.iter().enumerate() {
844
+ let is_cursor = i == self.plugin_cursor;
845
+ let check_icon = if plugin.enabled { " ◉ " } else { " ○ " };
846
+ let cursor_prefix = if is_cursor { " › " } else { " " };
847
+
848
+ let mut lines = Vec::new();
849
+ if is_cursor {
850
+ let check_style = Style::default()
851
+ .fg(if plugin.enabled {
852
+ Color::Black
853
+ } else {
854
+ Color::DarkGray
855
+ })
856
+ .bg(SELECTION_BACKGROUND)
857
+ .add_modifier(Modifier::BOLD);
858
+
859
+ let desc_part = format!(" — {}", plugin.description);
860
+ let full_row_text =
861
+ format!("{cursor_prefix}{check_icon}{}{desc_part}", plugin.name);
862
+ let visual_width = UnicodeWidthStr::width(full_row_text.as_str());
863
+ let pad_len = list_inner_width.saturating_sub(visual_width);
864
+
865
+ lines.push(Line::from(vec![
866
+ Span::styled(
867
+ cursor_prefix,
868
+ Style::default()
869
+ .fg(SELECTION_TEXT)
870
+ .bg(SELECTION_BACKGROUND)
871
+ .add_modifier(Modifier::BOLD),
872
+ ),
873
+ Span::styled(check_icon, check_style),
874
+ Span::styled(
875
+ plugin.name,
876
+ Style::default()
877
+ .fg(SELECTION_TEXT)
878
+ .bg(SELECTION_BACKGROUND)
879
+ .add_modifier(Modifier::BOLD),
880
+ ),
881
+ Span::styled(
882
+ desc_part,
883
+ Style::default().fg(SELECTION_TEXT).bg(SELECTION_BACKGROUND),
884
+ ),
885
+ Span::styled(
886
+ " ".repeat(pad_len),
887
+ Style::default().bg(SELECTION_BACKGROUND),
888
+ ),
889
+ ]));
890
+ list_items.push(ListItem::new(lines));
891
+ } else {
892
+ let name_style = if plugin.enabled {
893
+ Style::default().fg(TEXT)
894
+ } else {
895
+ Style::default().fg(MUTED)
896
+ };
897
+ let check_color = if plugin.enabled { ACCENT } else { MUTED };
898
+
899
+ lines.push(Line::from(vec![
900
+ Span::raw(cursor_prefix),
901
+ Span::styled(
902
+ check_icon,
903
+ Style::default()
904
+ .fg(check_color)
905
+ .add_modifier(Modifier::BOLD),
906
+ ),
907
+ Span::styled(plugin.name, name_style),
908
+ Span::styled(
909
+ format!(" — {}", plugin.description),
910
+ Style::default().fg(MUTED),
911
+ ),
912
+ ]));
913
+ list_items.push(ListItem::new(lines));
493
914
  }
494
- Ok(false) => {}
495
- Err(_) => {
496
- break;
915
+ }
916
+
917
+ let allowed_count = self.plugins.iter().filter(|p| p.enabled).count();
918
+ let total_count = self.plugins.len();
919
+
920
+ let list_block = Block::default()
921
+ .borders(Borders::ALL)
922
+ .border_type(BorderType::Rounded)
923
+ .border_style(Style::default().fg(TEXT))
924
+ .title(Line::from(vec![
925
+ Span::styled(
926
+ " Allowed Native Plugins ",
927
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
928
+ ),
929
+ Span::styled(
930
+ " [ SELECTING ] ",
931
+ Style::default()
932
+ .fg(SELECTION_TEXT)
933
+ .bg(SELECTION_BACKGROUND)
934
+ .add_modifier(Modifier::BOLD),
935
+ ),
936
+ Span::styled(
937
+ format!(" [Allowed: {allowed_count} / {total_count} plugins] "),
938
+ Style::default().fg(TEXT),
939
+ ),
940
+ Span::styled(
941
+ " [Space: Toggle | a: All | i: Invert] ",
942
+ Style::default().fg(MUTED),
943
+ ),
944
+ ]));
945
+
946
+ let mut list_state = ListState::default();
947
+ list_state.select(Some(self.plugin_cursor));
948
+ frame.render_stateful_widget(
949
+ List::new(list_items).block(list_block),
950
+ chunks[1],
951
+ &mut list_state,
952
+ );
953
+
954
+ let action_hint = Paragraph::new(Line::from(vec![
955
+ Span::styled(" [ ⬅ Back (Esc) ] ", Style::default().fg(MUTED)),
956
+ Span::raw(" "),
957
+ Span::styled(
958
+ " [ Enter: Next Step (Launch Target) ➔ ] ",
959
+ Style::default()
960
+ .fg(SELECTION_TEXT)
961
+ .bg(SELECTION_BACKGROUND)
962
+ .add_modifier(Modifier::BOLD),
963
+ ),
964
+ ]))
965
+ .alignment(Alignment::Right);
966
+ frame.render_widget(action_hint, chunks[2]);
967
+ }
968
+
969
+ fn render_step_3_launch(&self, frame: &mut ratatui::Frame, area: Rect) {
970
+ let block = Block::default()
971
+ .borders(Borders::ALL)
972
+ .border_type(BorderType::Rounded)
973
+ .border_style(Style::default().fg(SUBTLE))
974
+ .title(Span::styled(
975
+ " Step 3: Choose Environment to Launch ",
976
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
977
+ ));
978
+
979
+ let inner = block.inner(area);
980
+ frame.render_widget(block, area);
981
+
982
+ let chunks = Layout::default()
983
+ .direction(Direction::Vertical)
984
+ .margin(1)
985
+ .constraints([
986
+ Constraint::Length(2), // Subtitle
987
+ Constraint::Min(8), // Target list
988
+ Constraint::Length(2), // Action hint
989
+ ])
990
+ .split(inner);
991
+
992
+ let subtitle = Paragraph::new(
993
+ "Configurations have been saved successfully.\nSelect an environment to launch Mint AI Agent, or choose Save & Exit."
994
+ ).style(Style::default().fg(MUTED));
995
+ frame.render_widget(subtitle, chunks[0]);
996
+
997
+ let list_inner_width = chunks[1].width.saturating_sub(2) as usize;
998
+ let mut list_items = Vec::new();
999
+
1000
+ for (i, opt) in self.launch_options.iter().enumerate() {
1001
+ let is_cursor = i == self.launch_cursor;
1002
+ let cursor_prefix = if is_cursor { " › " } else { " " };
1003
+ let radio = if is_cursor { " ◉ " } else { " ○ " };
1004
+
1005
+ let desc_part = format!(" — {}", opt.description);
1006
+ let full_row_text = format!("{cursor_prefix}{radio}{}{desc_part}", opt.title);
1007
+ let visual_width = UnicodeWidthStr::width(full_row_text.as_str());
1008
+ let pad_len = list_inner_width.saturating_sub(visual_width);
1009
+
1010
+ if is_cursor {
1011
+ let line = Line::from(vec![
1012
+ Span::styled(
1013
+ cursor_prefix,
1014
+ Style::default()
1015
+ .fg(SELECTION_TEXT)
1016
+ .bg(SELECTION_BACKGROUND)
1017
+ .add_modifier(Modifier::BOLD),
1018
+ ),
1019
+ Span::styled(
1020
+ radio,
1021
+ Style::default()
1022
+ .fg(SELECTION_TEXT)
1023
+ .bg(SELECTION_BACKGROUND)
1024
+ .add_modifier(Modifier::BOLD),
1025
+ ),
1026
+ Span::styled(
1027
+ opt.title,
1028
+ Style::default()
1029
+ .fg(SELECTION_TEXT)
1030
+ .bg(SELECTION_BACKGROUND)
1031
+ .add_modifier(Modifier::BOLD),
1032
+ ),
1033
+ Span::styled(
1034
+ desc_part,
1035
+ Style::default().fg(SELECTION_TEXT).bg(SELECTION_BACKGROUND),
1036
+ ),
1037
+ Span::styled(
1038
+ " ".repeat(pad_len),
1039
+ Style::default().bg(SELECTION_BACKGROUND),
1040
+ ),
1041
+ ]);
1042
+ list_items.push(ListItem::new(line));
1043
+ } else {
1044
+ let line = Line::from(vec![
1045
+ Span::raw(cursor_prefix),
1046
+ Span::styled(radio, Style::default().fg(MUTED)),
1047
+ Span::styled(opt.title, Style::default().fg(TEXT)),
1048
+ Span::styled(desc_part, Style::default().fg(MUTED)),
1049
+ ]);
1050
+ list_items.push(ListItem::new(line));
497
1051
  }
498
1052
  }
1053
+
1054
+ let list_block = Block::default()
1055
+ .borders(Borders::ALL)
1056
+ .border_type(BorderType::Rounded)
1057
+ .border_style(Style::default().fg(TEXT))
1058
+ .title(Line::from(vec![
1059
+ Span::styled(
1060
+ " Launch Targets ",
1061
+ Style::default().fg(TEXT).add_modifier(Modifier::BOLD),
1062
+ ),
1063
+ Span::styled(
1064
+ " [ SELECTING ] ",
1065
+ Style::default()
1066
+ .fg(SELECTION_TEXT)
1067
+ .bg(SELECTION_BACKGROUND)
1068
+ .add_modifier(Modifier::BOLD),
1069
+ ),
1070
+ Span::styled(
1071
+ " (↑/↓: Choose target | Enter: Execute) ",
1072
+ Style::default().fg(MUTED),
1073
+ ),
1074
+ ]));
1075
+
1076
+ let mut list_state = ListState::default();
1077
+ list_state.select(Some(self.launch_cursor));
1078
+ frame.render_stateful_widget(
1079
+ List::new(list_items).block(list_block),
1080
+ chunks[1],
1081
+ &mut list_state,
1082
+ );
1083
+
1084
+ let action_hint = Paragraph::new(Line::from(vec![
1085
+ Span::styled(" [ ⬅ Back (Esc) ] ", Style::default().fg(MUTED)),
1086
+ Span::raw(" "),
1087
+ Span::styled(
1088
+ " [ Enter: Confirm & Execute ➔ ] ",
1089
+ Style::default()
1090
+ .fg(SELECTION_TEXT)
1091
+ .bg(SELECTION_BACKGROUND)
1092
+ .add_modifier(Modifier::BOLD),
1093
+ ),
1094
+ ]))
1095
+ .alignment(Alignment::Right);
1096
+ frame.render_widget(action_hint, chunks[2]);
499
1097
  }
500
- disable_raw_mode()?;
501
- println!();
502
-
503
- let enabled_natives: Vec<serde_json::Value> = native_options
504
- .iter()
505
- .filter(|o| o.enabled)
506
- .map(|o| serde_json::Value::String(o.key.to_string()))
507
- .collect();
508
-
509
- config.extra.insert(
510
- "allowedNativePlugins".to_string(),
511
- serde_json::Value::Array(enabled_natives),
512
- );
513
- save_config(&config)?;
514
-
515
- println!("\x1b[32mSuccessfully updated native plugins configurations!\x1b[0m\n");
516
-
517
- let run_options = vec![
518
- ToolOption {
519
- name: "1. CLI (Interactive Terminal Assistant)",
520
- key: "cli",
521
- enabled: false,
522
- },
523
- ToolOption {
524
- name: "2. Desktop App (Download & Install)",
525
- key: "app_link",
526
- enabled: false,
527
- },
528
- ToolOption {
529
- name: "3. Web (Vite Web App UI)",
530
- key: "web",
531
- enabled: false,
532
- },
533
- ];
534
-
535
- let mut run_cursor = 0;
536
- redraw_phase_3(&run_options, run_cursor);
537
- enable_raw_mode()?;
538
1098
 
539
- loop {
540
- match event::poll(std::time::Duration::from_millis(100)) {
541
- Ok(true) => {
542
- if let Event::Key(key_event) = event::read()?
543
- && key_event.kind == event::KeyEventKind::Press
544
- {
545
- let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
546
- && key_event
547
- .modifiers
548
- .contains(crossterm::event::KeyModifiers::CONTROL);
549
- if is_ctrl_c {
550
- disable_raw_mode()?;
551
- println!("\n\x1b[31mRun selection cancelled.\x1b[0m");
552
- return Ok(None);
553
- }
1099
+ fn render_footer(&self, frame: &mut ratatui::Frame, area: Rect) {
1100
+ let shortcuts = match self.step {
1101
+ SetupStep::Tools => {
1102
+ "Focus: [Agent Tools] · [↑/↓] Navigate · [Space] Toggle · [a] All · [i] Invert · [Enter] Next · [Esc] Cancel"
1103
+ }
1104
+ SetupStep::Plugins => {
1105
+ "Focus: [Native Plugins] · [↑/↓] Navigate · [Space] Toggle · [a] All · [i] Invert · [Enter] Next · [Esc] Back"
1106
+ }
1107
+ SetupStep::Launch => {
1108
+ "Focus: [Launch Target] · [↑/↓] Select Target · [Enter] Confirm & Execute · [Esc] Back"
1109
+ }
1110
+ };
1111
+
1112
+ let chunks = Layout::default()
1113
+ .direction(Direction::Horizontal)
1114
+ .constraints([Constraint::Min(20), Constraint::Length(16)])
1115
+ .split(area);
1116
+
1117
+ frame.render_widget(
1118
+ Paragraph::new(format!(" {shortcuts}")).style(Style::default().fg(MUTED)),
1119
+ chunks[0],
1120
+ );
1121
+ frame.render_widget(
1122
+ Paragraph::new("Mint v1.18.0 ")
1123
+ .style(Style::default().fg(SUBTLE))
1124
+ .alignment(Alignment::Right),
1125
+ chunks[1],
1126
+ );
1127
+ }
1128
+
1129
+ // ─────────────────────────────────────────────────────────────────────────
1130
+ // Key Event Handlers
1131
+ // ─────────────────────────────────────────────────────────────────────────
1132
+
1133
+ pub fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Option<String>> {
1134
+ let is_ctrl_c =
1135
+ matches!(key.code, KeyCode::Char('c')) && key.modifiers.contains(KeyModifiers::CONTROL);
1136
+ if is_ctrl_c {
1137
+ return Some(None);
1138
+ }
554
1139
 
555
- match key_event.code {
556
- KeyCode::Up => {
557
- if run_cursor > 0 {
558
- run_cursor -= 1;
559
- } else {
560
- run_cursor = run_options.len() - 1;
561
- }
562
- disable_raw_mode()?;
563
- redraw_phase_3(&run_options, run_cursor);
564
- enable_raw_mode()?;
565
- }
566
- KeyCode::Down => {
567
- if run_cursor < run_options.len() - 1 {
568
- run_cursor += 1;
569
- } else {
570
- run_cursor = 0;
571
- }
572
- disable_raw_mode()?;
573
- redraw_phase_3(&run_options, run_cursor);
574
- enable_raw_mode()?;
575
- }
576
- KeyCode::Enter => {
577
- break;
578
- }
579
- _ => {}
1140
+ match self.step {
1141
+ SetupStep::Tools => match key.code {
1142
+ KeyCode::Up => {
1143
+ if self.tool_cursor > 0 {
1144
+ self.tool_cursor -= 1;
1145
+ } else if !self.tools.is_empty() {
1146
+ self.tool_cursor = self.tools.len() - 1;
580
1147
  }
581
1148
  }
582
- }
583
- Ok(false) => {}
584
- Err(_) => {
585
- break;
586
- }
1149
+ KeyCode::Down => {
1150
+ if !self.tools.is_empty() && self.tool_cursor < self.tools.len() - 1 {
1151
+ self.tool_cursor += 1;
1152
+ } else {
1153
+ self.tool_cursor = 0;
1154
+ }
1155
+ }
1156
+ KeyCode::Char(' ') => {
1157
+ if let Some(tool) = self.tools.get_mut(self.tool_cursor) {
1158
+ tool.enabled = !tool.enabled;
1159
+ }
1160
+ }
1161
+ KeyCode::Char('a') => {
1162
+ for tool in &mut self.tools {
1163
+ tool.enabled = true;
1164
+ }
1165
+ }
1166
+ KeyCode::Char('i') => {
1167
+ for tool in &mut self.tools {
1168
+ tool.enabled = !tool.enabled;
1169
+ }
1170
+ }
1171
+ KeyCode::Enter => {
1172
+ self.step = SetupStep::Plugins;
1173
+ }
1174
+ KeyCode::Esc => {
1175
+ return Some(None);
1176
+ }
1177
+ _ => {}
1178
+ },
1179
+ SetupStep::Plugins => match key.code {
1180
+ KeyCode::Up => {
1181
+ if self.plugin_cursor > 0 {
1182
+ self.plugin_cursor -= 1;
1183
+ } else if !self.plugins.is_empty() {
1184
+ self.plugin_cursor = self.plugins.len() - 1;
1185
+ }
1186
+ }
1187
+ KeyCode::Down => {
1188
+ if !self.plugins.is_empty() && self.plugin_cursor < self.plugins.len() - 1 {
1189
+ self.plugin_cursor += 1;
1190
+ } else {
1191
+ self.plugin_cursor = 0;
1192
+ }
1193
+ }
1194
+ KeyCode::Char(' ') => {
1195
+ if let Some(plugin) = self.plugins.get_mut(self.plugin_cursor) {
1196
+ plugin.enabled = !plugin.enabled;
1197
+ }
1198
+ }
1199
+ KeyCode::Char('a') => {
1200
+ for plugin in &mut self.plugins {
1201
+ plugin.enabled = true;
1202
+ }
1203
+ }
1204
+ KeyCode::Char('i') => {
1205
+ for plugin in &mut self.plugins {
1206
+ plugin.enabled = !plugin.enabled;
1207
+ }
1208
+ }
1209
+ KeyCode::Enter => {
1210
+ let _ = self.save_changes();
1211
+ self.step = SetupStep::Launch;
1212
+ }
1213
+ KeyCode::Esc => {
1214
+ self.step = SetupStep::Tools;
1215
+ }
1216
+ _ => {}
1217
+ },
1218
+ SetupStep::Launch => match key.code {
1219
+ KeyCode::Up => {
1220
+ if self.launch_cursor > 0 {
1221
+ self.launch_cursor -= 1;
1222
+ } else if !self.launch_options.is_empty() {
1223
+ self.launch_cursor = self.launch_options.len() - 1;
1224
+ }
1225
+ }
1226
+ KeyCode::Down => {
1227
+ if !self.launch_options.is_empty()
1228
+ && self.launch_cursor < self.launch_options.len() - 1
1229
+ {
1230
+ self.launch_cursor += 1;
1231
+ } else {
1232
+ self.launch_cursor = 0;
1233
+ }
1234
+ }
1235
+ KeyCode::Enter => {
1236
+ let _ = self.save_changes();
1237
+ let target_key = self.launch_options[self.launch_cursor]
1238
+ .key
1239
+ .map(|s| s.to_string());
1240
+ return Some(target_key);
1241
+ }
1242
+ KeyCode::Esc => {
1243
+ self.step = SetupStep::Plugins;
1244
+ }
1245
+ _ => {}
1246
+ },
587
1247
  }
1248
+ None
588
1249
  }
589
- disable_raw_mode()?;
590
- println!();
591
1250
 
592
- Ok(Some(run_options[run_cursor].key.to_string()))
593
- }
1251
+ pub fn run_with_terminal<B: Backend>(
1252
+ &mut self,
1253
+ terminal: &mut Terminal<B>,
1254
+ ) -> Result<Option<String>> {
1255
+ loop {
1256
+ terminal.draw(|f| self.render(f))?;
594
1257
 
595
- fn print_options(options: &[ToolOption], cursor: usize) {
596
- for (i, opt) in options.iter().enumerate() {
597
- let checkbox = if opt.enabled {
598
- "\x1b[32m◉\x1b[0m"
599
- } else {
600
- "\x1b[90m○\x1b[0m"
601
- };
602
- if i == cursor {
603
- println!(
604
- " {ANSI_BLUE}❯\x1b[0m {} {ANSI_BLUE}{}\x1b[0m",
605
- checkbox, opt.name
606
- );
607
- } else {
608
- println!(" {} {}", checkbox, opt.name);
1258
+ if event::poll(Duration::from_millis(50))?
1259
+ && let Ok(Event::Key(key)) = event::read()
1260
+ && key.kind == crossterm::event::KeyEventKind::Press
1261
+ && let Some(outcome) = self.handle_key(key)
1262
+ {
1263
+ return Ok(outcome);
1264
+ }
609
1265
  }
610
1266
  }
611
- let _ = io::stdout().flush();
612
1267
  }
613
1268
 
614
- fn print_run_options(options: &[ToolOption], cursor: usize) {
615
- for (i, opt) in options.iter().enumerate() {
616
- if i == cursor {
617
- println!(" {ANSI_BLUE}❯\x1b[0m {ANSI_BLUE}{}\x1b[0m", opt.name);
618
- } else {
619
- println!(" {}", opt.name);
1269
+ // ─────────────────────────────────────────────────────────────────────────────
1270
+ // Public Entrypoint for `mint setup`
1271
+ // ─────────────────────────────────────────────────────────────────────────────
1272
+
1273
+ pub async fn run() -> Result<Option<String>> {
1274
+ let config = load_config()?;
1275
+ enable_raw_mode()?;
1276
+ let mut stdout = std::io::stdout();
1277
+ let _ = stdout.execute(EnterAlternateScreen);
1278
+ let _ = stdout.execute(Hide);
1279
+ let backend = CrosstermBackend::new(stdout);
1280
+ let mut terminal = Terminal::new(backend)?;
1281
+
1282
+ let mut wizard = SetupWizard::new(config);
1283
+ let outcome = wizard.run_with_terminal(&mut terminal);
1284
+
1285
+ let _ = disable_raw_mode();
1286
+ let mut stdout = std::io::stdout();
1287
+ let _ = stdout.execute(Show);
1288
+ let _ = stdout.execute(LeaveAlternateScreen);
1289
+
1290
+ match outcome {
1291
+ Ok(Some(ref target)) => {
1292
+ println!("\x1b[32m✔ Setup complete. Launching {target}...\x1b[0m\n");
1293
+ }
1294
+ Ok(None) => {
1295
+ println!("\x1b[32m✔ Configuration saved. Exiting setup.\x1b[0m\n");
1296
+ }
1297
+ Err(ref e) => {
1298
+ eprintln!("\x1b[31mSetup error: {e}\x1b[0m");
620
1299
  }
621
1300
  }
622
- let _ = io::stdout().flush();
623
- }
624
1301
 
625
- fn redraw_phase_1(options: &[ToolOption], cursor: usize) {
626
- print!("\x1b[2J\x1b[1;1H");
627
- println!("{ANSI_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
628
- println!("\x1b[32m Mint CLI Tool Manager Wizard\x1b[0m");
629
- println!("{ANSI_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
630
- println!("Configure which agent tools are enabled or disabled:");
631
- println!(
632
- " \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
633
- );
634
- println!();
635
- print_options(options, cursor);
1302
+ outcome
636
1303
  }
637
1304
 
638
- fn redraw_phase_2(options: &[ToolOption], cursor: usize) {
639
- print!("\x1b[2J\x1b[1;1H");
640
- println!("{ANSI_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
641
- println!("\x1b[32m Configure Native Plugins Access\x1b[0m");
642
- println!("{ANSI_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
643
- println!("Select which native plugins are allowed to run:");
644
- println!(
645
- " \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
646
- );
647
- println!();
648
- print_options(options, cursor);
649
- }
1305
+ // ─────────────────────────────────────────────────────────────────────────────
1306
+ // Unit Tests
1307
+ // ─────────────────────────────────────────────────────────────────────────────
1308
+
1309
+ #[cfg(test)]
1310
+ mod tests {
1311
+ use super::*;
1312
+
1313
+ #[test]
1314
+ fn test_setup_wizard_initialization() {
1315
+ let mut config = MintConfig::default();
1316
+ config.disabled_tools = vec!["web_search".to_string(), "video_trim".to_string()];
1317
+
1318
+ let wizard = SetupWizard::new(config);
1319
+ assert_eq!(wizard.step, SetupStep::Tools);
1320
+ assert_eq!(wizard.tools.len(), 49);
1321
+
1322
+ // Verify disabled tools match config
1323
+ let web_search = wizard.tools.iter().find(|t| t.key == "web_search").unwrap();
1324
+ assert!(!web_search.enabled);
1325
+
1326
+ let list_files = wizard.tools.iter().find(|t| t.key == "list_files").unwrap();
1327
+ assert!(list_files.enabled);
1328
+
1329
+ assert_eq!(wizard.launch_options.len(), 4);
1330
+ }
1331
+
1332
+ #[test]
1333
+ fn test_tool_toggle_and_shortcuts() {
1334
+ let config = MintConfig::default();
1335
+ let mut wizard = SetupWizard::new(config);
650
1336
 
651
- fn redraw_phase_3(options: &[ToolOption], cursor: usize) {
652
- print!("\x1b[2J\x1b[1;1H");
653
- println!("{ANSI_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
654
- println!("\x1b[32m Choose where to run Mint AI Agent\x1b[0m");
655
- println!("{ANSI_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
656
- println!("Select the environment you want to launch:");
657
- println!(" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Enter: Confirm]\x1b[0m");
658
- println!();
659
- print_run_options(options, cursor);
1337
+ let initial_status = wizard.tools[0].enabled;
1338
+ let space_key = crossterm::event::KeyEvent::new(KeyCode::Char(' '), KeyModifiers::empty());
1339
+ wizard.handle_key(space_key);
1340
+ assert_eq!(wizard.tools[0].enabled, !initial_status);
1341
+
1342
+ // Test 'a' (enable all)
1343
+ let a_key = crossterm::event::KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
1344
+ wizard.handle_key(a_key);
1345
+ assert!(wizard.tools.iter().all(|t| t.enabled));
1346
+
1347
+ // Test 'i' (invert)
1348
+ let i_key = crossterm::event::KeyEvent::new(KeyCode::Char('i'), KeyModifiers::empty());
1349
+ wizard.handle_key(i_key);
1350
+ assert!(wizard.tools.iter().all(|t| !t.enabled));
1351
+ }
1352
+
1353
+ #[test]
1354
+ fn test_setup_step_transitions() {
1355
+ let config = MintConfig::default();
1356
+ let mut wizard = SetupWizard::new(config);
1357
+ assert_eq!(wizard.step, SetupStep::Tools);
1358
+
1359
+ let enter_key = crossterm::event::KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
1360
+ let esc_key = crossterm::event::KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
1361
+
1362
+ // Tools -> Plugins
1363
+ wizard.handle_key(enter_key);
1364
+ assert_eq!(wizard.step, SetupStep::Plugins);
1365
+
1366
+ // Plugins -> Launch
1367
+ wizard.handle_key(enter_key);
1368
+ assert_eq!(wizard.step, SetupStep::Launch);
1369
+
1370
+ // Launch -> Plugins (Esc)
1371
+ wizard.handle_key(esc_key);
1372
+ assert_eq!(wizard.step, SetupStep::Plugins);
1373
+
1374
+ // Plugins -> Tools (Esc)
1375
+ wizard.handle_key(esc_key);
1376
+ assert_eq!(wizard.step, SetupStep::Tools);
1377
+ }
660
1378
  }