@pheem49/mint 1.14.0 → 1.15.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.
- package/CLAUDE.md +1 -1
- package/Cargo.lock +291 -11
- package/Cargo.toml +1 -1
- package/README.md +71 -9
- package/ROADMAP.md +30 -0
- package/Release_Note.md +579 -465
- package/crates/mint-cli/Cargo.toml +3 -1
- package/crates/mint-cli/src/agent/approval_prompts.rs +16 -3
- package/crates/mint-cli/src/agent/live_status.rs +367 -118
- package/crates/mint-cli/src/agent/markdown_render.rs +601 -61
- package/crates/mint-cli/src/agent.rs +452 -215
- package/crates/mint-cli/src/commands/agent.rs +16 -21
- package/crates/mint-cli/src/commands/eval.rs +9 -5
- package/crates/mint-cli/src/commands/git.rs +204 -0
- package/crates/mint-cli/src/commands/integrations.rs +28 -0
- package/crates/mint-cli/src/commands/knowledge.rs +1 -0
- package/crates/mint-cli/src/commands/mod.rs +37 -2
- package/crates/mint-cli/src/commands/tailnet_web.rs +235 -0
- package/crates/mint-cli/src/image.rs +26 -3
- package/crates/mint-cli/src/interactive/chat_tui.rs +4742 -0
- package/crates/mint-cli/src/interactive/command_ui.rs +477 -0
- package/crates/mint-cli/src/interactive/format.rs +8 -0
- package/crates/mint-cli/src/interactive/input_box.rs +38 -2
- package/crates/mint-cli/src/interactive/picker.rs +180 -37
- package/crates/mint-cli/src/interactive/resume_picker.rs +686 -0
- package/crates/mint-cli/src/interactive/slash_commands.rs +868 -450
- package/crates/mint-cli/src/interactive/thought_viewer.rs +286 -0
- package/crates/mint-cli/src/interactive.rs +400 -136
- package/crates/mint-cli/src/main.rs +84 -23
- package/crates/mint-cli/src/markdown.rs +12 -5
- package/crates/mint-cli/src/onboard.rs +32 -30
- package/crates/mint-cli/src/plugins_cli.rs +8 -5
- package/crates/mint-cli/src/setup.rs +10 -8
- package/crates/mint-cli/src/terminal_theme.rs +187 -0
- package/crates/mint-cli/src/updater.rs +56 -21
- package/crates/mint-core/src/agent/agent_loop.rs +1 -0
- package/crates/mint-core/src/agent/chat.rs +843 -177
- package/crates/mint-core/src/agent/code_tools.rs +83 -2
- package/crates/mint-core/src/agent/memory.rs +658 -20
- package/crates/mint-core/src/agent/skills.rs +1 -1
- package/crates/mint-core/src/api_server/routes/chat.rs +31 -5
- package/crates/mint-core/src/api_server/routes/linked_folders.rs +118 -3
- package/crates/mint-core/src/api_server/routes/misc.rs +10 -14
- package/crates/mint-core/src/api_server/routes/sessions.rs +44 -0
- package/crates/mint-core/src/api_server.rs +22 -3
- package/crates/mint-core/src/avatar_bridge.rs +2 -0
- package/crates/mint-core/src/browser/cdp.rs +74 -83
- package/crates/mint-core/src/browser/evidence.rs +260 -0
- package/crates/mint-core/src/browser/input.rs +1 -1
- package/crates/mint-core/src/browser/interact.rs +98 -124
- package/crates/mint-core/src/browser/lifecycle.rs +75 -4
- package/crates/mint-core/src/browser/mod.rs +16 -2
- package/crates/mint-core/src/browser/navigate.rs +27 -19
- package/crates/mint-core/src/browser/observe.js +40 -0
- package/crates/mint-core/src/browser/session.rs +1558 -0
- package/crates/mint-core/src/git/branches.rs +348 -0
- package/crates/mint-core/src/git/checkpoint.rs +40 -0
- package/crates/mint-core/src/git/diff.rs +206 -0
- package/crates/mint-core/src/git/mod.rs +10 -2
- package/crates/mint-core/src/integrations/channels.rs +1 -0
- package/crates/mint-core/src/lib.rs +22 -14
- package/crates/mint-core/src/live_sync.rs +28 -9
- package/crates/mint-core/src/media/auto_shorts.rs +1 -0
- package/crates/mint-core/src/media/gemini_live.rs +12 -1
- package/crates/mint-core/src/media/mic_transcribe.rs +1 -0
- package/crates/mint-core/src/media/subtitle.rs +1 -0
- package/crates/mint-core/src/orchestration/context_render.rs +1 -0
- package/crates/mint-core/src/orchestration/memory_skill.rs +6 -1
- package/crates/mint-core/src/orchestration/mod.rs +1024 -271
- package/crates/mint-core/src/orchestration/tools/browser.rs +16 -139
- package/crates/mint-core/src/orchestration/tools/misc.rs +2 -1
- package/crates/mint-core/src/orchestration/tools/web.rs +1 -1
- package/crates/mint-core/src/prompts/agent.rs +58 -29
- package/crates/mint-core/src/prompts/chat.rs +1 -1
- package/crates/mint-core/src/prompts/persona.rs +1 -1
- package/crates/mint-core/src/prompts/tool_catalog.rs +93 -25
- package/crates/mint-core/src/search/linked_folders/index.rs +732 -0
- package/crates/mint-core/src/search/linked_folders.rs +391 -130
- package/crates/mint-core/src/slash/mod.rs +470 -22
- package/crates/mint-core/src/slash/model_fetcher.rs +12 -3
- package/crates/mint-core/src/slash/models.rs +66 -10
- package/crates/mint-core/src/system/config.rs +230 -0
- package/crates/mint-core/src/system/workspace.rs +228 -0
- package/crates/mint-core/src/system.rs +1 -0
- package/crates/mint-core/tests/memory_persistence.rs +39 -0
- package/mint-presentation-demo.mp4 +0 -0
- package/out/web/assets/CliSessionsView-BxUVWEEL.js +1 -0
- package/out/web/assets/CliSessionsView-CBDTXuQv.css +1 -0
- package/out/web/assets/CodeReviewPage-DkMgfcos.css +1 -0
- package/out/web/assets/CodeReviewPage-jKlmLJwT.js +3 -0
- package/out/web/assets/CommandPalette-DnwPA5hy.js +1 -0
- package/out/web/assets/ImageStudioPanel-BR1XFwhR.js +1 -0
- package/out/web/assets/ImageStudioPanel-D0lnhQV5.css +1 -0
- package/out/web/assets/LinkedFoldersView-bi-CWQ-I.js +1 -0
- package/out/web/assets/McpRegistryPicker-D5tEFnyq.js +1 -0
- package/out/web/assets/McpServersView-BGG1dhFM.js +1 -0
- package/out/web/assets/MintDashboard-BIglDEWX.js +20 -0
- package/out/web/assets/MintDashboard-DSMywSgs.css +1 -0
- package/out/web/assets/ModelPanel-CbJ1PFoZ.js +1 -0
- package/out/web/assets/PicturesLibrary-CQP0fLP1.css +1 -0
- package/out/web/assets/PicturesLibrary-DoYN7T1p.js +1 -0
- package/out/web/assets/PluginsView-CU1HWXGk.js +1 -0
- package/out/web/assets/ScheduledTasksView-Bnl36y2N.js +1 -0
- package/out/web/assets/SettingsWindow-DhWLAn9T.css +1 -0
- package/out/web/assets/SettingsWindow-Dw8L01pw.js +2 -0
- package/out/web/assets/SkillsView-cnl8aY5p.js +2 -0
- package/out/web/assets/ToolSurfacePage-CgsQw6l5.css +1 -0
- package/out/web/assets/ToolSurfacePage-Cz2VJAPZ.js +37 -0
- package/out/web/assets/VeoStudioPanel-D4JnERWR.js +4 -0
- package/out/web/assets/VeoStudioPanel-DOj7_WR3.css +1 -0
- package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +1 -0
- package/out/web/assets/{abnfDiagram-VCTEODGH-DAOBA7ZG.js → abnfDiagram-VCTEODGH-ByMyfanq.js} +1 -1
- package/out/web/assets/{architecture-7GRP2DOG-CgSwQOPQ.js → architecture-7GRP2DOG-CLq2kEOB.js} +1 -1
- package/out/web/assets/{architectureDiagram-5GKGNRK7-CYp3yupP.js → architectureDiagram-5GKGNRK7-6PoVSmNG.js} +1 -1
- package/out/web/assets/{blockDiagram-NRAW4CY4-CyWybDWr.js → blockDiagram-NRAW4CY4-cKw9mAKi.js} +1 -1
- package/out/web/assets/{c4Diagram-UCG6FXSJ-BvY3U709.js → c4Diagram-UCG6FXSJ-Cqy8N5Ge.js} +1 -1
- package/out/web/assets/{chunk-75Z2AOVW-TLXCoZYv.js → chunk-75Z2AOVW-3qybijF5.js} +1 -1
- package/out/web/assets/chunk-CKBBP62Z-cIrXq6jN.js +2 -0
- package/out/web/assets/{chunk-CLGD4ZFX-xjsKtNS7.js → chunk-CLGD4ZFX-CndDQkww.js} +1 -1
- package/out/web/assets/chunk-DSNCTWBM-Cc4NxvgD.js +2 -0
- package/out/web/assets/chunk-DU6HZSFF-drzh6GsU.js +128 -0
- package/out/web/assets/{chunk-F27PBJKO-BFpgF55A.js → chunk-F27PBJKO-FiY35l4v.js} +1 -1
- package/out/web/assets/{chunk-G27WJ6UU-DfF7qW-C.js → chunk-G27WJ6UU-Bjt9Il4A.js} +1 -1
- package/out/web/assets/{chunk-GMAD6QVW-C-558kh_.js → chunk-GMAD6QVW-Bppi3Ctd.js} +1 -1
- package/out/web/assets/{chunk-JQ64N6SF-wJmeGKAw.js → chunk-JQ64N6SF-DGg5kcEV.js} +1 -1
- package/out/web/assets/{chunk-LCL6LL3I-C1wHD8Ie.js → chunk-LCL6LL3I-CCYcjd7p.js} +1 -1
- package/out/web/assets/{chunk-OBVCFTLP-BsRTPBqs.js → chunk-OBVCFTLP-DJeNil5M.js} +1 -1
- package/out/web/assets/{chunk-P2QGCYS3-HZIiM7Ni.js → chunk-P2QGCYS3-lRzkRjBB.js} +1 -1
- package/out/web/assets/{chunk-POPQ4Y6H-Dk9wvVb0.js → chunk-POPQ4Y6H-DouASMBW.js} +1 -1
- package/out/web/assets/{chunk-PWAF6VOD-BmjYLNbq.js → chunk-PWAF6VOD-DMmD1vVf.js} +1 -1
- package/out/web/assets/{chunk-R7TYR2AO-CaHIf-lK.js → chunk-R7TYR2AO-CYLA5UC5.js} +1 -1
- package/out/web/assets/{chunk-RHFEMEQ7-_T561hOU.js → chunk-RHFEMEQ7-BiXwEcUT.js} +1 -1
- package/out/web/assets/{chunk-SVP7TREG-CM-GGYoY.js → chunk-SVP7TREG-BV-zPslF.js} +1 -1
- package/out/web/assets/{chunk-TEH6E4GO-K2A8Vg2C.js → chunk-TEH6E4GO-B2RGEohZ.js} +1 -1
- package/out/web/assets/classDiagram-DTDB5LWJ-Dq0smRWk.js +1 -0
- package/out/web/assets/classDiagram-v2-JRS7N3AN-Dq0smRWk.js +1 -0
- package/out/web/assets/config-C-ZFtipU.css +1 -0
- package/out/web/assets/config-eEDngJXO.js +1 -0
- package/out/web/assets/core-hQ2VA9Ti.js +1 -0
- package/out/web/assets/{cynefin-OW5HDTMX-ClzaB6cc.js → cynefin-OW5HDTMX-Cas4csHM.js} +1 -1
- package/out/web/assets/{cynefinDiagram-5FMLGOSQ-D_JzZBzf.js → cynefinDiagram-5FMLGOSQ-Bbpj66Y3.js} +1 -1
- package/out/web/assets/{dagre-3AP2YEHR-BDuUeMHj.js → dagre-3AP2YEHR-B0-2RDEd.js} +1 -1
- package/out/web/assets/{diagram-S7CK7UJ4-DgtcfE8P.js → diagram-S7CK7UJ4-BdzEjUyH.js} +1 -1
- package/out/web/assets/{diagram-UQ7AKVKN-DTLoEYti.js → diagram-UQ7AKVKN-CJDwyPxF.js} +1 -1
- package/out/web/assets/{diagram-VSXAHHWV-ChGl9pXz.js → diagram-VSXAHHWV-BxaG1r_B.js} +1 -1
- package/out/web/assets/{diagram-VX7I27RA-Be8DWz0S.js → diagram-VX7I27RA-BMmUqm8f.js} +1 -1
- package/out/web/assets/{diagram-Z3DM3KII-BMXjRTOF.js → diagram-Z3DM3KII-CFtd03eE.js} +1 -1
- package/out/web/assets/dist-js-Bf2bhoku.js +1 -0
- package/out/web/assets/{ebnfDiagram-PWID7BFC-8Q9pl1CJ.js → ebnfDiagram-PWID7BFC-DwAen1hO.js} +1 -1
- package/out/web/assets/{erDiagram-SSCWMZ5O-DjH8Lnkn.js → erDiagram-SSCWMZ5O-BCZSHACj.js} +1 -1
- package/out/web/assets/event-ChBVpRy0.js +1 -0
- package/out/web/assets/{eventmodeling-NTZA5JFV-BKUH_DOp.js → eventmodeling-NTZA5JFV-XHStE4E_.js} +1 -1
- package/out/web/assets/fira-code-latin-600-normal-BmSSHshu.woff +0 -0
- package/out/web/assets/fira-code-latin-600-normal-CvYZpPHg.woff2 +0 -0
- package/out/web/assets/fira-code-symbols2-600-normal-Dvq6IQE5.woff +0 -0
- package/out/web/assets/flowDiagram-A5DVABFB-BLg7nEcd.js +1 -0
- package/out/web/assets/{ganttDiagram-EL5Y4UJY-D7fZKJNM.js → ganttDiagram-EL5Y4UJY-BAEu1Cql.js} +1 -1
- package/out/web/assets/{gitGraph-4MIJSDKK-BypWaaBU.js → gitGraph-4MIJSDKK-r32Vyper.js} +1 -1
- package/out/web/assets/{gitGraphDiagram-WWUBYQGX-DWiw_3bq.js → gitGraphDiagram-WWUBYQGX-z9o9qt6V.js} +1 -1
- package/out/web/assets/index-Btsf4L8M.css +1 -0
- package/out/web/assets/index-D8D9MeRA.js +11 -0
- package/out/web/assets/{info-A6RAGUB7-KkAUxT4j.js → info-A6RAGUB7-BvLaC2cw.js} +1 -1
- package/out/web/assets/{infoDiagram-RXCK75RN-BNb6E48y.js → infoDiagram-RXCK75RN-Be6WAXXw.js} +1 -1
- package/out/web/assets/{ishikawaDiagram-5VMMS53U-Caa-JoAK.js → ishikawaDiagram-5VMMS53U-DZjoHJHC.js} +1 -1
- package/out/web/assets/{journeyDiagram-EYS64GPL-DOrmFGz6.js → journeyDiagram-EYS64GPL-CtVkjkGm.js} +1 -1
- package/out/web/assets/{kanban-definition-3QL26DDD-BOWCYcK0.js → kanban-definition-3QL26DDD-CW5FMv1w.js} +1 -1
- package/out/web/assets/lucide-react-Ip3L6Jtn.js +1 -0
- package/out/web/assets/markdown-BSeeUbJH.js +38 -0
- package/out/web/assets/{mermaid-parser.core-BQqjCkS_.js → mermaid-parser.core-aUgCqsZ5.js} +3 -3
- package/out/web/assets/mermaid.core-D57sEiSL.js +11 -0
- package/out/web/assets/{mindmap-definition-FBJOCRG2-jTceWpr0.js → mindmap-definition-FBJOCRG2-DAmSpM3a.js} +1 -1
- package/out/web/assets/modelManager-tMVmJVAW.js +2 -0
- package/out/web/assets/oauthManager-9jxicDWJ.js +1 -0
- package/out/web/assets/{packet-AYTQ26CC-CJ5RXgol.js → packet-AYTQ26CC-DJ27iIoJ.js} +1 -1
- package/out/web/assets/{pegDiagram-XKGWAZYB-y8wMtd0y.js → pegDiagram-XKGWAZYB-Bj3ii-gS.js} +1 -1
- package/out/web/assets/{pie-WAS4IAKB-72fTBbl4.js → pie-WAS4IAKB-DG5mCUbc.js} +1 -1
- package/out/web/assets/{pieDiagram-E7YTZNPT-e0O02qWq.js → pieDiagram-E7YTZNPT-BrJLPnDA.js} +1 -1
- package/out/web/assets/prompt-latin-700-normal-BfbftDSq.woff +0 -0
- package/out/web/assets/prompt-latin-700-normal-I2gc831J.woff2 +0 -0
- package/out/web/assets/prompt-thai-700-normal-Cb5ilR-c.woff +0 -0
- package/out/web/assets/prompt-thai-700-normal-Cg4aQ0Nn.woff2 +0 -0
- package/out/web/assets/{quadrantDiagram-AXDQQJYC-C9phYLom.js → quadrantDiagram-AXDQQJYC-3x_TmF3j.js} +1 -1
- package/out/web/assets/{radar-RG4KPBEZ-BAU2hzUT.js → radar-RG4KPBEZ-CrXaTTuV.js} +1 -1
- package/out/web/assets/{railroad-74A4TZTK-B0bCAVHC.js → railroad-74A4TZTK-BpqMH9BE.js} +1 -1
- package/out/web/assets/{railroad-abnf-HS5TGJTU-WhxO7ndE.js → railroad-abnf-HS5TGJTU-DHI0uLlV.js} +1 -1
- package/out/web/assets/{railroad-ebnf-LZEXJU2U-BkA0m7-F.js → railroad-ebnf-LZEXJU2U-CQCb316y.js} +1 -1
- package/out/web/assets/{railroad-peg-WCYAUIDC-BsVpHIaj.js → railroad-peg-WCYAUIDC-Ph1giKkH.js} +1 -1
- package/out/web/assets/{railroadDiagram-O6MQD6OU-CyOLN65Q.js → railroadDiagram-O6MQD6OU-BRq6wIxx.js} +1 -1
- package/out/web/assets/{requirementDiagram-EFPCY7ZU-DQXJ7Dud.js → requirementDiagram-EFPCY7ZU-DIJj6G1g.js} +1 -1
- package/out/web/assets/{sankeyDiagram-P5KCCOFB-B2XLyIIc.js → sankeyDiagram-P5KCCOFB-DZajSryq.js} +1 -1
- package/out/web/assets/{sequenceDiagram-WJ2MYXX4-DGQY4wwm.js → sequenceDiagram-WJ2MYXX4-DxJCWyZS.js} +1 -1
- package/out/web/assets/{stateDiagram-HBIQ2CUA-DHZpDwNy.js → stateDiagram-HBIQ2CUA-k6voi8Vf.js} +1 -1
- package/out/web/assets/stateDiagram-v2-4QOOHH4V-DeEc0lGZ.js +1 -0
- package/out/web/assets/studio-B1mk40wD.js +1 -0
- package/out/web/assets/{swimlanes-XN3QIQJK-Bn_jcZz-.js → swimlanes-XN3QIQJK-igYhTjXB.js} +1 -1
- package/out/web/assets/swimlanesDiagram-VK2B7HYN-DpVik9v7.js +8 -0
- package/out/web/assets/{timeline-definition-24CTP7MA-CdTERuJZ.js → timeline-definition-24CTP7MA-Dke2p93p.js} +1 -1
- package/out/web/assets/{treeView-Q6P3EWNA-TKBKQG6R.js → treeView-Q6P3EWNA-hNELnXUs.js} +1 -1
- package/out/web/assets/{treemap-WGGIJYW6-BP7gQoiC.js → treemap-WGGIJYW6-D2B6wDsx.js} +1 -1
- package/out/web/assets/{vennDiagram-4TSXK5OY-BHUmxDuK.js → vennDiagram-4TSXK5OY-CDkW7rUP.js} +1 -1
- package/out/web/assets/{wardley-WFR3VGLG-9bct5AAN.js → wardley-WFR3VGLG-BECdXETi.js} +1 -1
- package/out/web/assets/{wardleyDiagram-VM6X3IG4-BfJwDDso.js → wardleyDiagram-VM6X3IG4-B5BycpQ4.js} +1 -1
- package/out/web/assets/window-5qYXKoW7.js +1 -0
- package/out/web/assets/{xychartDiagram-S5SC5T6Z-Dqd9Mvpo.js → xychartDiagram-S5SC5T6Z-CtJS3b--.js} +1 -1
- package/out/web/index-web.html +3 -4
- package/out/web/index.html +3 -4
- package/package.json +17 -6
- package/skills-lock.json +36 -0
- package/slash-commands.json +9 -0
- package/src/renderer/index-web.html +1 -0
- package/src/renderer/index.html +1 -0
- package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/renderer/shared/agentProgress.ts +168 -3
- package/src/renderer/shared/components/AgentActivityDrawer.tsx +6 -6
- package/src/renderer/shared/components/AgentActivityTable.tsx +192 -132
- package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +4 -4
- package/src/renderer/shared/components/AuthGate.tsx +4 -9
- package/src/renderer/shared/components/CalculationCard.tsx +41 -15
- package/src/renderer/shared/components/ChatMessageItem.tsx +20 -21
- package/src/renderer/shared/components/ChatPanel.tsx +432 -193
- package/src/renderer/shared/components/ChunkErrorBoundary.tsx +198 -49
- package/src/renderer/shared/components/CliSessionsView.tsx +495 -0
- package/src/renderer/shared/components/CodeReviewPage.tsx +565 -0
- package/src/renderer/shared/components/CommandPalette.tsx +8 -8
- package/src/renderer/shared/components/DashboardSidebar.tsx +862 -125
- package/src/renderer/shared/components/DesktopTitlebar.tsx +345 -0
- package/src/renderer/shared/components/DiffReviewModal.tsx +3 -3
- package/src/renderer/shared/components/GitBranchSelector.tsx +442 -0
- package/src/renderer/shared/components/ImageGenCard.tsx +31 -12
- package/src/renderer/shared/components/ImageSearchCard.tsx +59 -38
- package/src/renderer/shared/components/ImageStudioPanel.tsx +40 -41
- package/src/renderer/shared/components/InlineThoughtNote.tsx +27 -0
- package/src/renderer/shared/components/LinkedFoldersView.tsx +365 -46
- package/src/renderer/shared/components/McpRegistryPicker.tsx +1 -1
- package/src/renderer/shared/components/McpServersView.tsx +24 -22
- package/src/renderer/shared/components/MermaidCard.tsx +14 -8
- package/src/renderer/shared/components/MintDashboard.tsx +1141 -508
- package/src/renderer/shared/components/ModelSelectorPopover.tsx +535 -214
- package/src/renderer/shared/components/PlanChecklistWidget.tsx +1 -1
- package/src/renderer/shared/components/PluginsView.tsx +1 -1
- package/src/renderer/shared/components/RewindModal.tsx +6 -6
- package/src/renderer/shared/components/RunSummaryDashboard.tsx +3 -3
- package/src/renderer/shared/components/ScheduledTasksView.tsx +4 -4
- package/src/renderer/shared/components/ScreenCaptureDialog.css +221 -0
- package/src/renderer/shared/components/ScreenCaptureDialog.tsx +199 -0
- package/src/renderer/shared/components/Settings/AgentsTab.tsx +34 -39
- package/src/renderer/shared/components/Settings/AudioTab.tsx +8 -8
- package/src/renderer/shared/components/Settings/AutomationTab.tsx +8 -58
- package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +368 -0
- package/src/renderer/shared/components/Settings/GeneralTab.tsx +335 -81
- package/src/renderer/shared/components/Settings/MemoryTab.tsx +3 -3
- package/src/renderer/shared/components/Settings/PluginsTab.tsx +20 -12
- package/src/renderer/shared/components/Settings/ProfileTab.tsx +29 -5
- package/src/renderer/shared/components/Settings/SearchableModelCombobox.tsx +411 -0
- package/src/renderer/shared/components/Settings/ThemeTab.tsx +401 -158
- package/src/renderer/shared/components/SkillsView.tsx +41 -16
- package/src/renderer/shared/components/SlashSuggestions.tsx +3 -2
- package/src/renderer/shared/components/SourcesBlock.tsx +44 -32
- package/src/renderer/shared/components/StockCard.tsx +58 -24
- package/src/renderer/shared/components/SubagentDagView.tsx +4 -4
- package/src/renderer/shared/components/TerminalDock.tsx +241 -0
- package/src/renderer/shared/components/ThinkingBlock.tsx +14 -8
- package/src/renderer/shared/components/ToolSurfacePage.tsx +520 -0
- package/src/renderer/shared/components/UiFeatureCard.tsx +267 -0
- package/src/renderer/shared/components/UiGridCard.tsx +188 -0
- package/src/renderer/shared/components/UiMockupWidget.tsx +149 -0
- package/src/renderer/shared/components/VeoStudioPanel.tsx +13 -29
- package/src/renderer/shared/components/WeatherCard.tsx +3 -3
- package/src/renderer/shared/components/WorkspaceContextPopover.tsx +133 -0
- package/src/renderer/shared/components/WorkspaceSelector.tsx +149 -0
- package/src/renderer/shared/constants/config.ts +28 -3
- package/src/renderer/shared/constants/models.ts +189 -2
- package/src/renderer/shared/conversation/coordinator.test.ts +27 -0
- package/src/renderer/shared/conversation/coordinator.ts +82 -0
- package/src/renderer/shared/conversation/syncView.test.ts +46 -0
- package/src/renderer/shared/conversation/syncView.ts +43 -0
- package/src/renderer/shared/conversation/useConversationCoordinator.ts +106 -0
- package/src/renderer/shared/css/agent-observability.css +35 -3
- package/src/renderer/shared/css/auth-gate.css +2 -2
- package/src/renderer/shared/css/chat-ui-cards.css +495 -0
- package/src/renderer/shared/css/code-review.css +823 -0
- package/src/renderer/shared/css/code-sessions.css +452 -0
- package/src/renderer/shared/css/file-changes.css +132 -0
- package/src/renderer/shared/css/image-studio.css +16 -16
- package/src/renderer/shared/css/management-views.css +277 -27
- package/src/renderer/shared/css/model-selector.css +442 -42
- package/src/renderer/shared/css/settings/agents.css +1 -1
- package/src/renderer/shared/css/settings/automation.css +13 -13
- package/src/renderer/shared/css/settings/general.css +526 -5
- package/src/renderer/shared/css/settings/plugins.css +5 -5
- package/src/renderer/shared/css/settings/profile.css +331 -1
- package/src/renderer/shared/css/settings/theme.css +651 -3
- package/src/renderer/shared/css/veo-studio.css +16 -15
- package/src/renderer/shared/fonts.ts +19 -37
- package/src/renderer/shared/platform.test.ts +34 -0
- package/src/renderer/shared/platform.ts +173 -6
- package/src/renderer/shared/theme/themeManager.ts +144 -0
- package/src/renderer/shared/theme/themes.ts +116 -0
- package/src/renderer/shared/theme/tokens.css +318 -0
- package/src/renderer/shared/types.ts +82 -1
- package/src/renderer/shared/utils/agentActivity.ts +126 -5
- package/src/renderer/shared/utils/cardJson.test.ts +35 -0
- package/src/renderer/shared/utils/cardJson.ts +30 -0
- package/src/renderer/shared/utils/fileIcons.ts +1 -1
- package/src/renderer/shared/utils/markdown.tsx +329 -80
- package/src/renderer/shared/utils/markdown.webImages.test.tsx +40 -0
- package/src/renderer/shared/utils/providers.ts +8 -3
- package/src/renderer/shared/utils/screenCapture.ts +43 -0
- package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
- package/src/renderer/shared/utils/ui.ts +4 -64
- package/src/renderer/src/App.tsx +7 -0
- package/src/renderer/src/components/BrowserShell.tsx +108 -0
- package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +91 -0
- package/src/renderer/src/components/Live2DStage.tsx +32 -9
- package/src/renderer/src/components/MintDashboard.tsx +4 -2
- package/src/renderer/src/components/ModelPanel.tsx +2 -1
- package/src/renderer/src/components/PicturesLibrary.tsx +3 -2
- package/src/renderer/src/components/ScreenPicker.css +190 -0
- package/src/renderer/src/components/ScreenPicker.tsx +185 -540
- package/src/renderer/src/components/SettingsWindow.tsx +40 -2
- package/src/renderer/src/components/SpotlightWindow.tsx +1 -0
- package/src/renderer/src/components/WidgetWindow.tsx +27 -3
- package/src/renderer/src/components/WorkspacePanel.tsx +61 -43
- package/src/renderer/src/components/browser-shell.css +185 -0
- package/src/renderer/src/components/browser-suggestions-popup.css +55 -0
- package/src/renderer/src/components/liveTranslateFrames.test.ts +23 -0
- package/src/renderer/src/components/liveTranslateFrames.ts +34 -0
- package/src/renderer/src/css/app-shell.css +394 -113
- package/src/renderer/src/css/chat.css +1926 -380
- package/src/renderer/src/css/dashboard.css +11 -0
- package/src/renderer/src/css/desktop-tools.css +730 -0
- package/src/renderer/src/css/misc.css +19 -22
- package/src/renderer/src/css/model.css +49 -48
- package/src/renderer/src/css/pictures.css +144 -136
- package/src/renderer/src/css/settings/base.css +357 -123
- package/src/renderer/src/css/sidebar-workspace.css +930 -291
- package/src/renderer/src/css/spotlight.css +15 -26
- package/src/renderer/src/css/tool-surfaces.css +728 -0
- package/src/renderer/src/css/widget.css +35 -7
- package/src/renderer/src/env.d.ts +3 -0
- package/src/renderer/src/hooks/useProviderModels.ts +2 -9
- package/src/renderer/src/index.css +53 -46
- package/src/renderer/src/main.tsx +11 -0
- package/src/renderer/src/tauri.ts +201 -30
- package/src/renderer/src-web/App.tsx +40 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +4 -2
- package/src/renderer/src-web/components/PicturesLibrary.tsx +3 -2
- package/src/renderer/src-web/components/PullToRefresh.tsx +107 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +28 -2
- package/src/renderer/src-web/css/app-shell.css +174 -96
- package/src/renderer/src-web/css/chat.css +1519 -316
- package/src/renderer/src-web/css/dashboard.css +11 -0
- package/src/renderer/src-web/css/misc.css +17 -17
- package/src/renderer/src-web/css/mobile.css +678 -0
- package/src/renderer/src-web/css/model.css +46 -46
- package/src/renderer/src-web/css/pictures.css +81 -34
- package/src/renderer/src-web/css/pull-to-refresh.css +54 -0
- package/src/renderer/src-web/css/settings/base.css +316 -100
- package/src/renderer/src-web/css/settings/mobile.css +320 -0
- package/src/renderer/src-web/css/sidebar-workspace.css +545 -100
- package/src/renderer/src-web/css/spotlight.css +9 -26
- package/src/renderer/src-web/css/widget.css +0 -5
- package/src/renderer/src-web/env.d.ts +3 -0
- package/src/renderer/src-web/hooks/useProviderModels.ts +2 -9
- package/src/renderer/src-web/index.css +53 -96
- package/src/renderer/src-web/main.tsx +11 -0
- package/src/renderer/src-web/tauri.ts +200 -30
- package/vite.config.web.ts +1 -0
- package/out/web/assets/MintDashboard-DWSqucgb.js +0 -57
- package/out/web/assets/SettingsWindow-DFM2aj5B.js +0 -2
- package/out/web/assets/chunk-CKBBP62Z-nr1uKPj9.js +0 -2
- package/out/web/assets/chunk-DSNCTWBM-Dtj8Rqsd.js +0 -2
- package/out/web/assets/chunk-DU6HZSFF-2tv5K1wT.js +0 -128
- package/out/web/assets/classDiagram-DTDB5LWJ-BdSFArgw.js +0 -1
- package/out/web/assets/classDiagram-v2-JRS7N3AN-BdSFArgw.js +0 -1
- package/out/web/assets/core-DImejIUG.js +0 -1
- package/out/web/assets/dist-js-BxuKEVWM.js +0 -1
- package/out/web/assets/event-BNl8Zahw.js +0 -1
- package/out/web/assets/fira-code-cyrillic-400-normal-UC0NFL4U.woff2 +0 -0
- package/out/web/assets/fira-code-cyrillic-400-normal-dc1q3VD7.woff +0 -0
- package/out/web/assets/fira-code-cyrillic-500-normal-Bu7VCL72.woff +0 -0
- package/out/web/assets/fira-code-cyrillic-500-normal-CX35h3Mg.woff2 +0 -0
- package/out/web/assets/fira-code-cyrillic-ext-400-normal-Buh61xzB.woff +0 -0
- package/out/web/assets/fira-code-cyrillic-ext-400-normal-txZ9Fk_1.woff2 +0 -0
- package/out/web/assets/fira-code-cyrillic-ext-500-normal-CYRspHgm.woff2 +0 -0
- package/out/web/assets/fira-code-cyrillic-ext-500-normal-CwtLmSFD.woff +0 -0
- package/out/web/assets/fira-code-greek-400-normal-B2Gh_Y8s.woff2 +0 -0
- package/out/web/assets/fira-code-greek-400-normal-DobhaxvF.woff +0 -0
- package/out/web/assets/fira-code-greek-500-normal-CVO0IRNt.woff2 +0 -0
- package/out/web/assets/fira-code-greek-500-normal-DEXIWiey.woff +0 -0
- package/out/web/assets/fira-code-greek-ext-400-normal-DR7mBgIM.woff2 +0 -0
- package/out/web/assets/fira-code-greek-ext-400-normal-DSPUheWg.woff +0 -0
- package/out/web/assets/fira-code-greek-ext-500-normal-BO3iq16n.woff2 +0 -0
- package/out/web/assets/fira-code-greek-ext-500-normal-CMiNLXER.woff +0 -0
- package/out/web/assets/fira-code-latin-ext-400-normal-DG73JjOf.woff +0 -0
- package/out/web/assets/fira-code-latin-ext-400-normal-KSMg0QLl.woff2 +0 -0
- package/out/web/assets/fira-code-latin-ext-500-normal-CaANqXjI.woff +0 -0
- package/out/web/assets/fira-code-latin-ext-500-normal-Crd9e_oI.woff2 +0 -0
- package/out/web/assets/flowDiagram-A5DVABFB-BpnHUSU7.js +0 -1
- package/out/web/assets/index-CVRg270z.css +0 -1
- package/out/web/assets/index-DxZQr9yD.js +0 -10
- package/out/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/out/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/out/web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
- package/out/web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
- package/out/web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
- package/out/web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
- package/out/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/out/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/out/web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
- package/out/web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
- package/out/web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
- package/out/web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
- package/out/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/out/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/out/web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
- package/out/web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
- package/out/web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
- package/out/web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
- package/out/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/out/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/out/web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
- package/out/web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
- package/out/web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
- package/out/web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
- package/out/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/out/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/out/web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/out/web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/out/web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/out/web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/out/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/out/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/out/web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
- package/out/web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
- package/out/web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
- package/out/web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
- package/out/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/out/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/out/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- package/out/web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
- package/out/web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
- package/out/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- package/out/web/assets/kanit-latin-400-normal-BUlsQXH3.woff2 +0 -0
- package/out/web/assets/kanit-latin-400-normal-HVZWrfev.woff +0 -0
- package/out/web/assets/kanit-latin-500-normal-CX_4XcKi.woff2 +0 -0
- package/out/web/assets/kanit-latin-500-normal-qBha86O4.woff +0 -0
- package/out/web/assets/kanit-latin-600-normal-0lS78yAd.woff2 +0 -0
- package/out/web/assets/kanit-latin-600-normal-DlacpeTg.woff +0 -0
- package/out/web/assets/kanit-latin-ext-400-normal-B2xPf58u.woff2 +0 -0
- package/out/web/assets/kanit-latin-ext-400-normal-CHvNKHHD.woff +0 -0
- package/out/web/assets/kanit-latin-ext-500-normal-BFdWrG22.woff +0 -0
- package/out/web/assets/kanit-latin-ext-500-normal-EHs3suD6.woff2 +0 -0
- package/out/web/assets/kanit-latin-ext-600-normal-BBDzoLaj.woff +0 -0
- package/out/web/assets/kanit-latin-ext-600-normal-DAX3wtuq.woff2 +0 -0
- package/out/web/assets/kanit-thai-400-normal-D2nliXKa.woff +0 -0
- package/out/web/assets/kanit-thai-400-normal-D80Pglro.woff2 +0 -0
- package/out/web/assets/kanit-thai-500-normal-BB8gri04.woff2 +0 -0
- package/out/web/assets/kanit-thai-500-normal-Iy_w0a80.woff +0 -0
- package/out/web/assets/kanit-thai-600-normal-Bj6oiaDc.woff2 +0 -0
- package/out/web/assets/kanit-thai-600-normal-wbTlPO7I.woff +0 -0
- package/out/web/assets/kanit-vietnamese-400-normal-DRI8YWyb.woff2 +0 -0
- package/out/web/assets/kanit-vietnamese-400-normal-dow6Bnto.woff +0 -0
- package/out/web/assets/kanit-vietnamese-500-normal-BzTrpCM3.woff2 +0 -0
- package/out/web/assets/kanit-vietnamese-500-normal-T1ndT7MF.woff +0 -0
- package/out/web/assets/kanit-vietnamese-600-normal-Bi7fw3I4.woff2 +0 -0
- package/out/web/assets/kanit-vietnamese-600-normal-DXCRAa_8.woff +0 -0
- package/out/web/assets/mali-latin-400-normal-ByPcZ8Gx.woff +0 -0
- package/out/web/assets/mali-latin-400-normal-Cuc6Ykl8.woff2 +0 -0
- package/out/web/assets/mali-latin-500-normal-B01M-LP6.woff2 +0 -0
- package/out/web/assets/mali-latin-500-normal-Thgqw_9f.woff +0 -0
- package/out/web/assets/mali-latin-600-normal-B9TiUNpT.woff2 +0 -0
- package/out/web/assets/mali-latin-600-normal-CycpQCwU.woff +0 -0
- package/out/web/assets/mali-latin-ext-400-normal-DUVdJQO4.woff +0 -0
- package/out/web/assets/mali-latin-ext-400-normal-iCG1ALuJ.woff2 +0 -0
- package/out/web/assets/mali-latin-ext-500-normal-BfJE4UMZ.woff2 +0 -0
- package/out/web/assets/mali-latin-ext-500-normal-FoLEyOKS.woff +0 -0
- package/out/web/assets/mali-latin-ext-600-normal-BTvIfiCp.woff2 +0 -0
- package/out/web/assets/mali-latin-ext-600-normal-DAQcLVmD.woff +0 -0
- package/out/web/assets/mali-thai-400-normal-BO7A3bLZ.woff2 +0 -0
- package/out/web/assets/mali-thai-400-normal-CX9VrjTn.woff +0 -0
- package/out/web/assets/mali-thai-500-normal-Cph2qha9.woff +0 -0
- package/out/web/assets/mali-thai-500-normal-D89rMT77.woff2 +0 -0
- package/out/web/assets/mali-thai-600-normal-DPgXT6rZ.woff +0 -0
- package/out/web/assets/mali-thai-600-normal-DlTx3OsB.woff2 +0 -0
- package/out/web/assets/mali-vietnamese-400-normal-DUKxzXaz.woff2 +0 -0
- package/out/web/assets/mali-vietnamese-400-normal-gFDLvsOK.woff +0 -0
- package/out/web/assets/mali-vietnamese-500-normal--OY07q2c.woff +0 -0
- package/out/web/assets/mali-vietnamese-500-normal-KDrNRwfW.woff2 +0 -0
- package/out/web/assets/mali-vietnamese-600-normal-CtTMUU7T.woff +0 -0
- package/out/web/assets/mali-vietnamese-600-normal-DFlJr66u.woff2 +0 -0
- package/out/web/assets/mermaid.core-DYGGlqOf.js +0 -11
- package/out/web/assets/mitr-latin-400-normal-Cb18vPmZ.woff +0 -0
- package/out/web/assets/mitr-latin-400-normal-CwbkLQ4a.woff2 +0 -0
- package/out/web/assets/mitr-latin-500-normal-B4JOhzAi.woff +0 -0
- package/out/web/assets/mitr-latin-500-normal-BmClOow2.woff2 +0 -0
- package/out/web/assets/mitr-latin-ext-400-normal-B3CPG7Yo.woff2 +0 -0
- package/out/web/assets/mitr-latin-ext-400-normal-DTx65hiC.woff +0 -0
- package/out/web/assets/mitr-latin-ext-500-normal-B_Diajz1.woff2 +0 -0
- package/out/web/assets/mitr-latin-ext-500-normal-DYOsVaVT.woff +0 -0
- package/out/web/assets/mitr-thai-400-normal-CS4Do6aM.woff +0 -0
- package/out/web/assets/mitr-thai-400-normal-CXWaAdVj.woff2 +0 -0
- package/out/web/assets/mitr-thai-500-normal-CVdBrEVH.woff2 +0 -0
- package/out/web/assets/mitr-thai-500-normal-CW_B5D-i.woff +0 -0
- package/out/web/assets/mitr-vietnamese-400-normal-CPMGWyoC.woff2 +0 -0
- package/out/web/assets/mitr-vietnamese-400-normal-Z6XhIpdY.woff +0 -0
- package/out/web/assets/mitr-vietnamese-500-normal-CDrv0585.woff2 +0 -0
- package/out/web/assets/mitr-vietnamese-500-normal-CMQWyq-Z.woff +0 -0
- package/out/web/assets/noto-sans-thai-latin-400-normal-BaWBrqFP.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-latin-400-normal-Dh_FJjOB.woff +0 -0
- package/out/web/assets/noto-sans-thai-latin-500-normal-BC04jsmm.woff +0 -0
- package/out/web/assets/noto-sans-thai-latin-500-normal-fyBKClF1.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-latin-600-normal-CemcjnVR.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-latin-600-normal-Dbc-dNO6.woff +0 -0
- package/out/web/assets/noto-sans-thai-latin-ext-400-normal-B-1H0YkH.woff +0 -0
- package/out/web/assets/noto-sans-thai-latin-ext-400-normal-nBP59pVw.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-latin-ext-500-normal-B-ZWb8SZ.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-latin-ext-500-normal-CrEHO7C8.woff +0 -0
- package/out/web/assets/noto-sans-thai-latin-ext-600-normal-PkMZo0xa.woff +0 -0
- package/out/web/assets/noto-sans-thai-latin-ext-600-normal-maHHBsez.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-thai-400-normal-BEOmKAI1.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-thai-400-normal-Dxb_unuU.woff +0 -0
- package/out/web/assets/noto-sans-thai-thai-500-normal-BOGXO2tw.woff +0 -0
- package/out/web/assets/noto-sans-thai-thai-500-normal-CU_X86GQ.woff2 +0 -0
- package/out/web/assets/noto-sans-thai-thai-600-normal-CmgB8_JK.woff +0 -0
- package/out/web/assets/noto-sans-thai-thai-600-normal-Cs73sVjl.woff2 +0 -0
- package/out/web/assets/oauthManager-DNiwM8Rn.js +0 -2
- package/out/web/assets/oauthManager-DuamS1Av.css +0 -1
- package/out/web/assets/outfit-latin-300-normal-AWLfuCom.woff +0 -0
- package/out/web/assets/outfit-latin-300-normal-CRYTwY6m.woff2 +0 -0
- package/out/web/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
- package/out/web/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
- package/out/web/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
- package/out/web/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
- package/out/web/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
- package/out/web/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
- package/out/web/assets/outfit-latin-ext-300-normal-BYS6Dpvq.woff2 +0 -0
- package/out/web/assets/outfit-latin-ext-300-normal-CMIMshEd.woff +0 -0
- package/out/web/assets/outfit-latin-ext-400-normal-5tcqmc2S.woff2 +0 -0
- package/out/web/assets/outfit-latin-ext-400-normal-DHm7mdGe.woff +0 -0
- package/out/web/assets/outfit-latin-ext-500-normal-DrCvqoFD.woff +0 -0
- package/out/web/assets/outfit-latin-ext-500-normal-zeox_O30.woff2 +0 -0
- package/out/web/assets/outfit-latin-ext-600-normal-B85nYjL1.woff2 +0 -0
- package/out/web/assets/outfit-latin-ext-600-normal-CWJcPgd7.woff +0 -0
- package/out/web/assets/preload-helper-D0uHx7FM.js +0 -1
- package/out/web/assets/prompt-latin-ext-400-normal-1pHbN9uy.woff +0 -0
- package/out/web/assets/prompt-latin-ext-400-normal-DdSafGZ9.woff2 +0 -0
- package/out/web/assets/prompt-latin-ext-500-normal--EZ1um7s.woff2 +0 -0
- package/out/web/assets/prompt-latin-ext-500-normal-D-lI1EVT.woff +0 -0
- package/out/web/assets/prompt-latin-ext-600-normal-C8b50x67.woff +0 -0
- package/out/web/assets/prompt-latin-ext-600-normal-Cg9L7iJU.woff2 +0 -0
- package/out/web/assets/prompt-vietnamese-400-normal-BCPzsgPT.woff2 +0 -0
- package/out/web/assets/prompt-vietnamese-400-normal-D4pLyeNK.woff +0 -0
- package/out/web/assets/prompt-vietnamese-500-normal-DmzxmPwa.woff2 +0 -0
- package/out/web/assets/prompt-vietnamese-500-normal-w_sS_j2F.woff +0 -0
- package/out/web/assets/prompt-vietnamese-600-normal-1dyohtNh.woff +0 -0
- package/out/web/assets/prompt-vietnamese-600-normal-7QWjJBsF.woff2 +0 -0
- package/out/web/assets/sarabun-latin-400-normal-DmguIS-7.woff +0 -0
- package/out/web/assets/sarabun-latin-400-normal-URPBxl-K.woff2 +0 -0
- package/out/web/assets/sarabun-latin-500-normal-BjUTcdxu.woff2 +0 -0
- package/out/web/assets/sarabun-latin-500-normal-CJexTAnl.woff +0 -0
- package/out/web/assets/sarabun-latin-600-normal-9FonlkKx.woff +0 -0
- package/out/web/assets/sarabun-latin-600-normal-DMD3TROr.woff2 +0 -0
- package/out/web/assets/sarabun-latin-ext-400-normal-BbP7GpLL.woff2 +0 -0
- package/out/web/assets/sarabun-latin-ext-400-normal-BsmTn0SS.woff +0 -0
- package/out/web/assets/sarabun-latin-ext-500-normal-Cot16Bd2.woff +0 -0
- package/out/web/assets/sarabun-latin-ext-500-normal-DE1LysqI.woff2 +0 -0
- package/out/web/assets/sarabun-latin-ext-600-normal-0tXu9x_i.woff +0 -0
- package/out/web/assets/sarabun-latin-ext-600-normal-BRxjKUQ6.woff2 +0 -0
- package/out/web/assets/sarabun-thai-400-normal-C2DaJlKK.woff2 +0 -0
- package/out/web/assets/sarabun-thai-400-normal-CH-UbWgg.woff +0 -0
- package/out/web/assets/sarabun-thai-500-normal-BVssjame.woff2 +0 -0
- package/out/web/assets/sarabun-thai-500-normal-C3iDLEzi.woff +0 -0
- package/out/web/assets/sarabun-thai-600-normal-3dIAQ-_s.woff2 +0 -0
- package/out/web/assets/sarabun-thai-600-normal-DWKeTe6a.woff +0 -0
- package/out/web/assets/sarabun-vietnamese-400-normal-DPfkSCMy.woff +0 -0
- package/out/web/assets/sarabun-vietnamese-400-normal-igdrzAGY.woff2 +0 -0
- package/out/web/assets/sarabun-vietnamese-500-normal-CpRVvGRU.woff +0 -0
- package/out/web/assets/sarabun-vietnamese-500-normal-Dpe7Br9m.woff2 +0 -0
- package/out/web/assets/sarabun-vietnamese-600-normal-BMbOgAW-.woff +0 -0
- package/out/web/assets/sarabun-vietnamese-600-normal-DkxrOlLB.woff2 +0 -0
- package/out/web/assets/stateDiagram-v2-4QOOHH4V-s6P9g2_b.js +0 -1
- package/out/web/assets/swimlanesDiagram-VK2B7HYN-CV9fOLrV.js +0 -8
- package/out/web/assets/tauri-CNrHQ2Gb.js +0 -3
- package/out/web/assets/window-DkIhgI7B.js +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const ANSI_BLUE: &str = crate::terminal_theme::ANSI_BLUE;
|
|
2
|
+
|
|
1
3
|
use super::*;
|
|
2
4
|
|
|
3
5
|
fn is_reachable(host: &str, port: u16) -> bool {
|
|
@@ -165,31 +167,11 @@ fn first_line_preview(path: &Path) -> String {
|
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
fn
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
} else {
|
|
174
|
-
if !table_buffer.is_empty() {
|
|
175
|
-
print!("{}", crate::markdown::render_markdown_table(&table_buffer));
|
|
176
|
-
table_buffer.clear();
|
|
177
|
-
}
|
|
178
|
-
if let Some(h) = line.strip_prefix("## ") {
|
|
179
|
-
println!("\n{BLUE}{h}{RESET}");
|
|
180
|
-
} else if let Some(h) = line.strip_prefix("# ") {
|
|
181
|
-
println!("\n{BLUE}{h}{RESET}");
|
|
182
|
-
} else {
|
|
183
|
-
println!("{line}");
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if !table_buffer.is_empty() {
|
|
188
|
-
print!("{}", crate::markdown::render_markdown_table(&table_buffer));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Option<SlashResult> {
|
|
170
|
+
async fn execute_core_slash(
|
|
171
|
+
session: &mut InteractiveSession,
|
|
172
|
+
ui: &mut dyn CommandUi,
|
|
173
|
+
query: &str,
|
|
174
|
+
) -> Option<SlashResult> {
|
|
193
175
|
use mint_core::slash::{SlashEffect, SlashRequest, SlashResponse};
|
|
194
176
|
|
|
195
177
|
let trimmed = query.trim();
|
|
@@ -213,28 +195,17 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
213
195
|
| "/videomodels"
|
|
214
196
|
| "/video-provider"
|
|
215
197
|
) {
|
|
216
|
-
|
|
217
|
-
let _ = std::io::Write::flush(&mut std::io::stdout());
|
|
198
|
+
ui.set_status(vec!["Fetching available models…".into()]);
|
|
218
199
|
}
|
|
219
200
|
|
|
220
201
|
let response = mint_core::slash::execute_async(&req, &mut session.config).await;
|
|
221
|
-
|
|
222
|
-
if matches!(
|
|
223
|
-
cmd,
|
|
224
|
-
"/models"
|
|
225
|
-
| "/image-models"
|
|
226
|
-
| "/image-provider"
|
|
227
|
-
| "/video-models"
|
|
228
|
-
| "/videomodels"
|
|
229
|
-
| "/video-provider"
|
|
230
|
-
) {
|
|
231
|
-
print!("\r\x1b[2K");
|
|
232
|
-
let _ = std::io::Write::flush(&mut std::io::stdout());
|
|
233
|
-
}
|
|
202
|
+
ui.clear_status();
|
|
234
203
|
|
|
235
204
|
match response {
|
|
236
205
|
SlashResponse::Message { markdown } => {
|
|
237
|
-
|
|
206
|
+
if !markdown.is_empty() {
|
|
207
|
+
ui.push_command_output(markdown);
|
|
208
|
+
}
|
|
238
209
|
Some(SlashResult::Handled)
|
|
239
210
|
}
|
|
240
211
|
SlashResponse::Applied { markdown, effects } => {
|
|
@@ -246,7 +217,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
246
217
|
}
|
|
247
218
|
SlashEffect::ProviderChanged { display } => {
|
|
248
219
|
save_needed = true;
|
|
249
|
-
|
|
220
|
+
ui.push_notice(format!("Switched to: {display}"));
|
|
250
221
|
}
|
|
251
222
|
SlashEffect::FastModeChanged { enabled } => {
|
|
252
223
|
session.fast_mode = *enabled;
|
|
@@ -264,6 +235,10 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
264
235
|
}
|
|
265
236
|
SlashEffect::HistoryCleared => {
|
|
266
237
|
session.history.clear();
|
|
238
|
+
ui.clear_transcript();
|
|
239
|
+
}
|
|
240
|
+
SlashEffect::ThinkingChanged { .. } => {
|
|
241
|
+
save_needed = true;
|
|
267
242
|
}
|
|
268
243
|
}
|
|
269
244
|
}
|
|
@@ -271,7 +246,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
271
246
|
let _ = mint_core::save_config(&session.config);
|
|
272
247
|
}
|
|
273
248
|
if !markdown.is_empty() {
|
|
274
|
-
|
|
249
|
+
ui.push_command_output(markdown);
|
|
275
250
|
}
|
|
276
251
|
Some(SlashResult::Handled)
|
|
277
252
|
}
|
|
@@ -280,20 +255,49 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
280
255
|
title,
|
|
281
256
|
options,
|
|
282
257
|
} => {
|
|
283
|
-
let
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
258
|
+
let choice_items: Vec<ChoiceItem> = options
|
|
259
|
+
.iter()
|
|
260
|
+
.map(|opt| ChoiceItem::new(opt.label.clone(), opt.value.clone()))
|
|
261
|
+
.collect();
|
|
262
|
+
match ui.prompt_choice(&title, "", &choice_items) {
|
|
263
|
+
Ok(Some(idx)) => {
|
|
264
|
+
let opt = &options[idx];
|
|
265
|
+
if opt.value == "__action:search" {
|
|
266
|
+
let text = ui
|
|
267
|
+
.prompt_text("Search models", "Search query", None)
|
|
268
|
+
.ok()
|
|
269
|
+
.flatten()
|
|
270
|
+
.unwrap_or_default();
|
|
271
|
+
let trimmed = text.trim();
|
|
272
|
+
if !trimmed.is_empty() {
|
|
273
|
+
let next_input = format!("{command} __action:search:{trimmed}");
|
|
274
|
+
return Box::pin(handle_slash_command(session, ui, &next_input)).await;
|
|
275
|
+
}
|
|
276
|
+
ui.push_notice("Cancelled.");
|
|
277
|
+
return Some(SlashResult::Handled);
|
|
290
278
|
}
|
|
279
|
+
if opt.value == "__action:custom" {
|
|
280
|
+
let custom = ui
|
|
281
|
+
.prompt_text("Custom model", "Enter custom model ID", None)
|
|
282
|
+
.ok()
|
|
283
|
+
.flatten()
|
|
284
|
+
.unwrap_or_default();
|
|
285
|
+
let trimmed = custom.trim();
|
|
286
|
+
if !trimmed.is_empty() {
|
|
287
|
+
let next_input = format!("{command} {trimmed}");
|
|
288
|
+
return Box::pin(handle_slash_command(session, ui, &next_input)).await;
|
|
289
|
+
}
|
|
290
|
+
ui.push_notice("Cancelled.");
|
|
291
|
+
return Some(SlashResult::Handled);
|
|
292
|
+
}
|
|
293
|
+
let next_input = format!("{command} {}", opt.value);
|
|
294
|
+
return Box::pin(handle_slash_command(session, ui, &next_input)).await;
|
|
291
295
|
}
|
|
292
296
|
Ok(None) => {
|
|
293
|
-
|
|
297
|
+
ui.push_notice("Command cancelled");
|
|
294
298
|
}
|
|
295
299
|
Err(e) => {
|
|
296
|
-
|
|
300
|
+
ui.report_error(&e);
|
|
297
301
|
}
|
|
298
302
|
}
|
|
299
303
|
Some(SlashResult::Handled)
|
|
@@ -308,7 +312,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
308
312
|
}
|
|
309
313
|
SlashResponse::Navigate { markdown, .. } => {
|
|
310
314
|
if !markdown.is_empty() {
|
|
311
|
-
|
|
315
|
+
ui.push_command_output(markdown);
|
|
312
316
|
}
|
|
313
317
|
Some(SlashResult::Handled)
|
|
314
318
|
}
|
|
@@ -320,6 +324,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
320
324
|
/// Route `/…` commands. Returns `None` if the input is not a slash command.
|
|
321
325
|
pub async fn handle_slash_command(
|
|
322
326
|
session: &mut InteractiveSession,
|
|
327
|
+
ui: &mut dyn CommandUi,
|
|
323
328
|
query: &str,
|
|
324
329
|
) -> Option<SlashResult> {
|
|
325
330
|
let trimmed = query.trim();
|
|
@@ -337,10 +342,15 @@ pub async fn handle_slash_command(
|
|
|
337
342
|
let is_cli_only_or_wizard = matches!(
|
|
338
343
|
cmd,
|
|
339
344
|
"/plan"
|
|
345
|
+
| "/thought"
|
|
346
|
+
| "/think"
|
|
347
|
+
| "/resume"
|
|
348
|
+
| "/branch"
|
|
340
349
|
| "/bg"
|
|
341
350
|
| "/jobs"
|
|
342
351
|
| "/shells"
|
|
343
352
|
| "/palette"
|
|
353
|
+
| "/theme"
|
|
344
354
|
| "/image"
|
|
345
355
|
| "/paste"
|
|
346
356
|
| "/avatar"
|
|
@@ -352,28 +362,33 @@ pub async fn handle_slash_command(
|
|
|
352
362
|
&& rest.is_empty());
|
|
353
363
|
|
|
354
364
|
if !is_cli_only_or_wizard {
|
|
355
|
-
if let Some(res) = execute_core_slash(session, trimmed).await {
|
|
365
|
+
if let Some(res) = execute_core_slash(session, ui, trimmed).await {
|
|
356
366
|
return Some(res);
|
|
357
367
|
}
|
|
358
368
|
}
|
|
359
369
|
|
|
360
370
|
match cmd {
|
|
361
371
|
"/help" => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
372
|
+
let mut lines = Vec::new();
|
|
373
|
+
lines.push(format!(
|
|
374
|
+
"{BLUE}────────────────────────────────────────────{RESET}"
|
|
375
|
+
));
|
|
376
|
+
lines.push(format!("{MINT} Mint Interactive Commands{RESET}"));
|
|
377
|
+
lines.push(format!(
|
|
378
|
+
"{BLUE}────────────────────────────────────────────{RESET}"
|
|
379
|
+
));
|
|
365
380
|
for spec in SLASH_COMMANDS.iter() {
|
|
366
381
|
let label = if spec.usage.is_empty() {
|
|
367
382
|
spec.token.to_string()
|
|
368
383
|
} else {
|
|
369
384
|
format!("{} {}", spec.token, spec.usage)
|
|
370
385
|
};
|
|
371
|
-
|
|
386
|
+
lines.push(format!(
|
|
372
387
|
" {MINT}{:<30}{RESET} {DIM}{}{RESET}",
|
|
373
388
|
label, spec.description
|
|
374
|
-
);
|
|
389
|
+
));
|
|
375
390
|
}
|
|
376
|
-
|
|
391
|
+
ui.push_command_output(lines.join("\n"));
|
|
377
392
|
Some(SlashResult::Handled)
|
|
378
393
|
}
|
|
379
394
|
|
|
@@ -384,34 +399,20 @@ pub async fn handle_slash_command(
|
|
|
384
399
|
"/fast" => {
|
|
385
400
|
let choice = if rest.is_empty() {
|
|
386
401
|
let options = vec![
|
|
387
|
-
"on (hide thinking traces)"
|
|
388
|
-
"off (show thinking traces)"
|
|
402
|
+
ChoiceItem::new("on (hide thinking traces)", "on"),
|
|
403
|
+
ChoiceItem::new("off (show thinking traces)", "off"),
|
|
389
404
|
];
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
};
|
|
395
|
-
match prompt_interactive_select("Select Fast Mode", &options, current) {
|
|
396
|
-
Ok(Some(sel)) => {
|
|
397
|
-
if sel.starts_with("on") {
|
|
398
|
-
Some(true)
|
|
399
|
-
} else {
|
|
400
|
-
Some(false)
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
Ok(None) => None,
|
|
404
|
-
Err(e) => {
|
|
405
|
-
println!("{ERROR}Error selecting fast mode:{RESET} {e}\n");
|
|
406
|
-
None
|
|
407
|
-
}
|
|
405
|
+
match ui.prompt_choice("Select Fast Mode", "", &options) {
|
|
406
|
+
Ok(Some(0)) => Some(true),
|
|
407
|
+
Ok(Some(1)) => Some(false),
|
|
408
|
+
_ => None,
|
|
408
409
|
}
|
|
409
410
|
} else {
|
|
410
411
|
match rest {
|
|
411
412
|
"on" => Some(true),
|
|
412
413
|
"off" => Some(false),
|
|
413
414
|
_ => {
|
|
414
|
-
|
|
415
|
+
ui.push_notice("/fast usage: /fast [on|off]");
|
|
415
416
|
None
|
|
416
417
|
}
|
|
417
418
|
}
|
|
@@ -420,16 +421,82 @@ pub async fn handle_slash_command(
|
|
|
420
421
|
if let Some(mode) = choice {
|
|
421
422
|
session.fast_mode = mode;
|
|
422
423
|
if session.fast_mode {
|
|
423
|
-
|
|
424
|
+
ui.push_notice("[Fast] mode ON — thinking traces hidden");
|
|
425
|
+
} else {
|
|
426
|
+
ui.push_notice("[Fast] mode OFF");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
Some(SlashResult::Handled)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
"/resume" => {
|
|
433
|
+
let target_session_id = if rest.is_empty() {
|
|
434
|
+
prompt_resume_session_picker_with_ui(ui, &session.current_dir, &session.chat_id)
|
|
435
|
+
.ok()
|
|
436
|
+
.flatten()
|
|
437
|
+
} else {
|
|
438
|
+
let trimmed = rest.trim();
|
|
439
|
+
let candidate =
|
|
440
|
+
if !trimmed.starts_with("cli::") && trimmed != mint_core::CHAT_CLI_ID {
|
|
441
|
+
format!("cli::{trimmed}")
|
|
442
|
+
} else {
|
|
443
|
+
trimmed.to_string()
|
|
444
|
+
};
|
|
445
|
+
if let Ok(memory) = MemoryStore::open_default()
|
|
446
|
+
&& let Ok(sessions) = memory.list_chat_sessions()
|
|
447
|
+
&& sessions.iter().any(|s| s.id == candidate)
|
|
448
|
+
{
|
|
449
|
+
Some(candidate)
|
|
424
450
|
} else {
|
|
425
|
-
|
|
451
|
+
Some(trimmed.to_string())
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
if let Some(target_id) = target_session_id {
|
|
456
|
+
if target_id == session.chat_id {
|
|
457
|
+
ui.push_notice(format!("Already in session '{target_id}'."));
|
|
458
|
+
} else {
|
|
459
|
+
session.chat_id = target_id.clone();
|
|
460
|
+
mint_core::live_sync::update_live_sync_chat_id(&session.chat_id);
|
|
461
|
+
ui.reload_transcript(&session.chat_id, &session.current_dir);
|
|
462
|
+
if let Ok(memory) = MemoryStore::open_default() {
|
|
463
|
+
let sessions = memory.list_chat_sessions().unwrap_or_default();
|
|
464
|
+
let title = sessions
|
|
465
|
+
.iter()
|
|
466
|
+
.find(|s| s.id == session.chat_id)
|
|
467
|
+
.map(|s| s.title.as_str())
|
|
468
|
+
.unwrap_or("Conversation");
|
|
469
|
+
ui.push_notice(format!("Switched to session: {title} ({target_id})"));
|
|
470
|
+
if let Ok(recent) = memory.get_session_preview(&session.chat_id, 2)
|
|
471
|
+
&& let Some(last) = recent.first()
|
|
472
|
+
{
|
|
473
|
+
let snippet = crate::interactive::truncate_utf8(&last.user_text, 80);
|
|
474
|
+
ui.push_notice(format!("Last turn: {snippet}"));
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
ui.push_notice(format!("Switched to session: {target_id}"));
|
|
478
|
+
}
|
|
426
479
|
}
|
|
480
|
+
} else {
|
|
481
|
+
ui.push_notice("Resume cancelled.");
|
|
427
482
|
}
|
|
428
483
|
Some(SlashResult::Handled)
|
|
429
484
|
}
|
|
430
485
|
|
|
431
486
|
"/rewind" => {
|
|
432
|
-
let checkpoints =
|
|
487
|
+
let (target_rewind_chat_id, checkpoints) = {
|
|
488
|
+
let local_cps = mint_core::git::list_checkpoints(&session.chat_id);
|
|
489
|
+
if local_cps.is_empty() && session.chat_id != mint_core::CHAT_CLI_ID {
|
|
490
|
+
let fallback_cps = mint_core::git::list_checkpoints(mint_core::CHAT_CLI_ID);
|
|
491
|
+
if !fallback_cps.is_empty() {
|
|
492
|
+
(mint_core::CHAT_CLI_ID.to_string(), fallback_cps)
|
|
493
|
+
} else {
|
|
494
|
+
(session.chat_id.clone(), local_cps)
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
(session.chat_id.clone(), local_cps)
|
|
498
|
+
}
|
|
499
|
+
};
|
|
433
500
|
if rest.is_empty() {
|
|
434
501
|
if checkpoints.is_empty() {
|
|
435
502
|
println!("\n{DIM}No git checkpoints recorded for this CLI session yet.{RESET}");
|
|
@@ -472,7 +539,7 @@ pub async fn handle_slash_command(
|
|
|
472
539
|
Ok(step) => {
|
|
473
540
|
match mint_core::git::rollback_to_step(
|
|
474
541
|
&session.current_dir,
|
|
475
|
-
|
|
542
|
+
&target_rewind_chat_id,
|
|
476
543
|
step,
|
|
477
544
|
) {
|
|
478
545
|
Ok(msg) => {
|
|
@@ -493,67 +560,125 @@ pub async fn handle_slash_command(
|
|
|
493
560
|
}
|
|
494
561
|
}
|
|
495
562
|
|
|
563
|
+
"/theme" => {
|
|
564
|
+
let current = crate::terminal_theme::TuiTheme::from_config(&session.config.tui_theme);
|
|
565
|
+
match ui.prompt_theme(current.index()) {
|
|
566
|
+
Ok(Some(index)) => {
|
|
567
|
+
let selected = crate::terminal_theme::TuiTheme::from_index(index);
|
|
568
|
+
let previous = std::mem::replace(
|
|
569
|
+
&mut session.config.tui_theme,
|
|
570
|
+
selected.config_value().to_owned(),
|
|
571
|
+
);
|
|
572
|
+
if let Err(error) = mint_core::save_config(&session.config) {
|
|
573
|
+
session.config.tui_theme = previous;
|
|
574
|
+
ui.report_error(&error.into());
|
|
575
|
+
} else {
|
|
576
|
+
ui.set_theme(selected);
|
|
577
|
+
ui.push_notice(format!("Theme: {}", selected.label()));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
Ok(None) => {}
|
|
581
|
+
Err(error) => ui.report_error(&error),
|
|
582
|
+
}
|
|
583
|
+
Some(SlashResult::Handled)
|
|
584
|
+
}
|
|
585
|
+
|
|
496
586
|
"/palette" => {
|
|
497
587
|
let options = vec![
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
588
|
+
ChoiceItem::with_description(
|
|
589
|
+
"Quick Actions Menu",
|
|
590
|
+
"Show general quick actions",
|
|
591
|
+
"tip",
|
|
592
|
+
),
|
|
593
|
+
ChoiceItem::with_description(
|
|
594
|
+
"Switch AI Model (/models)",
|
|
595
|
+
"Change active model or provider",
|
|
596
|
+
"/models",
|
|
597
|
+
),
|
|
598
|
+
ChoiceItem::with_description(
|
|
599
|
+
"Toggle Plan Mode (/plan)",
|
|
600
|
+
"Turn plan mode on or off",
|
|
601
|
+
"/plan",
|
|
602
|
+
),
|
|
603
|
+
ChoiceItem::with_description(
|
|
604
|
+
"Toggle Fast Mode (/fast)",
|
|
605
|
+
"Turn fast mode on or off",
|
|
606
|
+
"/fast",
|
|
607
|
+
),
|
|
608
|
+
ChoiceItem::with_description(
|
|
609
|
+
"Git Checkpoint Rollback (/rewind)",
|
|
610
|
+
"Undo recent AI edits",
|
|
611
|
+
"/rewind",
|
|
612
|
+
),
|
|
613
|
+
ChoiceItem::with_description(
|
|
614
|
+
"Manage MCP Servers (/mcp)",
|
|
615
|
+
"Add, remove, or configure tools",
|
|
616
|
+
"/mcp",
|
|
617
|
+
),
|
|
618
|
+
ChoiceItem::with_description(
|
|
619
|
+
"Manage Memory & Facts (/memory)",
|
|
620
|
+
"View or clear project memory",
|
|
621
|
+
"/memory",
|
|
622
|
+
),
|
|
623
|
+
ChoiceItem::with_description(
|
|
624
|
+
"Change Directory (/cd)",
|
|
625
|
+
"Change workspace directory",
|
|
626
|
+
"/cd",
|
|
627
|
+
),
|
|
628
|
+
ChoiceItem::with_description(
|
|
629
|
+
"Clear Conversation (/clear)",
|
|
630
|
+
"Clear current conversation history",
|
|
631
|
+
"/clear",
|
|
632
|
+
),
|
|
633
|
+
ChoiceItem::with_description(
|
|
634
|
+
"Help & Documentation (/help)",
|
|
635
|
+
"List all available slash commands",
|
|
636
|
+
"/help",
|
|
637
|
+
),
|
|
508
638
|
];
|
|
509
|
-
match
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
} else if sel.contains("/mcp") {
|
|
520
|
-
"/mcp"
|
|
521
|
-
} else if sel.contains("/memory") {
|
|
522
|
-
"/memory"
|
|
523
|
-
} else if sel.contains("/clear") {
|
|
524
|
-
"/clear"
|
|
525
|
-
} else if sel.contains("/help") {
|
|
526
|
-
"/help"
|
|
527
|
-
} else {
|
|
528
|
-
println!(
|
|
529
|
-
"{MINT}Tip:{RESET} Type {BLUE}/<command>{RESET} or press {MINT}Tab{RESET} to autocomplete commands anytime.\n"
|
|
639
|
+
match ui.prompt_choice(
|
|
640
|
+
"Universal Command Palette",
|
|
641
|
+
"Select command to run",
|
|
642
|
+
&options,
|
|
643
|
+
) {
|
|
644
|
+
Ok(Some(idx)) => {
|
|
645
|
+
let val = &options[idx].value;
|
|
646
|
+
if val == "tip" {
|
|
647
|
+
ui.push_notice(
|
|
648
|
+
"Tip: Type /<command> or press Tab to autocomplete commands anytime.",
|
|
530
649
|
);
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
Ok(None) => Some(SlashResult::Handled),
|
|
536
|
-
Err(e) => {
|
|
537
|
-
println!("{ERROR}Palette error:{RESET} {e}\n");
|
|
538
|
-
Some(SlashResult::Handled)
|
|
650
|
+
Some(SlashResult::Handled)
|
|
651
|
+
} else {
|
|
652
|
+
Box::pin(handle_slash_command(session, ui, val)).await
|
|
653
|
+
}
|
|
539
654
|
}
|
|
655
|
+
_ => Some(SlashResult::Handled),
|
|
540
656
|
}
|
|
541
657
|
}
|
|
542
658
|
|
|
659
|
+
"/thought" | "/think" => {
|
|
660
|
+
if let Some(record) = get_last_thought() {
|
|
661
|
+
let _ = show_thought_viewer(&record.thought, &record.elapsed_str);
|
|
662
|
+
} else {
|
|
663
|
+
ui.push_notice("No recent thought process recorded for this turn.");
|
|
664
|
+
}
|
|
665
|
+
Some(SlashResult::Handled)
|
|
666
|
+
}
|
|
667
|
+
|
|
543
668
|
"/plan" if rest == "list" => {
|
|
544
669
|
let files = list_plan_files(&session.current_dir);
|
|
545
670
|
if files.is_empty() {
|
|
546
|
-
|
|
671
|
+
ui.push_notice("No saved plans yet.");
|
|
547
672
|
} else {
|
|
548
|
-
|
|
673
|
+
let mut lines = vec![format!("{BLUE}Saved plans:{RESET}")];
|
|
549
674
|
for (idx, path) in files.iter().enumerate() {
|
|
550
|
-
|
|
675
|
+
lines.push(format!(
|
|
551
676
|
" {DIM}[{}]{RESET} {BLUE}{}{RESET}",
|
|
552
677
|
idx + 1,
|
|
553
678
|
first_line_preview(path)
|
|
554
|
-
);
|
|
679
|
+
));
|
|
555
680
|
}
|
|
556
|
-
|
|
681
|
+
ui.push_command_output(lines.join("\n"));
|
|
557
682
|
}
|
|
558
683
|
Some(SlashResult::Handled)
|
|
559
684
|
}
|
|
@@ -572,12 +697,12 @@ pub async fn handle_slash_command(
|
|
|
572
697
|
};
|
|
573
698
|
match found {
|
|
574
699
|
Some(path) => match std::fs::read_to_string(path) {
|
|
575
|
-
Ok(content) =>
|
|
576
|
-
Err(e) =>
|
|
700
|
+
Ok(content) => ui.push_command_output(content),
|
|
701
|
+
Err(e) => ui.push_notice(format!("Could not read plan file: {e}")),
|
|
577
702
|
},
|
|
578
|
-
None =>
|
|
579
|
-
"
|
|
580
|
-
),
|
|
703
|
+
None => ui.push_notice(format!(
|
|
704
|
+
"No saved plan matching \"{arg}\" — run /plan list to see saved plans."
|
|
705
|
+
)),
|
|
581
706
|
}
|
|
582
707
|
Some(SlashResult::Handled)
|
|
583
708
|
}
|
|
@@ -585,27 +710,13 @@ pub async fn handle_slash_command(
|
|
|
585
710
|
"/plan" => {
|
|
586
711
|
let choice = if rest.is_empty() {
|
|
587
712
|
let options = vec![
|
|
588
|
-
"on (read-only until plan is approved)"
|
|
589
|
-
"off (edits/shell run immediately)"
|
|
713
|
+
ChoiceItem::new("on (read-only until plan is approved)", "on"),
|
|
714
|
+
ChoiceItem::new("off (edits/shell run immediately)", "off"),
|
|
590
715
|
];
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
};
|
|
596
|
-
match prompt_interactive_select("Select Plan Mode", &options, current) {
|
|
597
|
-
Ok(Some(sel)) => {
|
|
598
|
-
if sel.starts_with("on") {
|
|
599
|
-
Some(true)
|
|
600
|
-
} else {
|
|
601
|
-
Some(false)
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
Ok(None) => None,
|
|
605
|
-
Err(e) => {
|
|
606
|
-
println!("{ERROR}Error selecting plan mode:{RESET} {e}\n");
|
|
607
|
-
None
|
|
608
|
-
}
|
|
716
|
+
match ui.prompt_choice("Select Plan Mode", "", &options) {
|
|
717
|
+
Ok(Some(0)) => Some(true),
|
|
718
|
+
Ok(Some(1)) => Some(false),
|
|
719
|
+
_ => None,
|
|
609
720
|
}
|
|
610
721
|
} else {
|
|
611
722
|
match rest {
|
|
@@ -613,9 +724,7 @@ pub async fn handle_slash_command(
|
|
|
613
724
|
"off" => Some(false),
|
|
614
725
|
task => {
|
|
615
726
|
session.plan_mode = true;
|
|
616
|
-
|
|
617
|
-
"{DIM}[Plan] mode ON — agent will investigate read-only and present a plan before editing{RESET}\n"
|
|
618
|
-
);
|
|
727
|
+
ui.push_notice("[Plan] mode ON — agent will investigate read-only and present a plan before editing");
|
|
619
728
|
return Some(SlashResult::ForwardToAgent(task.to_string()));
|
|
620
729
|
}
|
|
621
730
|
}
|
|
@@ -624,11 +733,9 @@ pub async fn handle_slash_command(
|
|
|
624
733
|
if let Some(mode) = choice {
|
|
625
734
|
session.plan_mode = mode;
|
|
626
735
|
if session.plan_mode {
|
|
627
|
-
|
|
628
|
-
"{DIM}[Plan] mode ON — agent will investigate read-only and present a plan via exit_plan_mode before editing files or running commands{RESET}\n"
|
|
629
|
-
);
|
|
736
|
+
ui.push_notice("[Plan] mode ON — agent will investigate read-only and present a plan via exit_plan_mode before editing files or running commands");
|
|
630
737
|
} else {
|
|
631
|
-
|
|
738
|
+
ui.push_notice("[Plan] mode OFF");
|
|
632
739
|
}
|
|
633
740
|
}
|
|
634
741
|
Some(SlashResult::Handled)
|
|
@@ -1438,18 +1545,15 @@ pub async fn handle_slash_command(
|
|
|
1438
1545
|
|
|
1439
1546
|
"/bg" => {
|
|
1440
1547
|
if rest.is_empty() {
|
|
1441
|
-
|
|
1548
|
+
ui.push_notice("Usage: /bg <query>");
|
|
1442
1549
|
return Some(SlashResult::Handled);
|
|
1443
1550
|
}
|
|
1444
1551
|
let id = session
|
|
1445
1552
|
.jobs
|
|
1446
1553
|
.spawn(&session.config, &session.current_dir, rest);
|
|
1447
|
-
|
|
1448
|
-
"
|
|
1449
|
-
);
|
|
1450
|
-
println!(
|
|
1451
|
-
"{DIM}Note: /bg runs non-interactively, so file writes/shell/plugin/MCP actions needing approval are declined automatically.{RESET}\n"
|
|
1452
|
-
);
|
|
1554
|
+
ui.push_notice(format!(
|
|
1555
|
+
"Started background job #{id}. Keep chatting — check on it with /jobs, or view its result with /jobs show {id}."
|
|
1556
|
+
));
|
|
1453
1557
|
Some(SlashResult::Handled)
|
|
1454
1558
|
}
|
|
1455
1559
|
|
|
@@ -1457,9 +1561,9 @@ pub async fn handle_slash_command(
|
|
|
1457
1561
|
if rest.is_empty() {
|
|
1458
1562
|
let list = session.jobs.list();
|
|
1459
1563
|
if list.is_empty() {
|
|
1460
|
-
|
|
1564
|
+
ui.push_notice("No background jobs yet. Start one with /bg <query>.");
|
|
1461
1565
|
} else {
|
|
1462
|
-
|
|
1566
|
+
let mut lines = vec![format!("{BLUE}Background jobs{RESET}")];
|
|
1463
1567
|
for job in &list {
|
|
1464
1568
|
let status_str = match job.status {
|
|
1465
1569
|
JobStatus::Running => format!("{MINT}running{RESET}"),
|
|
@@ -1473,13 +1577,13 @@ pub async fn handle_slash_command(
|
|
|
1473
1577
|
} else {
|
|
1474
1578
|
""
|
|
1475
1579
|
};
|
|
1476
|
-
|
|
1580
|
+
lines.push(format!(
|
|
1477
1581
|
" #{:<3} [{status_str}] {DIM}{:>4}s{RESET} {preview}{suffix}",
|
|
1478
1582
|
job.id,
|
|
1479
1583
|
job.elapsed_secs(),
|
|
1480
|
-
);
|
|
1584
|
+
));
|
|
1481
1585
|
}
|
|
1482
|
-
|
|
1586
|
+
ui.push_command_output(lines.join("\n"));
|
|
1483
1587
|
}
|
|
1484
1588
|
return Some(SlashResult::Handled);
|
|
1485
1589
|
}
|
|
@@ -1492,22 +1596,30 @@ pub async fn handle_slash_command(
|
|
|
1492
1596
|
match sub {
|
|
1493
1597
|
"show" => match arg.parse::<u32>().ok().and_then(|id| session.jobs.show(id)) {
|
|
1494
1598
|
Some(job) => {
|
|
1495
|
-
|
|
1496
|
-
|
|
1599
|
+
let mut lines = vec![
|
|
1600
|
+
format!("{BLUE}Job #{} — {}{RESET}", job.id, job.status),
|
|
1601
|
+
format!("{DIM}Query:{RESET} {}", job.query),
|
|
1602
|
+
];
|
|
1497
1603
|
match job.result {
|
|
1498
|
-
Some(result) =>
|
|
1499
|
-
|
|
1604
|
+
Some(result) => {
|
|
1605
|
+
lines.push(String::new());
|
|
1606
|
+
lines.push(result);
|
|
1607
|
+
}
|
|
1608
|
+
None => {
|
|
1609
|
+
lines.push(format!("{DIM}(still running — no result yet){RESET}"))
|
|
1610
|
+
}
|
|
1500
1611
|
}
|
|
1612
|
+
ui.push_command_output(lines.join("\n"));
|
|
1501
1613
|
}
|
|
1502
|
-
None =>
|
|
1614
|
+
None => ui.push_notice("No such job."),
|
|
1503
1615
|
},
|
|
1504
1616
|
"cancel" => match arg.parse::<u32>().ok() {
|
|
1505
1617
|
Some(id) if session.jobs.cancel(id) => {
|
|
1506
|
-
|
|
1618
|
+
ui.push_notice(format!("Cancelling job #{id}..."));
|
|
1507
1619
|
}
|
|
1508
|
-
_ =>
|
|
1620
|
+
_ => ui.push_notice("No such running job."),
|
|
1509
1621
|
},
|
|
1510
|
-
_ =>
|
|
1622
|
+
_ => ui.push_notice("Usage: /jobs [show <id>|cancel <id>]"),
|
|
1511
1623
|
}
|
|
1512
1624
|
Some(SlashResult::Handled)
|
|
1513
1625
|
}
|
|
@@ -1516,11 +1628,11 @@ pub async fn handle_slash_command(
|
|
|
1516
1628
|
if rest.is_empty() {
|
|
1517
1629
|
let jobs = mint_core::bg_shell::list_jobs();
|
|
1518
1630
|
if jobs.is_empty() {
|
|
1519
|
-
|
|
1520
|
-
"
|
|
1631
|
+
ui.push_notice(
|
|
1632
|
+
"No background shell jobs. The agent starts one when it calls run_shell with background: true."
|
|
1521
1633
|
);
|
|
1522
1634
|
} else {
|
|
1523
|
-
|
|
1635
|
+
let mut lines = vec![format!("{BLUE}Background shell jobs{RESET}")];
|
|
1524
1636
|
for job in &jobs {
|
|
1525
1637
|
let status_str = match &job.status {
|
|
1526
1638
|
mint_core::bg_shell::JobStatus::Running => {
|
|
@@ -1543,12 +1655,12 @@ pub async fn handle_slash_command(
|
|
|
1543
1655
|
} else {
|
|
1544
1656
|
""
|
|
1545
1657
|
};
|
|
1546
|
-
|
|
1658
|
+
lines.push(format!(
|
|
1547
1659
|
" {:<6} [{status_str}] {DIM}{:>4}s{RESET} {preview}{suffix}",
|
|
1548
1660
|
job.id, job.elapsed_secs,
|
|
1549
|
-
);
|
|
1661
|
+
));
|
|
1550
1662
|
}
|
|
1551
|
-
|
|
1663
|
+
ui.push_command_output(lines.join("\n"));
|
|
1552
1664
|
}
|
|
1553
1665
|
return Some(SlashResult::Handled);
|
|
1554
1666
|
}
|
|
@@ -1561,48 +1673,48 @@ pub async fn handle_slash_command(
|
|
|
1561
1673
|
match sub {
|
|
1562
1674
|
"show" if !arg.is_empty() => match mint_core::bg_shell::snapshot(arg) {
|
|
1563
1675
|
Ok(job) => {
|
|
1564
|
-
|
|
1676
|
+
let mut lines =
|
|
1677
|
+
vec![format!("{BLUE}Job {} — {}{RESET}", job.id, job.command)];
|
|
1565
1678
|
if let Some(pid) = job.pid {
|
|
1566
|
-
|
|
1679
|
+
lines.push(format!(
|
|
1680
|
+
"{DIM}pid: {pid} cwd: {}{RESET}",
|
|
1681
|
+
job.cwd.display()
|
|
1682
|
+
));
|
|
1567
1683
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1684
|
+
lines.push(format!("{DIM}stdout:{RESET}\n{}", job.stdout));
|
|
1685
|
+
lines.push(format!("{DIM}stderr:{RESET}\n{}", job.stderr));
|
|
1686
|
+
ui.push_command_output(lines.join("\n"));
|
|
1570
1687
|
}
|
|
1571
|
-
Err(_) =>
|
|
1688
|
+
Err(_) => ui.push_notice("No such job."),
|
|
1572
1689
|
},
|
|
1573
1690
|
"kill" if !arg.is_empty() => match mint_core::bg_shell::kill_job(arg) {
|
|
1574
|
-
Ok(msg) =>
|
|
1575
|
-
Err(_) =>
|
|
1691
|
+
Ok(msg) => ui.push_notice(msg),
|
|
1692
|
+
Err(_) => ui.push_notice("No such job."),
|
|
1576
1693
|
},
|
|
1577
|
-
_ =>
|
|
1694
|
+
_ => ui.push_notice("Usage: /shells [show <id>|kill <id>]"),
|
|
1578
1695
|
}
|
|
1579
1696
|
Some(SlashResult::Handled)
|
|
1580
1697
|
}
|
|
1581
1698
|
|
|
1582
1699
|
"/clear" | "/reset" => {
|
|
1583
|
-
let
|
|
1584
|
-
"
|
|
1585
|
-
|
|
1586
|
-
];
|
|
1587
|
-
let choice = match prompt_interactive_select(
|
|
1588
|
-
"Clear conversation history?",
|
|
1589
|
-
&options,
|
|
1590
|
-
&options[0],
|
|
1591
|
-
) {
|
|
1592
|
-
Ok(Some(sel)) => sel == options[1],
|
|
1593
|
-
_ => false,
|
|
1594
|
-
};
|
|
1595
|
-
|
|
1700
|
+
let choice = ui
|
|
1701
|
+
.prompt_confirm("Clear conversation history?", false)
|
|
1702
|
+
.unwrap_or(false);
|
|
1596
1703
|
if choice {
|
|
1704
|
+
clear_last_thought();
|
|
1705
|
+
session.history.clear();
|
|
1706
|
+
ui.clear_transcript();
|
|
1597
1707
|
if let Ok(memory) = MemoryStore::open_default() {
|
|
1598
|
-
match memory.
|
|
1599
|
-
Ok(count) =>
|
|
1600
|
-
|
|
1708
|
+
match memory.clear_interactions_for_chat(&session.chat_id) {
|
|
1709
|
+
Ok(count) => {
|
|
1710
|
+
ui.push_notice(format!("Cleared {count} interactions for session."));
|
|
1711
|
+
}
|
|
1712
|
+
Err(error) => ui.push_notice(format!("Memory error: {error}")),
|
|
1601
1713
|
}
|
|
1602
1714
|
}
|
|
1603
|
-
|
|
1715
|
+
ui.push_notice("Conversation context cleared.");
|
|
1604
1716
|
} else {
|
|
1605
|
-
|
|
1717
|
+
ui.push_notice("Cancelled.");
|
|
1606
1718
|
}
|
|
1607
1719
|
Some(SlashResult::Handled)
|
|
1608
1720
|
}
|
|
@@ -1616,7 +1728,7 @@ pub async fn handle_slash_command(
|
|
|
1616
1728
|
session.current_dir = new_dir.canonicalize().unwrap_or(new_dir);
|
|
1617
1729
|
println!(
|
|
1618
1730
|
"{DIM}Workspace: {}{RESET}\n",
|
|
1619
|
-
|
|
1731
|
+
format_workspace_with_branch(&session.current_dir)
|
|
1620
1732
|
);
|
|
1621
1733
|
} else {
|
|
1622
1734
|
println!("{ERROR}Directory not found:{RESET} {rest}\n");
|
|
@@ -1625,6 +1737,8 @@ pub async fn handle_slash_command(
|
|
|
1625
1737
|
Some(SlashResult::Handled)
|
|
1626
1738
|
}
|
|
1627
1739
|
|
|
1740
|
+
"/branch" => Some(handle_branch_slash(session, ui, rest)),
|
|
1741
|
+
|
|
1628
1742
|
"/veo" => {
|
|
1629
1743
|
if rest.is_empty() {
|
|
1630
1744
|
println!(
|
|
@@ -2272,8 +2386,35 @@ pub async fn handle_slash_command(
|
|
|
2272
2386
|
}
|
|
2273
2387
|
}
|
|
2274
2388
|
}
|
|
2389
|
+
"refresh" => match mint_core::refresh_linked_folder(args) {
|
|
2390
|
+
Ok(status) => println!("Indexed {} files in {args}.\n", status.indexed_files),
|
|
2391
|
+
Err(e) => println!("{ERROR}Error:{RESET} {e}\n"),
|
|
2392
|
+
},
|
|
2393
|
+
"notes" => match mint_core::list_linked_folder_notes(args) {
|
|
2394
|
+
Ok(notes) => {
|
|
2395
|
+
for note in notes {
|
|
2396
|
+
println!("{} [{}] {}", note.created_at, note.status, note.path);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
Err(e) => println!("{ERROR}Error:{RESET} {e}\n"),
|
|
2400
|
+
},
|
|
2401
|
+
"save" => {
|
|
2402
|
+
if let Some((name, content)) = args.split_once('|') {
|
|
2403
|
+
match mint_core::save_linked_folder_note(name.trim(), content.trim()) {
|
|
2404
|
+
Ok(note) if note.status == "saved" => {
|
|
2405
|
+
println!("Saved note: {}", note.path)
|
|
2406
|
+
}
|
|
2407
|
+
Ok(note) => {
|
|
2408
|
+
println!("{ERROR}{}{RESET}", note.error.unwrap_or_default())
|
|
2409
|
+
}
|
|
2410
|
+
Err(e) => println!("{ERROR}Error:{RESET} {e}"),
|
|
2411
|
+
}
|
|
2412
|
+
} else {
|
|
2413
|
+
println!("{WARN}/link save <name> | <text>{RESET}");
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2275
2416
|
_ => println!(
|
|
2276
|
-
"{WARN}/link usage: list | add <name> | <path> | <description> | remove <name>{RESET}\n"
|
|
2417
|
+
"{WARN}/link usage: list | add <name> | <path> | <description> | remove <name> | refresh <name> | notes <name> | save <name> | <text>{RESET}\n"
|
|
2277
2418
|
),
|
|
2278
2419
|
}
|
|
2279
2420
|
Some(SlashResult::Handled)
|
|
@@ -2337,7 +2478,7 @@ pub async fn handle_slash_command(
|
|
|
2337
2478
|
.split_once(char::is_whitespace)
|
|
2338
2479
|
.map(|(c, a)| (c, a.trim()))
|
|
2339
2480
|
.unwrap_or((rest, ""));
|
|
2340
|
-
handle_mcp_slash(session, subcmd, args);
|
|
2481
|
+
handle_mcp_slash(session, ui, subcmd, args);
|
|
2341
2482
|
Some(SlashResult::Handled)
|
|
2342
2483
|
}
|
|
2343
2484
|
|
|
@@ -2356,36 +2497,42 @@ pub async fn handle_slash_command(
|
|
|
2356
2497
|
})
|
|
2357
2498
|
.map(|v| v.len())
|
|
2358
2499
|
.unwrap_or(0);
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2500
|
+
let mut out = Vec::new();
|
|
2501
|
+
out.push(format!(
|
|
2502
|
+
"\n{BLUE}─ Session Stats ─────────────────────────{RESET}"
|
|
2503
|
+
));
|
|
2504
|
+
out.push(format!(" Provider : {MINT}{provider}{RESET}"));
|
|
2505
|
+
out.push(format!(" Model : {model}"));
|
|
2506
|
+
out.push(format!(
|
|
2363
2507
|
" Workspace: {}",
|
|
2364
|
-
|
|
2365
|
-
);
|
|
2366
|
-
|
|
2508
|
+
format_workspace_with_branch(&session.current_dir)
|
|
2509
|
+
));
|
|
2510
|
+
out.push(format!(
|
|
2367
2511
|
" Fast mode: {}",
|
|
2368
2512
|
if session.fast_mode { "on" } else { "off" }
|
|
2369
|
-
);
|
|
2370
|
-
|
|
2513
|
+
));
|
|
2514
|
+
out.push(format!(" Memory : {interactions} interactions"));
|
|
2371
2515
|
if let Some(ref img_data) = session.pending_image {
|
|
2372
2516
|
let count = img_data.split_whitespace().count();
|
|
2373
2517
|
if count > 1 {
|
|
2374
|
-
|
|
2518
|
+
out.push(format!(
|
|
2519
|
+
" Images : {WARN}{} images attached{RESET}",
|
|
2520
|
+
count
|
|
2521
|
+
));
|
|
2375
2522
|
} else {
|
|
2376
|
-
|
|
2523
|
+
out.push(format!(" Image : {WARN}attached{RESET}"));
|
|
2377
2524
|
}
|
|
2378
2525
|
}
|
|
2379
|
-
|
|
2380
|
-
println!();
|
|
2526
|
+
ui.push_command_output(out.join("\n"));
|
|
2381
2527
|
Some(SlashResult::Handled)
|
|
2382
2528
|
}
|
|
2383
2529
|
|
|
2384
2530
|
"/release-notes" => {
|
|
2385
2531
|
const RELEASE_NOTES: &str = include_str!("../../../../Release_Note.md");
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2532
|
+
ui.push_command_output(format!(
|
|
2533
|
+
"\n{BLUE}────────────────────────────────────────────{RESET}\n{}\n{BLUE}────────────────────────────────────────────{RESET}",
|
|
2534
|
+
RELEASE_NOTES.trim()
|
|
2535
|
+
));
|
|
2389
2536
|
Some(SlashResult::Handled)
|
|
2390
2537
|
}
|
|
2391
2538
|
|
|
@@ -2541,7 +2688,7 @@ pub async fn handle_slash_command(
|
|
|
2541
2688
|
} else if e.is_oauth {
|
|
2542
2689
|
format!("{DIM}{:<22}{RESET}", plain_status)
|
|
2543
2690
|
} else {
|
|
2544
|
-
format!("
|
|
2691
|
+
format!("{ANSI_BLUE}{:<22}\x1b[0m", plain_status)
|
|
2545
2692
|
};
|
|
2546
2693
|
println!(
|
|
2547
2694
|
" {BLUE}❯ {:<18}{RESET}{} {DIM}{short_desc}{RESET}",
|
|
@@ -2824,6 +2971,136 @@ pub async fn handle_slash_command(
|
|
|
2824
2971
|
}
|
|
2825
2972
|
}
|
|
2826
2973
|
|
|
2974
|
+
fn handle_branch_slash(
|
|
2975
|
+
session: &mut InteractiveSession,
|
|
2976
|
+
ui: &mut dyn CommandUi,
|
|
2977
|
+
requested: &str,
|
|
2978
|
+
) -> SlashResult {
|
|
2979
|
+
use mint_core::git::{BranchChange, BranchChangeOutcome, change_branch, read_branch_info};
|
|
2980
|
+
|
|
2981
|
+
let info = match read_branch_info(&session.current_dir) {
|
|
2982
|
+
Ok(info) if info.is_repository => info,
|
|
2983
|
+
Ok(_) => {
|
|
2984
|
+
ui.push_notice("Current workspace is not a Git repository.");
|
|
2985
|
+
return SlashResult::Handled;
|
|
2986
|
+
}
|
|
2987
|
+
Err(error) => {
|
|
2988
|
+
ui.push_notice(format!("Git error: {error}"));
|
|
2989
|
+
return SlashResult::Handled;
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
|
|
2993
|
+
let (remote, branch) = if requested.is_empty() {
|
|
2994
|
+
let mut choices = info
|
|
2995
|
+
.branches
|
|
2996
|
+
.iter()
|
|
2997
|
+
.map(|branch| {
|
|
2998
|
+
(
|
|
2999
|
+
format!(
|
|
3000
|
+
"{branch}{}",
|
|
3001
|
+
if info.current_branch.as_deref() == Some(branch) {
|
|
3002
|
+
" (current)"
|
|
3003
|
+
} else {
|
|
3004
|
+
""
|
|
3005
|
+
}
|
|
3006
|
+
),
|
|
3007
|
+
false,
|
|
3008
|
+
branch.clone(),
|
|
3009
|
+
)
|
|
3010
|
+
})
|
|
3011
|
+
.collect::<Vec<_>>();
|
|
3012
|
+
choices.extend(
|
|
3013
|
+
info.remote_branches
|
|
3014
|
+
.iter()
|
|
3015
|
+
.map(|branch| (format!("{branch} (remote · track)"), true, branch.clone())),
|
|
3016
|
+
);
|
|
3017
|
+
if choices.is_empty() {
|
|
3018
|
+
ui.push_notice("No branches found in this workspace.");
|
|
3019
|
+
return SlashResult::Handled;
|
|
3020
|
+
}
|
|
3021
|
+
let items: Vec<ChoiceItem> = choices
|
|
3022
|
+
.iter()
|
|
3023
|
+
.map(|(label, is_remote, name)| {
|
|
3024
|
+
let desc = if *is_remote {
|
|
3025
|
+
"remote · track"
|
|
3026
|
+
} else {
|
|
3027
|
+
"local"
|
|
3028
|
+
};
|
|
3029
|
+
ChoiceItem::with_description(label.clone(), desc, name.clone())
|
|
3030
|
+
})
|
|
3031
|
+
.collect();
|
|
3032
|
+
match ui.prompt_choice("Switch Git Branch", "Select a branch to switch to", &items) {
|
|
3033
|
+
Ok(Some(idx)) => {
|
|
3034
|
+
let (_, is_remote, ref b) = choices[idx];
|
|
3035
|
+
(is_remote, b.clone())
|
|
3036
|
+
}
|
|
3037
|
+
_ => {
|
|
3038
|
+
ui.push_notice("Branch selection cancelled.");
|
|
3039
|
+
return SlashResult::Handled;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
} else if info.branches.iter().any(|branch| branch == requested) {
|
|
3043
|
+
(false, requested.to_string())
|
|
3044
|
+
} else if info
|
|
3045
|
+
.remote_branches
|
|
3046
|
+
.iter()
|
|
3047
|
+
.any(|branch| branch == requested)
|
|
3048
|
+
{
|
|
3049
|
+
(true, requested.to_string())
|
|
3050
|
+
} else {
|
|
3051
|
+
ui.push_notice(format!("Branch not found: {requested}"));
|
|
3052
|
+
return SlashResult::Handled;
|
|
3053
|
+
};
|
|
3054
|
+
|
|
3055
|
+
if !remote && info.current_branch.as_deref() == Some(branch.as_str()) {
|
|
3056
|
+
ui.push_notice(format!("Already on branch {branch}."));
|
|
3057
|
+
return SlashResult::Handled;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
let change = if remote {
|
|
3061
|
+
BranchChange::Track {
|
|
3062
|
+
remote_branch: branch.clone(),
|
|
3063
|
+
}
|
|
3064
|
+
} else {
|
|
3065
|
+
BranchChange::Switch {
|
|
3066
|
+
branch: branch.clone(),
|
|
3067
|
+
}
|
|
3068
|
+
};
|
|
3069
|
+
let result = match change_branch(&session.current_dir, &change, false) {
|
|
3070
|
+
Ok(BranchChangeOutcome::Changed { info }) => Ok(info),
|
|
3071
|
+
Ok(BranchChangeOutcome::ConfirmationRequired { .. }) => {
|
|
3072
|
+
match ui.prompt_confirm(
|
|
3073
|
+
"Workspace has uncommitted changes. Continue switching branches?",
|
|
3074
|
+
false,
|
|
3075
|
+
) {
|
|
3076
|
+
Ok(true) => {
|
|
3077
|
+
change_branch(&session.current_dir, &change, true).and_then(|outcome| {
|
|
3078
|
+
match outcome {
|
|
3079
|
+
BranchChangeOutcome::Changed { info } => Ok(info),
|
|
3080
|
+
BranchChangeOutcome::ConfirmationRequired { .. } => {
|
|
3081
|
+
Err("Workspace still requires confirmation.".to_string())
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
})
|
|
3085
|
+
}
|
|
3086
|
+
_ => {
|
|
3087
|
+
ui.push_notice("Branch change cancelled.");
|
|
3088
|
+
return SlashResult::Handled;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
Err(error) => Err(error),
|
|
3093
|
+
};
|
|
3094
|
+
match result {
|
|
3095
|
+
Ok(next) => {
|
|
3096
|
+
let current = next.current_branch.as_deref().unwrap_or("detached HEAD");
|
|
3097
|
+
ui.push_notice(format!("Switched to {current}."));
|
|
3098
|
+
}
|
|
3099
|
+
Err(error) => ui.push_notice(format!("Git error: {error}")),
|
|
3100
|
+
}
|
|
3101
|
+
SlashResult::Handled
|
|
3102
|
+
}
|
|
3103
|
+
|
|
2827
3104
|
// ── /mcp interactive handling ────────────────────────────────────────────────
|
|
2828
3105
|
|
|
2829
3106
|
/// Pull the just-persisted config back into the session so subsequent turns
|
|
@@ -2834,9 +3111,14 @@ fn reload_session_config(session: &mut InteractiveSession) {
|
|
|
2834
3111
|
}
|
|
2835
3112
|
}
|
|
2836
3113
|
|
|
2837
|
-
fn handle_mcp_slash(
|
|
3114
|
+
fn handle_mcp_slash(
|
|
3115
|
+
session: &mut InteractiveSession,
|
|
3116
|
+
ui: &mut dyn CommandUi,
|
|
3117
|
+
subcmd: &str,
|
|
3118
|
+
args: &str,
|
|
3119
|
+
) {
|
|
2838
3120
|
match subcmd {
|
|
2839
|
-
"list" | "" => mcp_interactive_picker(session),
|
|
3121
|
+
"list" | "" => mcp_interactive_picker(session, ui),
|
|
2840
3122
|
|
|
2841
3123
|
"add" => {
|
|
2842
3124
|
let mut tokens: Vec<&str> = args.split_whitespace().collect();
|
|
@@ -2857,54 +3139,52 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2857
3139
|
if allow_all {
|
|
2858
3140
|
let _ = crate::mcp::allow(name, "*");
|
|
2859
3141
|
}
|
|
2860
|
-
|
|
2861
|
-
"
|
|
3142
|
+
ui.push_notice(format!(
|
|
3143
|
+
"Added MCP server '{name}'{}.",
|
|
2862
3144
|
if allow_all {
|
|
2863
3145
|
" (all tools allowed)"
|
|
2864
3146
|
} else {
|
|
2865
3147
|
""
|
|
2866
3148
|
}
|
|
2867
|
-
);
|
|
3149
|
+
));
|
|
2868
3150
|
reload_session_config(session);
|
|
2869
3151
|
}
|
|
2870
|
-
Err(e) =>
|
|
3152
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2871
3153
|
}
|
|
2872
3154
|
}
|
|
2873
|
-
_ =>
|
|
2874
|
-
"{WARN}/mcp add usage:{RESET} <name> <command|url> [args...] [--allow-all]\n"
|
|
2875
|
-
),
|
|
3155
|
+
_ => ui.push_notice("/mcp add usage: <name> <command|url> [args...] [--allow-all]"),
|
|
2876
3156
|
}
|
|
2877
3157
|
}
|
|
2878
3158
|
|
|
2879
3159
|
"remove" if !args.is_empty() => match crate::mcp::remove(args) {
|
|
2880
3160
|
Ok(true) => {
|
|
2881
|
-
|
|
3161
|
+
ui.push_notice(format!("Removed '{args}'."));
|
|
2882
3162
|
reload_session_config(session);
|
|
2883
3163
|
}
|
|
2884
|
-
Ok(false) =>
|
|
2885
|
-
Err(e) =>
|
|
3164
|
+
Ok(false) => ui.push_notice(format!("No MCP server named '{args}'.")),
|
|
3165
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2886
3166
|
},
|
|
2887
3167
|
|
|
2888
3168
|
"clear" => match crate::mcp::clear() {
|
|
2889
3169
|
Ok(()) => {
|
|
2890
|
-
|
|
3170
|
+
ui.push_notice("Removed all MCP servers.");
|
|
2891
3171
|
reload_session_config(session);
|
|
2892
3172
|
}
|
|
2893
|
-
Err(e) =>
|
|
3173
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2894
3174
|
},
|
|
2895
3175
|
|
|
2896
3176
|
"enable" | "disable" if !args.is_empty() => {
|
|
2897
3177
|
let disable = subcmd == "disable";
|
|
2898
3178
|
match crate::mcp::set_disabled(args, disable) {
|
|
2899
3179
|
Ok(true) => {
|
|
2900
|
-
|
|
2901
|
-
"{
|
|
3180
|
+
ui.push_notice(format!(
|
|
3181
|
+
"{} '{args}'.",
|
|
2902
3182
|
if disable { "Disabled" } else { "Enabled" }
|
|
2903
|
-
);
|
|
3183
|
+
));
|
|
2904
3184
|
reload_session_config(session);
|
|
2905
3185
|
}
|
|
2906
|
-
Ok(false) =>
|
|
2907
|
-
Err(e) =>
|
|
3186
|
+
Ok(false) => ui.push_notice(format!("No MCP server named '{args}'.")),
|
|
3187
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2908
3188
|
}
|
|
2909
3189
|
}
|
|
2910
3190
|
|
|
@@ -2918,9 +3198,7 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2918
3198
|
crate::mcp::edit(name, Some(value.to_string()), None, None, None)
|
|
2919
3199
|
}
|
|
2920
3200
|
"command" => {
|
|
2921
|
-
|
|
2922
|
-
"{WARN}/mcp edit <name> command <value> needs a value{RESET}\n"
|
|
2923
|
-
);
|
|
3201
|
+
ui.push_notice("/mcp edit <name> command <value> needs a value");
|
|
2924
3202
|
return;
|
|
2925
3203
|
}
|
|
2926
3204
|
"args" => crate::mcp::edit(
|
|
@@ -2938,20 +3216,20 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2938
3216
|
Some((!value.is_empty()).then(|| value.to_string())),
|
|
2939
3217
|
),
|
|
2940
3218
|
_ => {
|
|
2941
|
-
|
|
3219
|
+
ui.push_notice("Editable fields: command | args | icon");
|
|
2942
3220
|
return;
|
|
2943
3221
|
}
|
|
2944
3222
|
};
|
|
2945
3223
|
match result {
|
|
2946
3224
|
Ok(true) => {
|
|
2947
|
-
|
|
3225
|
+
ui.push_notice(format!("Updated '{name}'."));
|
|
2948
3226
|
reload_session_config(session);
|
|
2949
3227
|
}
|
|
2950
|
-
Ok(false) =>
|
|
2951
|
-
Err(e) =>
|
|
3228
|
+
Ok(false) => ui.push_notice(format!("No MCP server named '{name}'.")),
|
|
3229
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2952
3230
|
}
|
|
2953
3231
|
}
|
|
2954
|
-
_ =>
|
|
3232
|
+
_ => ui.push_notice("/mcp edit usage: <name> command|args|icon <value>"),
|
|
2955
3233
|
}
|
|
2956
3234
|
}
|
|
2957
3235
|
|
|
@@ -2966,45 +3244,43 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2966
3244
|
};
|
|
2967
3245
|
match result {
|
|
2968
3246
|
Ok(true) => {
|
|
2969
|
-
|
|
3247
|
+
ui.push_notice(format!("{verb} {server}/{tool}."));
|
|
2970
3248
|
reload_session_config(session);
|
|
2971
3249
|
}
|
|
2972
|
-
Ok(false) =>
|
|
2973
|
-
Err(e) =>
|
|
3250
|
+
Ok(false) => ui.push_notice(format!("No change for {server}/{tool}.")),
|
|
3251
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2974
3252
|
}
|
|
2975
3253
|
}
|
|
2976
|
-
_ =>
|
|
3254
|
+
_ => ui.push_notice(format!("/mcp {subcmd} usage: <server> <tool>")),
|
|
2977
3255
|
}
|
|
2978
3256
|
}
|
|
2979
3257
|
|
|
2980
3258
|
"reauth" if !args.is_empty() => {
|
|
2981
|
-
|
|
2982
|
-
"
|
|
2983
|
-
);
|
|
3259
|
+
ui.push_notice(format!(
|
|
3260
|
+
"Re-authenticating MCP server '{args}'... (a browser tab may open)"
|
|
3261
|
+
));
|
|
2984
3262
|
match crate::mcp::reauth(args) {
|
|
2985
|
-
Ok(true) =>
|
|
2986
|
-
Ok(false) =>
|
|
2987
|
-
|
|
2988
|
-
),
|
|
2989
|
-
Err(e) => println!("{ERROR}MCP error:{RESET} {e}\n"),
|
|
3263
|
+
Ok(true) => ui.push_notice(format!("Re-authentication succeeded for '{args}'.")),
|
|
3264
|
+
Ok(false) => ui.push_notice(format!("Re-authentication failed for '{args}'.")),
|
|
3265
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2990
3266
|
}
|
|
2991
3267
|
}
|
|
2992
3268
|
|
|
2993
|
-
_ =>
|
|
2994
|
-
"
|
|
3269
|
+
_ => ui.push_notice(
|
|
3270
|
+
"/mcp usage: list | add <name> <cmd> [args...] | remove <name> | \
|
|
2995
3271
|
enable|disable <name> | edit <name> command|args|icon <value> | \
|
|
2996
|
-
allow|disallow <server> <tool> | reauth <server> | clear
|
|
3272
|
+
allow|disallow <server> <tool> | reauth <server> | clear",
|
|
2997
3273
|
),
|
|
2998
3274
|
}
|
|
2999
3275
|
}
|
|
3000
3276
|
|
|
3001
|
-
/// The
|
|
3277
|
+
/// The `/mcp` flow: an "add" row plus every configured server; picking
|
|
3002
3278
|
/// a server opens a per-server action menu.
|
|
3003
|
-
fn mcp_interactive_picker(session: &mut InteractiveSession) {
|
|
3279
|
+
fn mcp_interactive_picker(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3004
3280
|
let servers = match crate::mcp::list() {
|
|
3005
3281
|
Ok(servers) => servers,
|
|
3006
3282
|
Err(e) => {
|
|
3007
|
-
|
|
3283
|
+
ui.push_notice(format!("MCP error: {e}"));
|
|
3008
3284
|
return;
|
|
3009
3285
|
}
|
|
3010
3286
|
};
|
|
@@ -3019,100 +3295,114 @@ fn mcp_interactive_picker(session: &mut InteractiveSession) {
|
|
|
3019
3295
|
.and_then(|v| v.as_object())
|
|
3020
3296
|
.cloned();
|
|
3021
3297
|
|
|
3022
|
-
let mut choices = vec![
|
|
3298
|
+
let mut choices = vec![ChoiceItem::with_description(
|
|
3299
|
+
ADD_ROW,
|
|
3300
|
+
"Connect a new MCP server (catalog, remote SSE, or local stdio)",
|
|
3301
|
+
ADD_ROW,
|
|
3302
|
+
)];
|
|
3023
3303
|
let mut names = Vec::new();
|
|
3024
|
-
let width = servers.keys().map(|k| k.len()).max().unwrap_or(6) + 2;
|
|
3025
3304
|
for (name, srv) in &servers {
|
|
3026
3305
|
let tools = allowed
|
|
3027
3306
|
.as_ref()
|
|
3028
3307
|
.and_then(|m| m.get(name))
|
|
3029
3308
|
.and_then(|v| v.as_array());
|
|
3030
3309
|
let label = match tools {
|
|
3031
|
-
Some(a) if a.iter().any(|t| t.as_str() == Some("*")) =>
|
|
3032
|
-
format!("{MINT}[tools: *]{RESET}")
|
|
3033
|
-
}
|
|
3310
|
+
Some(a) if a.iter().any(|t| t.as_str() == Some("*")) => "[tools: *]".to_string(),
|
|
3034
3311
|
Some(a) if !a.is_empty() => format!(
|
|
3035
|
-
"
|
|
3312
|
+
"[tools: {}]",
|
|
3036
3313
|
a.iter()
|
|
3037
3314
|
.filter_map(|t| t.as_str())
|
|
3038
3315
|
.collect::<Vec<_>>()
|
|
3039
3316
|
.join(", ")
|
|
3040
3317
|
),
|
|
3041
|
-
_ =>
|
|
3042
|
-
};
|
|
3043
|
-
let disabled = if srv.disabled {
|
|
3044
|
-
format!(" {DIM}(disabled){RESET}")
|
|
3045
|
-
} else {
|
|
3046
|
-
String::new()
|
|
3318
|
+
_ => "[no tools allowed]".to_string(),
|
|
3047
3319
|
};
|
|
3320
|
+
let disabled = if srv.disabled { " (disabled)" } else { "" };
|
|
3048
3321
|
let desc = if let Some(url) = srv.remote_url() {
|
|
3049
3322
|
format!("(url: {url})")
|
|
3050
3323
|
} else {
|
|
3051
3324
|
format!("({} {})", srv.command, srv.args.join(" "))
|
|
3052
3325
|
};
|
|
3053
|
-
choices.push(
|
|
3054
|
-
|
|
3326
|
+
choices.push(ChoiceItem::with_description(
|
|
3327
|
+
name.clone(),
|
|
3328
|
+
format!("{label}{disabled} {desc}"),
|
|
3329
|
+
name.clone(),
|
|
3055
3330
|
));
|
|
3056
3331
|
names.push(name.clone());
|
|
3057
3332
|
}
|
|
3058
|
-
choices.push(
|
|
3059
|
-
|
|
3060
|
-
|
|
3333
|
+
choices.push(ChoiceItem::with_description(
|
|
3334
|
+
CANCEL,
|
|
3335
|
+
"Close this menu",
|
|
3336
|
+
CANCEL,
|
|
3337
|
+
));
|
|
3338
|
+
|
|
3339
|
+
let Ok(Some(idx)) = ui.prompt_choice(
|
|
3340
|
+
"MCP Servers",
|
|
3341
|
+
"Select a server to manage or add a new one",
|
|
3342
|
+
&choices,
|
|
3343
|
+
) else {
|
|
3061
3344
|
return;
|
|
3062
3345
|
};
|
|
3346
|
+
let pick = &choices[idx].label;
|
|
3063
3347
|
if pick == CANCEL {
|
|
3064
3348
|
return;
|
|
3065
3349
|
}
|
|
3066
3350
|
if pick == ADD_ROW {
|
|
3067
|
-
mcp_add_flow(session);
|
|
3351
|
+
mcp_add_flow(session, ui);
|
|
3068
3352
|
return;
|
|
3069
3353
|
}
|
|
3070
|
-
let
|
|
3071
|
-
return;
|
|
3072
|
-
};
|
|
3073
|
-
let name = names[pos - 1].clone();
|
|
3354
|
+
let name = names[idx - 1].clone();
|
|
3074
3355
|
let disabled = servers.get(&name).map(|s| s.disabled).unwrap_or(false);
|
|
3075
|
-
mcp_server_actions(session, &name, disabled);
|
|
3356
|
+
mcp_server_actions(session, ui, &name, disabled);
|
|
3076
3357
|
}
|
|
3077
3358
|
|
|
3078
|
-
fn mcp_add_flow(session: &mut InteractiveSession) {
|
|
3359
|
+
fn mcp_add_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3079
3360
|
let choices = vec![
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3361
|
+
ChoiceItem::with_description(
|
|
3362
|
+
"From catalog",
|
|
3363
|
+
"Install from known MCP tools catalog",
|
|
3364
|
+
"catalog",
|
|
3365
|
+
),
|
|
3366
|
+
ChoiceItem::with_description(
|
|
3367
|
+
"Remote URL (SSE)",
|
|
3368
|
+
"Connect to remote HTTP/SSE MCP endpoint",
|
|
3369
|
+
"remote",
|
|
3370
|
+
),
|
|
3371
|
+
ChoiceItem::with_description(
|
|
3372
|
+
"Local Command (stdio)",
|
|
3373
|
+
"Run local command (e.g. npx, uvx)",
|
|
3374
|
+
"local",
|
|
3375
|
+
),
|
|
3083
3376
|
];
|
|
3084
|
-
match
|
|
3085
|
-
Ok(Some(
|
|
3086
|
-
Ok(Some(
|
|
3087
|
-
Ok(Some(
|
|
3377
|
+
match ui.prompt_choice("Add MCP server", "Select server type", &choices) {
|
|
3378
|
+
Ok(Some(0)) => mcp_add_from_catalog(session, ui),
|
|
3379
|
+
Ok(Some(1)) => mcp_add_remote_flow(session, ui),
|
|
3380
|
+
Ok(Some(2)) => mcp_add_custom_flow(session, ui),
|
|
3088
3381
|
_ => {}
|
|
3089
3382
|
}
|
|
3090
3383
|
}
|
|
3091
3384
|
|
|
3092
|
-
fn mcp_add_from_catalog(session: &mut InteractiveSession) {
|
|
3385
|
+
fn mcp_add_from_catalog(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3093
3386
|
let entries = crate::mcp::registry();
|
|
3094
|
-
let
|
|
3387
|
+
let choices: Vec<ChoiceItem> = entries
|
|
3095
3388
|
.iter()
|
|
3096
3389
|
.map(|e| {
|
|
3097
3390
|
let needs = if e.required_env.is_empty() {
|
|
3098
3391
|
String::new()
|
|
3099
3392
|
} else {
|
|
3100
|
-
|
|
3393
|
+
" (needs a key)".to_string()
|
|
3101
3394
|
};
|
|
3102
|
-
format!("{
|
|
3395
|
+
ChoiceItem::with_description(&e.name, format!("{}{needs}", e.desc), &e.key)
|
|
3103
3396
|
})
|
|
3104
3397
|
.collect();
|
|
3105
|
-
let Ok(Some(
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
.iter()
|
|
3111
|
-
.position(|l| *l == picked)
|
|
3112
|
-
.map(|i| &entries[i])
|
|
3113
|
-
else {
|
|
3398
|
+
let Ok(Some(idx)) = ui.prompt_choice(
|
|
3399
|
+
"MCP server catalog",
|
|
3400
|
+
"Select an MCP server from catalog",
|
|
3401
|
+
&choices,
|
|
3402
|
+
) else {
|
|
3114
3403
|
return;
|
|
3115
3404
|
};
|
|
3405
|
+
let entry = &entries[idx];
|
|
3116
3406
|
|
|
3117
3407
|
// One prompt per declared argInput (path / connection string / …).
|
|
3118
3408
|
let mut extra_args = Vec::new();
|
|
@@ -3122,10 +3412,14 @@ fn mcp_add_from_catalog(session: &mut InteractiveSession) {
|
|
|
3122
3412
|
} else {
|
|
3123
3413
|
format!("{} (e.g. {})", input.label, input.placeholder)
|
|
3124
3414
|
};
|
|
3125
|
-
let value =
|
|
3415
|
+
let value = ui
|
|
3416
|
+
.prompt_text(&entry.name, &hint, None)
|
|
3417
|
+
.ok()
|
|
3418
|
+
.flatten()
|
|
3419
|
+
.unwrap_or_default();
|
|
3126
3420
|
let value = value.trim();
|
|
3127
3421
|
if value.is_empty() {
|
|
3128
|
-
|
|
3422
|
+
ui.push_notice(format!("Cancelled ('{}' is required).", input.label));
|
|
3129
3423
|
return;
|
|
3130
3424
|
}
|
|
3131
3425
|
extra_args.push(value.to_string());
|
|
@@ -3134,65 +3428,96 @@ fn mcp_add_from_catalog(session: &mut InteractiveSession) {
|
|
|
3134
3428
|
// One prompt per required env var.
|
|
3135
3429
|
let mut env = Vec::new();
|
|
3136
3430
|
for var in &entry.required_env {
|
|
3137
|
-
if let Some(help) = &var.help {
|
|
3138
|
-
|
|
3139
|
-
}
|
|
3140
|
-
|
|
3431
|
+
let prompt_body = if let Some(help) = &var.help {
|
|
3432
|
+
format!("{} (get one at {help})", var.label)
|
|
3433
|
+
} else {
|
|
3434
|
+
var.label.clone()
|
|
3435
|
+
};
|
|
3436
|
+
let value = ui
|
|
3437
|
+
.prompt_text(&entry.name, &prompt_body, None)
|
|
3438
|
+
.ok()
|
|
3439
|
+
.flatten()
|
|
3440
|
+
.unwrap_or_default();
|
|
3141
3441
|
let value = value.trim();
|
|
3142
3442
|
if value.is_empty() {
|
|
3143
|
-
|
|
3443
|
+
ui.push_notice(format!("Cancelled ('{}' is required).", var.label));
|
|
3144
3444
|
return;
|
|
3145
3445
|
}
|
|
3146
3446
|
env.push(format!("{}={value}", var.key));
|
|
3147
3447
|
}
|
|
3148
3448
|
|
|
3149
|
-
let name =
|
|
3449
|
+
let name = ui
|
|
3450
|
+
.prompt_text(
|
|
3451
|
+
"Server name",
|
|
3452
|
+
"Choose a unique name for this MCP server",
|
|
3453
|
+
Some(&entry.key),
|
|
3454
|
+
)
|
|
3455
|
+
.ok()
|
|
3456
|
+
.flatten()
|
|
3457
|
+
.unwrap_or_default();
|
|
3150
3458
|
let name = if name.trim().is_empty() {
|
|
3151
3459
|
entry.key.clone()
|
|
3152
3460
|
} else {
|
|
3153
3461
|
name.trim().to_string()
|
|
3154
3462
|
};
|
|
3155
3463
|
|
|
3156
|
-
let allow_all =
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3464
|
+
let allow_all = ui
|
|
3465
|
+
.prompt_confirm(
|
|
3466
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3467
|
+
false,
|
|
3468
|
+
)
|
|
3469
|
+
.unwrap_or(false);
|
|
3160
3470
|
|
|
3161
3471
|
match crate::mcp::registry_add(&entry.key, Some(&name), extra_args, env, allow_all) {
|
|
3162
3472
|
Ok(saved) => {
|
|
3163
|
-
|
|
3164
|
-
"
|
|
3473
|
+
ui.push_notice(format!(
|
|
3474
|
+
"Added MCP server '{saved}'{}.",
|
|
3165
3475
|
if allow_all {
|
|
3166
3476
|
" (all tools allowed)"
|
|
3167
3477
|
} else {
|
|
3168
3478
|
""
|
|
3169
3479
|
}
|
|
3170
|
-
);
|
|
3480
|
+
));
|
|
3171
3481
|
reload_session_config(session);
|
|
3172
3482
|
}
|
|
3173
|
-
Err(e) =>
|
|
3483
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3174
3484
|
}
|
|
3175
3485
|
}
|
|
3176
3486
|
|
|
3177
|
-
fn mcp_add_remote_flow(session: &mut InteractiveSession) {
|
|
3178
|
-
let name =
|
|
3487
|
+
fn mcp_add_remote_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3488
|
+
let name = ui
|
|
3489
|
+
.prompt_text("Remote MCP Server", "Server name", None)
|
|
3490
|
+
.ok()
|
|
3491
|
+
.flatten()
|
|
3492
|
+
.unwrap_or_default();
|
|
3179
3493
|
let name = name.trim().to_string();
|
|
3180
3494
|
if name.is_empty() {
|
|
3181
|
-
|
|
3495
|
+
ui.push_notice("Cancelled (no name).");
|
|
3182
3496
|
return;
|
|
3183
3497
|
}
|
|
3184
|
-
let url =
|
|
3498
|
+
let url = ui
|
|
3499
|
+
.prompt_text(
|
|
3500
|
+
"Remote MCP Server",
|
|
3501
|
+
"Server URL (e.g. https://example.com/sse)",
|
|
3502
|
+
None,
|
|
3503
|
+
)
|
|
3504
|
+
.ok()
|
|
3505
|
+
.flatten()
|
|
3185
3506
|
.unwrap_or_default();
|
|
3186
3507
|
let url = url.trim().to_string();
|
|
3187
3508
|
if url.is_empty() {
|
|
3188
|
-
|
|
3509
|
+
ui.push_notice("Cancelled (no URL).");
|
|
3189
3510
|
return;
|
|
3190
3511
|
}
|
|
3191
|
-
let auth =
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3512
|
+
let auth = ui
|
|
3513
|
+
.prompt_text(
|
|
3514
|
+
"Remote MCP Server",
|
|
3515
|
+
"Auth header or bearer token (blank for none, e.g. Bearer token)",
|
|
3516
|
+
None,
|
|
3517
|
+
)
|
|
3518
|
+
.ok()
|
|
3519
|
+
.flatten()
|
|
3520
|
+
.unwrap_or_default();
|
|
3196
3521
|
let headers = if auth.trim().is_empty() {
|
|
3197
3522
|
None
|
|
3198
3523
|
} else {
|
|
@@ -3212,46 +3537,65 @@ fn mcp_add_remote_flow(session: &mut InteractiveSession) {
|
|
|
3212
3537
|
|
|
3213
3538
|
match crate::mcp::add_remote(&name, &url, headers) {
|
|
3214
3539
|
Ok(()) => {
|
|
3215
|
-
let allow_all =
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3540
|
+
let allow_all = ui
|
|
3541
|
+
.prompt_confirm(
|
|
3542
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3543
|
+
false,
|
|
3544
|
+
)
|
|
3545
|
+
.unwrap_or(false);
|
|
3219
3546
|
if allow_all {
|
|
3220
3547
|
let _ = crate::mcp::allow(&name, "*");
|
|
3221
3548
|
}
|
|
3222
|
-
|
|
3223
|
-
"
|
|
3549
|
+
ui.push_notice(format!(
|
|
3550
|
+
"Added Remote MCP server '{name}'{}.",
|
|
3224
3551
|
if allow_all {
|
|
3225
3552
|
" (all tools allowed)"
|
|
3226
3553
|
} else {
|
|
3227
3554
|
""
|
|
3228
3555
|
}
|
|
3229
|
-
);
|
|
3556
|
+
));
|
|
3230
3557
|
reload_session_config(session);
|
|
3231
3558
|
}
|
|
3232
|
-
Err(e) =>
|
|
3559
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3233
3560
|
}
|
|
3234
3561
|
}
|
|
3235
3562
|
|
|
3236
|
-
fn mcp_add_custom_flow(session: &mut InteractiveSession) {
|
|
3237
|
-
let name =
|
|
3563
|
+
fn mcp_add_custom_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3564
|
+
let name = ui
|
|
3565
|
+
.prompt_text("Custom MCP Server", "Server name", None)
|
|
3566
|
+
.ok()
|
|
3567
|
+
.flatten()
|
|
3568
|
+
.unwrap_or_default();
|
|
3238
3569
|
let name = name.trim().to_string();
|
|
3239
3570
|
if name.is_empty() {
|
|
3240
|
-
|
|
3571
|
+
ui.push_notice("Cancelled (no name).");
|
|
3241
3572
|
return;
|
|
3242
3573
|
}
|
|
3243
|
-
let command =
|
|
3574
|
+
let command = ui
|
|
3575
|
+
.prompt_text(
|
|
3576
|
+
"Custom MCP Server",
|
|
3577
|
+
"Command or URL (e.g. npx or https://...)",
|
|
3578
|
+
None,
|
|
3579
|
+
)
|
|
3580
|
+
.ok()
|
|
3581
|
+
.flatten()
|
|
3244
3582
|
.unwrap_or_default();
|
|
3245
3583
|
let command = command.trim().to_string();
|
|
3246
3584
|
if command.is_empty() {
|
|
3247
|
-
|
|
3585
|
+
ui.push_notice("Cancelled (no command).");
|
|
3248
3586
|
return;
|
|
3249
3587
|
}
|
|
3250
3588
|
|
|
3251
3589
|
if command.starts_with("http://") || command.starts_with("https://") {
|
|
3252
|
-
let auth =
|
|
3253
|
-
|
|
3254
|
-
|
|
3590
|
+
let auth = ui
|
|
3591
|
+
.prompt_text(
|
|
3592
|
+
"Remote MCP Server",
|
|
3593
|
+
"Auth header or bearer token (blank for none)",
|
|
3594
|
+
None,
|
|
3595
|
+
)
|
|
3596
|
+
.ok()
|
|
3597
|
+
.flatten()
|
|
3598
|
+
.unwrap_or_default();
|
|
3255
3599
|
let headers = if auth.trim().is_empty() {
|
|
3256
3600
|
None
|
|
3257
3601
|
} else {
|
|
@@ -3270,35 +3614,48 @@ fn mcp_add_custom_flow(session: &mut InteractiveSession) {
|
|
|
3270
3614
|
};
|
|
3271
3615
|
match crate::mcp::add_remote(&name, &command, headers) {
|
|
3272
3616
|
Ok(()) => {
|
|
3273
|
-
let allow_all =
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3617
|
+
let allow_all = ui
|
|
3618
|
+
.prompt_confirm(
|
|
3619
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3620
|
+
false,
|
|
3621
|
+
)
|
|
3622
|
+
.unwrap_or(false);
|
|
3277
3623
|
if allow_all {
|
|
3278
3624
|
let _ = crate::mcp::allow(&name, "*");
|
|
3279
3625
|
}
|
|
3280
|
-
|
|
3281
|
-
"
|
|
3626
|
+
ui.push_notice(format!(
|
|
3627
|
+
"Added Remote MCP server '{name}'{}.",
|
|
3282
3628
|
if allow_all {
|
|
3283
3629
|
" (all tools allowed)"
|
|
3284
3630
|
} else {
|
|
3285
3631
|
""
|
|
3286
3632
|
}
|
|
3287
|
-
);
|
|
3633
|
+
));
|
|
3288
3634
|
reload_session_config(session);
|
|
3289
3635
|
}
|
|
3290
|
-
Err(e) =>
|
|
3636
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3291
3637
|
}
|
|
3292
3638
|
return;
|
|
3293
3639
|
}
|
|
3294
3640
|
|
|
3295
|
-
let args =
|
|
3641
|
+
let args = ui
|
|
3642
|
+
.prompt_text(
|
|
3643
|
+
"Custom MCP Server",
|
|
3644
|
+
"Arguments (space-separated, blank for none)",
|
|
3645
|
+
None,
|
|
3646
|
+
)
|
|
3647
|
+
.ok()
|
|
3648
|
+
.flatten()
|
|
3649
|
+
.unwrap_or_default();
|
|
3650
|
+
let env_raw = ui
|
|
3651
|
+
.prompt_text(
|
|
3652
|
+
"Custom MCP Server",
|
|
3653
|
+
"Env vars (KEY=VALUE, comma-separated, blank for none)",
|
|
3654
|
+
None,
|
|
3655
|
+
)
|
|
3656
|
+
.ok()
|
|
3657
|
+
.flatten()
|
|
3296
3658
|
.unwrap_or_default();
|
|
3297
|
-
let env_raw = crate::onboard::prompt_input(
|
|
3298
|
-
"Env vars (KEY=VALUE, comma-separated, blank for none)",
|
|
3299
|
-
None,
|
|
3300
|
-
)
|
|
3301
|
-
.unwrap_or_default();
|
|
3302
3659
|
|
|
3303
3660
|
let args: Vec<String> = args.split_whitespace().map(str::to_string).collect();
|
|
3304
3661
|
let env: Vec<String> = env_raw
|
|
@@ -3310,79 +3667,115 @@ fn mcp_add_custom_flow(session: &mut InteractiveSession) {
|
|
|
3310
3667
|
|
|
3311
3668
|
match crate::mcp::add(&name, &command, args, env) {
|
|
3312
3669
|
Ok(()) => {
|
|
3313
|
-
let allow_all =
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3670
|
+
let allow_all = ui
|
|
3671
|
+
.prompt_confirm(
|
|
3672
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3673
|
+
false,
|
|
3674
|
+
)
|
|
3675
|
+
.unwrap_or(false);
|
|
3317
3676
|
if allow_all {
|
|
3318
3677
|
let _ = crate::mcp::allow(&name, "*");
|
|
3319
3678
|
}
|
|
3320
|
-
|
|
3321
|
-
"
|
|
3679
|
+
ui.push_notice(format!(
|
|
3680
|
+
"Added MCP server '{name}'{}.",
|
|
3322
3681
|
if allow_all {
|
|
3323
3682
|
" (all tools allowed)"
|
|
3324
3683
|
} else {
|
|
3325
3684
|
""
|
|
3326
3685
|
}
|
|
3327
|
-
);
|
|
3686
|
+
));
|
|
3328
3687
|
reload_session_config(session);
|
|
3329
3688
|
}
|
|
3330
|
-
Err(e) =>
|
|
3689
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3331
3690
|
}
|
|
3332
3691
|
}
|
|
3333
3692
|
|
|
3334
|
-
fn mcp_server_actions(
|
|
3693
|
+
fn mcp_server_actions(
|
|
3694
|
+
session: &mut InteractiveSession,
|
|
3695
|
+
ui: &mut dyn CommandUi,
|
|
3696
|
+
name: &str,
|
|
3697
|
+
disabled: bool,
|
|
3698
|
+
) {
|
|
3335
3699
|
let toggle_label = if disabled {
|
|
3336
3700
|
"Enable server"
|
|
3337
3701
|
} else {
|
|
3338
3702
|
"Disable server"
|
|
3339
3703
|
};
|
|
3340
3704
|
let actions = vec![
|
|
3341
|
-
"Check connection"
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3705
|
+
ChoiceItem::with_description("Check connection", "Test tool discovery", "check"),
|
|
3706
|
+
ChoiceItem::with_description(
|
|
3707
|
+
"Allow all tools (*)",
|
|
3708
|
+
"Grant unrestricted tool call access",
|
|
3709
|
+
"allow_all",
|
|
3710
|
+
),
|
|
3711
|
+
ChoiceItem::with_description(
|
|
3712
|
+
"Allow a specific tool",
|
|
3713
|
+
"Whitelist an individual tool name",
|
|
3714
|
+
"allow_tool",
|
|
3715
|
+
),
|
|
3716
|
+
ChoiceItem::with_description(
|
|
3717
|
+
"Disallow a tool",
|
|
3718
|
+
"Remove a tool from allowlist",
|
|
3719
|
+
"disallow_tool",
|
|
3720
|
+
),
|
|
3721
|
+
ChoiceItem::with_description(
|
|
3722
|
+
"Re-authenticate (OAuth)",
|
|
3723
|
+
"Trigger browser OAuth login flow",
|
|
3724
|
+
"reauth",
|
|
3725
|
+
),
|
|
3726
|
+
ChoiceItem::with_description(toggle_label, "Toggle server active status", "toggle"),
|
|
3727
|
+
ChoiceItem::with_description(
|
|
3728
|
+
"Edit (command / args / icon)",
|
|
3729
|
+
"Update server settings",
|
|
3730
|
+
"edit",
|
|
3731
|
+
),
|
|
3732
|
+
ChoiceItem::with_description("Remove server", "Delete server from config", "remove"),
|
|
3733
|
+
ChoiceItem::with_description("Back", "Return to MCP servers list", "back"),
|
|
3350
3734
|
];
|
|
3351
|
-
let Ok(Some(
|
|
3352
|
-
prompt_interactive_select(&format!("MCP: {name}"), &actions, &actions[0])
|
|
3353
|
-
else {
|
|
3735
|
+
let Ok(Some(idx)) = ui.prompt_choice(&format!("MCP: {name}"), "Choose action", &actions) else {
|
|
3354
3736
|
return;
|
|
3355
3737
|
};
|
|
3356
3738
|
|
|
3357
|
-
match
|
|
3358
|
-
"
|
|
3359
|
-
Ok(_) =>
|
|
3360
|
-
Err(e) =>
|
|
3739
|
+
match actions[idx].value.as_str() {
|
|
3740
|
+
"check" => match mint_core::list_server_tools(&session.config, name) {
|
|
3741
|
+
Ok(_) => ui.push_notice("● Connected"),
|
|
3742
|
+
Err(e) => ui.push_notice(format!("● Not connected: {e}")),
|
|
3361
3743
|
},
|
|
3362
|
-
"
|
|
3744
|
+
"allow_all" => match crate::mcp::allow(name, "*") {
|
|
3363
3745
|
Ok(_) => {
|
|
3364
|
-
|
|
3746
|
+
ui.push_notice(format!("Allowed all tools for '{name}'."));
|
|
3365
3747
|
reload_session_config(session);
|
|
3366
3748
|
}
|
|
3367
|
-
Err(e) =>
|
|
3749
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3368
3750
|
},
|
|
3369
|
-
"
|
|
3370
|
-
let tool =
|
|
3751
|
+
"allow_tool" => {
|
|
3752
|
+
let tool = ui
|
|
3753
|
+
.prompt_text(&format!("MCP: {name}"), "Tool name", None)
|
|
3754
|
+
.ok()
|
|
3755
|
+
.flatten()
|
|
3756
|
+
.unwrap_or_default();
|
|
3371
3757
|
let tool = tool.trim();
|
|
3372
3758
|
if tool.is_empty() {
|
|
3373
3759
|
return;
|
|
3374
3760
|
}
|
|
3375
3761
|
match crate::mcp::allow(name, tool) {
|
|
3376
3762
|
Ok(true) => {
|
|
3377
|
-
|
|
3763
|
+
ui.push_notice(format!("Allowed {name}/{tool}."));
|
|
3378
3764
|
reload_session_config(session);
|
|
3379
3765
|
}
|
|
3380
|
-
Ok(false) =>
|
|
3381
|
-
Err(e) =>
|
|
3766
|
+
Ok(false) => ui.push_notice(format!("{name}/{tool} was already allowed.")),
|
|
3767
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3382
3768
|
}
|
|
3383
3769
|
}
|
|
3384
|
-
"
|
|
3385
|
-
let tool =
|
|
3770
|
+
"disallow_tool" => {
|
|
3771
|
+
let tool = ui
|
|
3772
|
+
.prompt_text(
|
|
3773
|
+
&format!("MCP: {name}"),
|
|
3774
|
+
"Tool to remove (* to clear all)",
|
|
3775
|
+
None,
|
|
3776
|
+
)
|
|
3777
|
+
.ok()
|
|
3778
|
+
.flatten()
|
|
3386
3779
|
.unwrap_or_default();
|
|
3387
3780
|
let tool = tool.trim();
|
|
3388
3781
|
if tool.is_empty() {
|
|
@@ -3390,42 +3783,45 @@ fn mcp_server_actions(session: &mut InteractiveSession, name: &str, disabled: bo
|
|
|
3390
3783
|
}
|
|
3391
3784
|
match crate::mcp::disallow(name, tool) {
|
|
3392
3785
|
Ok(true) => {
|
|
3393
|
-
|
|
3786
|
+
ui.push_notice(format!("Removed {name}/{tool} from the allowlist."));
|
|
3394
3787
|
reload_session_config(session);
|
|
3395
3788
|
}
|
|
3396
|
-
Ok(false) =>
|
|
3397
|
-
Err(e) =>
|
|
3789
|
+
Ok(false) => ui.push_notice(format!("{name}/{tool} was not in the allowlist.")),
|
|
3790
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3398
3791
|
}
|
|
3399
3792
|
}
|
|
3400
|
-
"
|
|
3401
|
-
|
|
3793
|
+
"reauth" => {
|
|
3794
|
+
ui.push_notice(format!(
|
|
3795
|
+
"Re-authenticating '{name}'... (a browser tab may open)"
|
|
3796
|
+
));
|
|
3402
3797
|
match crate::mcp::reauth(name) {
|
|
3403
|
-
Ok(true) =>
|
|
3404
|
-
Ok(false) =>
|
|
3405
|
-
|
|
3406
|
-
}
|
|
3407
|
-
Err(e) => println!("{ERROR}MCP error:{RESET} {e}\n"),
|
|
3798
|
+
Ok(true) => ui.push_notice("Re-authentication succeeded."),
|
|
3799
|
+
Ok(false) => ui.push_notice("Re-authentication failed (see output above)."),
|
|
3800
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3408
3801
|
}
|
|
3409
3802
|
}
|
|
3410
|
-
|
|
3803
|
+
"toggle" => match crate::mcp::set_disabled(name, !disabled) {
|
|
3411
3804
|
Ok(_) => {
|
|
3412
|
-
|
|
3413
|
-
"{
|
|
3805
|
+
ui.push_notice(format!(
|
|
3806
|
+
"{} '{name}'.",
|
|
3414
3807
|
if disabled { "Enabled" } else { "Disabled" }
|
|
3415
|
-
);
|
|
3808
|
+
));
|
|
3416
3809
|
reload_session_config(session);
|
|
3417
3810
|
}
|
|
3418
|
-
Err(e) =>
|
|
3811
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3419
3812
|
},
|
|
3420
|
-
"
|
|
3421
|
-
"
|
|
3422
|
-
if
|
|
3813
|
+
"edit" => mcp_edit_flow(session, ui, name),
|
|
3814
|
+
"remove" => {
|
|
3815
|
+
if ui
|
|
3816
|
+
.prompt_confirm(&format!("Remove MCP server '{name}'?"), false)
|
|
3817
|
+
.unwrap_or(false)
|
|
3818
|
+
{
|
|
3423
3819
|
match crate::mcp::remove(name) {
|
|
3424
3820
|
Ok(_) => {
|
|
3425
|
-
|
|
3821
|
+
ui.push_notice(format!("Removed '{name}'."));
|
|
3426
3822
|
reload_session_config(session);
|
|
3427
3823
|
}
|
|
3428
|
-
Err(e) =>
|
|
3824
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3429
3825
|
}
|
|
3430
3826
|
}
|
|
3431
3827
|
}
|
|
@@ -3433,25 +3829,28 @@ fn mcp_server_actions(session: &mut InteractiveSession, name: &str, disabled: bo
|
|
|
3433
3829
|
}
|
|
3434
3830
|
}
|
|
3435
3831
|
|
|
3436
|
-
fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
3832
|
+
fn mcp_edit_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi, name: &str) {
|
|
3437
3833
|
let Some(srv) = crate::mcp::list().ok().and_then(|m| m.get(name).cloned()) else {
|
|
3438
|
-
|
|
3834
|
+
ui.push_notice(format!("No MCP server named '{name}'."));
|
|
3439
3835
|
return;
|
|
3440
3836
|
};
|
|
3441
3837
|
let fields = vec![
|
|
3442
|
-
"command".
|
|
3443
|
-
"args".
|
|
3444
|
-
"icon".
|
|
3445
|
-
"Back"
|
|
3838
|
+
ChoiceItem::with_description("command", &srv.command, "command"),
|
|
3839
|
+
ChoiceItem::with_description("args", srv.args.join(" "), "args"),
|
|
3840
|
+
ChoiceItem::with_description("icon", srv.icon.as_deref().unwrap_or("(none)"), "icon"),
|
|
3841
|
+
ChoiceItem::with_description("Back", "Cancel edit", "back"),
|
|
3446
3842
|
];
|
|
3447
|
-
let Ok(Some(
|
|
3843
|
+
let Ok(Some(idx)) = ui.prompt_choice(&format!("Edit {name}"), "Select field to edit", &fields)
|
|
3448
3844
|
else {
|
|
3449
3845
|
return;
|
|
3450
3846
|
};
|
|
3451
|
-
let result = match
|
|
3847
|
+
let result = match fields[idx].value.as_str() {
|
|
3452
3848
|
"command" => {
|
|
3453
|
-
let value =
|
|
3454
|
-
|
|
3849
|
+
let value = ui
|
|
3850
|
+
.prompt_text(&format!("Edit {name}"), "Command", Some(&srv.command))
|
|
3851
|
+
.ok()
|
|
3852
|
+
.flatten()
|
|
3853
|
+
.unwrap_or_default();
|
|
3455
3854
|
let value = value.trim();
|
|
3456
3855
|
if value.is_empty() {
|
|
3457
3856
|
return;
|
|
@@ -3460,7 +3859,14 @@ fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
|
3460
3859
|
}
|
|
3461
3860
|
"args" => {
|
|
3462
3861
|
let current = srv.args.join(" ");
|
|
3463
|
-
let value =
|
|
3862
|
+
let value = ui
|
|
3863
|
+
.prompt_text(
|
|
3864
|
+
&format!("Edit {name}"),
|
|
3865
|
+
"Arguments (space-separated)",
|
|
3866
|
+
Some(¤t),
|
|
3867
|
+
)
|
|
3868
|
+
.ok()
|
|
3869
|
+
.flatten()
|
|
3464
3870
|
.unwrap_or_default();
|
|
3465
3871
|
crate::mcp::edit(
|
|
3466
3872
|
name,
|
|
@@ -3472,7 +3878,14 @@ fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
|
3472
3878
|
}
|
|
3473
3879
|
"icon" => {
|
|
3474
3880
|
let current = srv.icon.clone().unwrap_or_default();
|
|
3475
|
-
let value =
|
|
3881
|
+
let value = ui
|
|
3882
|
+
.prompt_text(
|
|
3883
|
+
&format!("Edit {name}"),
|
|
3884
|
+
"Icon (blank to clear)",
|
|
3885
|
+
Some(¤t),
|
|
3886
|
+
)
|
|
3887
|
+
.ok()
|
|
3888
|
+
.flatten()
|
|
3476
3889
|
.unwrap_or_default();
|
|
3477
3890
|
let value = value.trim();
|
|
3478
3891
|
crate::mcp::edit(
|
|
@@ -3487,10 +3900,10 @@ fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
|
3487
3900
|
};
|
|
3488
3901
|
match result {
|
|
3489
3902
|
Ok(_) => {
|
|
3490
|
-
|
|
3903
|
+
ui.push_notice(format!("Updated '{name}'."));
|
|
3491
3904
|
reload_session_config(session);
|
|
3492
3905
|
}
|
|
3493
|
-
Err(e) =>
|
|
3906
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3494
3907
|
}
|
|
3495
3908
|
}
|
|
3496
3909
|
|
|
@@ -3500,7 +3913,8 @@ mod tests {
|
|
|
3500
3913
|
|
|
3501
3914
|
/// Commands that are dispatched but deliberately left undocumented —
|
|
3502
3915
|
/// shortcuts for another command's own token, not gaps in `SLASH_COMMANDS`.
|
|
3503
|
-
const UNDOCUMENTED_ALIASES: &[&str] =
|
|
3916
|
+
const UNDOCUMENTED_ALIASES: &[&str] =
|
|
3917
|
+
&["/quit", "/reset", "/plugin", "/searchProvider", "/think"];
|
|
3504
3918
|
|
|
3505
3919
|
/// Regression guard for the exact bug that motivated `SLASH_COMMANDS`:
|
|
3506
3920
|
/// `/edit-image`, `/gen-image`, `/shells`, and `/subagent` all worked but
|
|
@@ -3555,6 +3969,7 @@ mod tests {
|
|
|
3555
3969
|
#[tokio::test]
|
|
3556
3970
|
async fn core_slash_clear_clears_history() {
|
|
3557
3971
|
let mut session = InteractiveSession {
|
|
3972
|
+
chat_id: mint_core::CHAT_CLI_ID.to_string(),
|
|
3558
3973
|
config: MintConfig::default(),
|
|
3559
3974
|
current_dir: std::env::current_dir().unwrap(),
|
|
3560
3975
|
fast_mode: false,
|
|
@@ -3564,7 +3979,8 @@ mod tests {
|
|
|
3564
3979
|
jobs: BackgroundJobs::new(),
|
|
3565
3980
|
};
|
|
3566
3981
|
|
|
3567
|
-
let
|
|
3982
|
+
let mut ui = MockCommandUi::new();
|
|
3983
|
+
let res = handle_slash_command(&mut session, &mut ui, "/clear").await;
|
|
3568
3984
|
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
3569
3985
|
assert!(session.history.is_empty());
|
|
3570
3986
|
}
|
|
@@ -3572,6 +3988,7 @@ mod tests {
|
|
|
3572
3988
|
#[tokio::test]
|
|
3573
3989
|
async fn core_slash_fast_mode_toggle() {
|
|
3574
3990
|
let mut session = InteractiveSession {
|
|
3991
|
+
chat_id: mint_core::CHAT_CLI_ID.to_string(),
|
|
3575
3992
|
config: MintConfig::default(),
|
|
3576
3993
|
current_dir: std::env::current_dir().unwrap(),
|
|
3577
3994
|
fast_mode: false,
|
|
@@ -3581,7 +3998,8 @@ mod tests {
|
|
|
3581
3998
|
jobs: BackgroundJobs::new(),
|
|
3582
3999
|
};
|
|
3583
4000
|
|
|
3584
|
-
let
|
|
4001
|
+
let mut ui = MockCommandUi::new();
|
|
4002
|
+
let res = handle_slash_command(&mut session, &mut ui, "/fast on").await;
|
|
3585
4003
|
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
3586
4004
|
assert!(session.fast_mode);
|
|
3587
4005
|
}
|