@pheem49/mint 1.14.0 → 1.16.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/Cargo.lock +1258 -35
- package/Cargo.toml +5 -2
- package/LICENSE +21 -654
- package/README.md +93 -756
- package/Release_Note.md +53 -446
- package/apps/mint-companion/src-tauri/Cargo.toml +20 -0
- package/apps/mint-companion/src-tauri/build.rs +3 -0
- package/apps/mint-companion/src-tauri/src/main.rs +468 -0
- package/apps/mint-companion/tsconfig.json +20 -0
- package/apps/mint-companion/vite.config.ts +3 -0
- package/components.json +19 -0
- package/crates/mint-cli/Cargo.toml +9 -1
- package/crates/mint-cli/src/agent/approval_prompts.rs +16 -3
- package/crates/mint-cli/src/agent/live_status.rs +545 -148
- package/crates/mint-cli/src/agent/markdown_render.rs +601 -61
- package/crates/mint-cli/src/agent.rs +723 -258
- package/crates/mint-cli/src/commands/agent.rs +83 -74
- 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 +35 -0
- package/crates/mint-cli/src/commands/knowledge.rs +13 -32
- package/crates/mint-cli/src/commands/mod.rs +81 -2
- package/crates/mint-cli/src/commands/tailnet_web.rs +235 -0
- package/crates/mint-cli/src/image.rs +32 -11
- package/crates/mint-cli/src/interactive/chat_tui.rs +7853 -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 +94 -8
- 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 +1071 -501
- package/crates/mint-cli/src/interactive/thought_viewer.rs +286 -0
- package/crates/mint-cli/src/interactive/tool_activity.rs +328 -0
- package/crates/mint-cli/src/interactive.rs +454 -166
- package/crates/mint-cli/src/main.rs +93 -26
- package/crates/mint-cli/src/markdown.rs +12 -5
- package/crates/mint-cli/src/mcp.rs +5 -0
- package/crates/mint-cli/src/mod_host.rs +6 -0
- package/crates/mint-cli/src/mods_cli.rs +62 -0
- package/crates/mint-cli/src/mods_ui.rs +187 -0
- package/crates/mint-cli/src/onboard.rs +37 -61
- 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 +189 -0
- package/crates/mint-cli/src/updater.rs +112 -23
- package/crates/mint-cli/tests/library_intake.rs +77 -0
- package/crates/mint-cli/tests/mods_testing.rs +133 -0
- package/crates/mint-companion-protocol/Cargo.toml +10 -0
- package/crates/mint-companion-protocol/src/lib.rs +163 -0
- package/crates/mint-core/Cargo.toml +6 -3
- package/crates/mint-core/build.rs +26 -0
- package/crates/mint-core/src/agent/agent_loop.rs +1 -0
- package/crates/mint-core/src/agent/chat.rs +1003 -209
- package/crates/mint-core/src/agent/code_tools.rs +98 -2
- package/crates/mint-core/src/agent/memory/sources.rs +240 -0
- package/crates/mint-core/src/agent/memory.rs +805 -33
- package/crates/mint-core/src/agent/skill_inventory.rs +325 -0
- package/crates/mint-core/src/agent/skills.rs +437 -90
- package/crates/mint-core/src/agent.rs +1 -0
- package/crates/mint-core/src/api_server/routes/background_jobs.rs +310 -0
- package/crates/mint-core/src/api_server/routes/chat.rs +233 -190
- package/crates/mint-core/src/api_server/routes/html_preview.rs +77 -0
- package/crates/mint-core/src/api_server/routes/library.rs +97 -0
- package/crates/mint-core/src/api_server/routes/linked_folders.rs +118 -3
- package/crates/mint-core/src/api_server/routes/media_gen.rs +10 -57
- package/crates/mint-core/src/api_server/routes/misc.rs +114 -121
- package/crates/mint-core/src/api_server/routes/mod.rs +6 -0
- package/crates/mint-core/src/api_server/routes/mods.rs +31 -0
- package/crates/mint-core/src/api_server/routes/sessions.rs +85 -0
- package/crates/mint-core/src/api_server/routes/skills_subagents.rs +12 -42
- package/crates/mint-core/src/api_server/routes/slash.rs +50 -1
- package/crates/mint-core/src/api_server/routes/sources.rs +101 -0
- package/crates/mint-core/src/api_server.rs +140 -12
- package/crates/mint-core/src/avatar_bridge.rs +13 -2
- 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 +1610 -0
- package/crates/mint-core/src/companion.rs +716 -0
- package/crates/mint-core/src/cron/scheduler.rs +34 -2
- 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 +88 -345
- package/crates/mint-core/src/integrations/mcp.rs +397 -50
- package/crates/mint-core/src/integrations/mcp_calls.rs +823 -0
- package/crates/mint-core/src/integrations/mcp_catalog.rs +267 -0
- package/crates/mint-core/src/integrations/mcp_device.rs +319 -0
- package/crates/mint-core/src/integrations/mcp_result.rs +356 -0
- package/crates/mint-core/src/integrations/webhooks.rs +539 -0
- package/crates/mint-core/src/integrations.rs +2 -0
- package/crates/mint-core/src/lib.rs +107 -40
- 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 +145 -14
- package/crates/mint-core/src/media/image_models.rs +3 -27
- package/crates/mint-core/src/media/image_workflow.rs +216 -0
- package/crates/mint-core/src/media/library/intake.rs +144 -0
- package/crates/mint-core/src/media/library/migration.rs +56 -0
- package/crates/mint-core/src/media/library/removal.rs +55 -0
- package/crates/mint-core/src/media/library/tests.rs +730 -0
- package/crates/mint-core/src/media/library/thumbnails.rs +72 -0
- package/crates/mint-core/src/media/library.rs +545 -0
- package/crates/mint-core/src/media/mic_transcribe.rs +1 -0
- package/crates/mint-core/src/media/model_catalog.rs +375 -0
- package/crates/mint-core/src/media/pictures.rs +79 -222
- package/crates/mint-core/src/media/subtitle.rs +1 -0
- package/crates/mint-core/src/media/video_models.rs +3 -22
- package/crates/mint-core/src/media.rs +3 -0
- package/crates/mint-core/src/mods/manager.rs +1124 -0
- package/crates/mint-core/src/mods/mod.rs +569 -0
- package/crates/mint-core/src/mods/operations.rs +204 -0
- package/crates/mint-core/src/mods/session.rs +146 -0
- package/crates/mint-core/src/mods/slash.rs +59 -0
- package/crates/mint-core/src/mods/telemetry.rs +306 -0
- package/crates/mint-core/src/orchestration/approvals.rs +344 -0
- package/crates/mint-core/src/orchestration/context_render.rs +105 -0
- package/crates/mint-core/src/orchestration/mcp_review.rs +153 -0
- package/crates/mint-core/src/orchestration/memory_skill.rs +110 -33
- package/crates/mint-core/src/orchestration/mod.rs +3129 -481
- package/crates/mint-core/src/orchestration/mod_authoring_regressions.rs +209 -0
- package/crates/mint-core/src/orchestration/progress_clock.rs +80 -0
- package/crates/mint-core/src/orchestration/repeated_failures.rs +116 -0
- package/crates/mint-core/src/orchestration/review_regressions.rs +449 -0
- package/crates/mint-core/src/orchestration/run_control.rs +196 -0
- package/crates/mint-core/src/orchestration/tools/browser.rs +16 -139
- package/crates/mint-core/src/orchestration/tools/files.rs +161 -14
- package/crates/mint-core/src/orchestration/tools/media.rs +37 -41
- package/crates/mint-core/src/orchestration/tools/misc.rs +5 -3
- package/crates/mint-core/src/orchestration/tools/mod.rs +2 -0
- package/crates/mint-core/src/orchestration/tools/mods.rs +161 -0
- package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +303 -71
- package/crates/mint-core/src/orchestration/tools/shell.rs +95 -2
- package/crates/mint-core/src/orchestration/tools/web.rs +1 -1
- package/crates/mint-core/src/orchestration/workspace_helpers.rs +4 -1
- package/crates/mint-core/src/prompts/agent.rs +75 -31
- 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 +121 -27
- package/crates/mint-core/src/search/knowledge.rs +7 -1
- package/crates/mint-core/src/search/linked_folders/index.rs +896 -0
- package/crates/mint-core/src/search/linked_folders.rs +473 -131
- package/crates/mint-core/src/slash/catalog.rs +1 -1
- package/crates/mint-core/src/slash/mod.rs +644 -33
- 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/slash/mods.rs +155 -0
- package/crates/mint-core/src/system/bg_shell.rs +1129 -95
- package/crates/mint-core/src/system/config.rs +235 -10
- package/crates/mint-core/src/system/html_preview.rs +398 -0
- package/crates/mint-core/src/system/mint_web.rs +164 -0
- package/crates/mint-core/src/system/release_notes.rs +18 -0
- package/crates/mint-core/src/system/shell.rs +21 -6
- package/crates/mint-core/src/system/workspace.rs +292 -0
- package/crates/mint-core/src/system/workspace_history.rs +604 -0
- package/crates/mint-core/src/system.rs +5 -0
- package/crates/mint-core/tests/background_retention.rs +34 -0
- package/crates/mint-core/tests/background_shutdown.rs +24 -0
- package/crates/mint-core/tests/library_http.rs +209 -0
- package/crates/mint-core/tests/mcp_contract.rs +283 -0
- package/crates/mint-core/tests/mcp_stdio.rs +11 -10
- package/crates/mint-core/tests/media_model_catalog_http.rs +403 -0
- package/crates/mint-core/tests/memory_persistence.rs +39 -0
- package/crates/mint-core/tests/mods_http.rs +215 -0
- package/crates/mint-core/tests/terminal_shutdown.rs +109 -0
- package/crates/mint-mod-runtime/Cargo.toml +18 -0
- package/crates/mint-mod-runtime/src/lib.rs +507 -0
- package/crates/mint-mod-runtime/src/sdk.js +56 -0
- package/crates/mint-mod-runtime/src/test_sdk.js +32 -0
- package/crates/mint-mod-runtime/src/testing.rs +94 -0
- package/crates/mint-mod-runtime/tests/runtime.rs +156 -0
- package/docs/mods/AUTHORING.md +61 -0
- package/docs/mods/README.md +139 -0
- package/docs/mods/TESTING.md +56 -0
- package/docs/mods/mint-mods.d.ts +66 -0
- package/examples/device-mcp/README.md +22 -0
- package/examples/device-mcp/contract.md +49 -0
- package/examples/device-mcp/mock-device.mjs +66 -0
- package/examples/device-mcp/server.mjs +48 -0
- package/examples/device-mcp/server.test.mjs +78 -0
- package/examples/mods/task-checklist/README.md +1 -0
- package/examples/mods/task-checklist/main.ts +17 -0
- package/examples/mods/task-checklist/manifest.json +1 -0
- package/examples/mods/task-checklist/tests/checklist.test.ts +15 -0
- package/examples/mods/token-meter/README.md +1 -0
- package/examples/mods/token-meter/main.ts +8 -0
- package/examples/mods/token-meter/manifest.json +1 -0
- package/examples/mods/tool-guard/README.md +1 -0
- package/examples/mods/tool-guard/main.ts +9 -0
- package/examples/mods/tool-guard/manifest.json +1 -0
- package/out/web/assets/CliSessionsView-CtzUJpK_.css +1 -0
- package/out/web/assets/CliSessionsView-D7hw0Mgj.js +1 -0
- package/out/web/assets/CodeReviewPage-C7bEOJfd.js +4 -0
- package/out/web/assets/CodeReviewPage-Dte9uLiL.css +1 -0
- package/out/web/assets/CommandPalette-0RrF-AOT.js +1 -0
- package/out/web/assets/ImageStudioPanel-CdxkAf_r.js +1 -0
- package/out/web/assets/ImageStudioPanel-D0lnhQV5.css +1 -0
- package/out/web/assets/LibraryPage--Qfisp6D.js +2 -0
- package/out/web/assets/LibraryPage-DAxUTAg7.css +1 -0
- package/out/web/assets/LinkedFoldersView-2nfZbDgN.js +1 -0
- package/out/web/assets/McpRegistryPicker-h9uS8_wT.js +1 -0
- package/out/web/assets/McpServersView-slfQJzNB.js +1 -0
- package/out/web/assets/MintDashboard-BSR5XpXh.js +25 -0
- package/out/web/assets/MintDashboard-CWUcCGhd.css +1 -0
- package/out/web/assets/PluginsView-l7LSm503.js +1 -0
- package/out/web/assets/ScheduledTasksView-CttmLbq1.js +1 -0
- package/out/web/assets/SettingsWindow-BCMLYvEV.js +4 -0
- package/out/web/assets/SettingsWindow-DcMVmU9u.css +1 -0
- package/out/web/assets/SkillsView-rNs1qr_U.js +2 -0
- package/out/web/assets/ToolSurfacePage-B9DzEPWN.js +37 -0
- package/out/web/assets/ToolSurfacePage-CTfgggF2.css +1 -0
- package/out/web/assets/VeoStudioPanel-DOj7_WR3.css +1 -0
- package/out/web/assets/VeoStudioPanel-DiJs_Cg2.js +4 -0
- package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +1 -0
- package/out/web/assets/WorkspaceSelector-B2xIp04D.js +1 -0
- package/out/web/assets/WorkspaceSelector-Ca8uJOsS.css +1 -0
- package/out/web/assets/{abnfDiagram-VCTEODGH-DAOBA7ZG.js → abnfDiagram-VCTEODGH-CD0CjoGl.js} +1 -1
- package/out/web/assets/{architecture-7GRP2DOG-CgSwQOPQ.js → architecture-7GRP2DOG-RKV83OeZ.js} +1 -1
- package/out/web/assets/{architectureDiagram-5GKGNRK7-CYp3yupP.js → architectureDiagram-5GKGNRK7-nN_HAFct.js} +1 -1
- package/out/web/assets/{blockDiagram-NRAW4CY4-CyWybDWr.js → blockDiagram-NRAW4CY4-BrrqzHW5.js} +1 -1
- package/out/web/assets/{c4Diagram-UCG6FXSJ-BvY3U709.js → c4Diagram-UCG6FXSJ-DTdV8_zE.js} +1 -1
- package/out/web/assets/{chunk-75Z2AOVW-TLXCoZYv.js → chunk-75Z2AOVW-_M0z5olV.js} +1 -1
- package/out/web/assets/chunk-CKBBP62Z-BpQEgeHF.js +2 -0
- package/out/web/assets/{chunk-CLGD4ZFX-xjsKtNS7.js → chunk-CLGD4ZFX-D54yGvGS.js} +1 -1
- package/out/web/assets/chunk-DSNCTWBM-C-gvT2ru.js +2 -0
- package/out/web/assets/chunk-DU6HZSFF-CnFngIhW.js +128 -0
- package/out/web/assets/{chunk-F27PBJKO-BFpgF55A.js → chunk-F27PBJKO-BqtWrnEO.js} +1 -1
- package/out/web/assets/{chunk-G27WJ6UU-DfF7qW-C.js → chunk-G27WJ6UU-Ckoq5mi2.js} +1 -1
- package/out/web/assets/{chunk-GMAD6QVW-C-558kh_.js → chunk-GMAD6QVW-BuhEcgHq.js} +1 -1
- package/out/web/assets/{chunk-JQ64N6SF-wJmeGKAw.js → chunk-JQ64N6SF-CF8T7GiZ.js} +1 -1
- package/out/web/assets/{chunk-LCL6LL3I-C1wHD8Ie.js → chunk-LCL6LL3I-BhXV9zyH.js} +1 -1
- package/out/web/assets/{chunk-OBVCFTLP-BsRTPBqs.js → chunk-OBVCFTLP-CRIkunUB.js} +1 -1
- package/out/web/assets/{chunk-P2QGCYS3-HZIiM7Ni.js → chunk-P2QGCYS3-UFEU6BxZ.js} +1 -1
- package/out/web/assets/{chunk-POPQ4Y6H-Dk9wvVb0.js → chunk-POPQ4Y6H-CQcrNbjr.js} +1 -1
- package/out/web/assets/{chunk-PWAF6VOD-BmjYLNbq.js → chunk-PWAF6VOD-Cc2uVDsG.js} +1 -1
- package/out/web/assets/{chunk-R7TYR2AO-CaHIf-lK.js → chunk-R7TYR2AO-DiCxzdAY.js} +1 -1
- package/out/web/assets/{chunk-RHFEMEQ7-_T561hOU.js → chunk-RHFEMEQ7-b7hnPmrR.js} +1 -1
- package/out/web/assets/{chunk-SVP7TREG-CM-GGYoY.js → chunk-SVP7TREG-Bu_yYb-V.js} +1 -1
- package/out/web/assets/{chunk-TEH6E4GO-K2A8Vg2C.js → chunk-TEH6E4GO-G4Cdlyrt.js} +1 -1
- package/out/web/assets/classDiagram-DTDB5LWJ-RnC2mYO4.js +1 -0
- package/out/web/assets/classDiagram-v2-JRS7N3AN-RnC2mYO4.js +1 -0
- package/out/web/assets/config-CnvCChUM.js +2 -0
- package/out/web/assets/config-DM6oIVO3.css +1 -0
- package/out/web/assets/core-DsKkQ7ig.js +1 -0
- package/out/web/assets/{cynefin-OW5HDTMX-ClzaB6cc.js → cynefin-OW5HDTMX-CnzQfOGp.js} +1 -1
- package/out/web/assets/{cynefinDiagram-5FMLGOSQ-D_JzZBzf.js → cynefinDiagram-5FMLGOSQ-DVtQnfpm.js} +1 -1
- package/out/web/assets/{dagre-3AP2YEHR-BDuUeMHj.js → dagre-3AP2YEHR-DG76_k5_.js} +1 -1
- package/out/web/assets/{diagram-S7CK7UJ4-DgtcfE8P.js → diagram-S7CK7UJ4-CaouUoze.js} +1 -1
- package/out/web/assets/{diagram-UQ7AKVKN-DTLoEYti.js → diagram-UQ7AKVKN-B4bEc26B.js} +1 -1
- package/out/web/assets/{diagram-VSXAHHWV-ChGl9pXz.js → diagram-VSXAHHWV-BJ86Ov2Y.js} +1 -1
- package/out/web/assets/{diagram-VX7I27RA-Be8DWz0S.js → diagram-VX7I27RA-7hH5IPYf.js} +1 -1
- package/out/web/assets/{diagram-Z3DM3KII-BMXjRTOF.js → diagram-Z3DM3KII-CCM4clo8.js} +1 -1
- package/out/web/assets/dist-js-BkYNznHX.js +1 -0
- package/out/web/assets/{ebnfDiagram-PWID7BFC-8Q9pl1CJ.js → ebnfDiagram-PWID7BFC-Da7WAM3k.js} +1 -1
- package/out/web/assets/{erDiagram-SSCWMZ5O-DjH8Lnkn.js → erDiagram-SSCWMZ5O-CzzmnZWT.js} +1 -1
- package/out/web/assets/event-Cdt7Byyj.js +1 -0
- package/out/web/assets/{eventmodeling-NTZA5JFV-BKUH_DOp.js → eventmodeling-NTZA5JFV-xcfsA0XD.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-JdtH1673.js +1 -0
- package/out/web/assets/{ganttDiagram-EL5Y4UJY-D7fZKJNM.js → ganttDiagram-EL5Y4UJY-B3ClFGmL.js} +1 -1
- package/out/web/assets/{gitGraph-4MIJSDKK-BypWaaBU.js → gitGraph-4MIJSDKK-BZ_y_cDU.js} +1 -1
- package/out/web/assets/{gitGraphDiagram-WWUBYQGX-DWiw_3bq.js → gitGraphDiagram-WWUBYQGX-drhRSYm3.js} +1 -1
- package/out/web/assets/index-BZ9xgmMP.css +1 -0
- package/out/web/assets/index-Ca8cCS4h.js +168 -0
- package/out/web/assets/{info-A6RAGUB7-KkAUxT4j.js → info-A6RAGUB7-B7I3K0F1.js} +1 -1
- package/out/web/assets/{infoDiagram-RXCK75RN-BNb6E48y.js → infoDiagram-RXCK75RN-deX_1MPh.js} +1 -1
- package/out/web/assets/{ishikawaDiagram-5VMMS53U-Caa-JoAK.js → ishikawaDiagram-5VMMS53U-s38yo28y.js} +1 -1
- package/out/web/assets/{journeyDiagram-EYS64GPL-DOrmFGz6.js → journeyDiagram-EYS64GPL-CZn2kB8b.js} +1 -1
- package/out/web/assets/jsx-runtime-DN4NhQUK.js +1 -0
- package/out/web/assets/{kanban-definition-3QL26DDD-BOWCYcK0.js → kanban-definition-3QL26DDD-DodjgySL.js} +1 -1
- package/out/web/assets/markdown-CMhYIcWB.js +39 -0
- package/out/web/assets/{mermaid-parser.core-BQqjCkS_.js → mermaid-parser.core-Whu2rlJC.js} +3 -3
- package/out/web/assets/mermaid.core-CKyPo8VH.js +11 -0
- package/out/web/assets/{mindmap-definition-FBJOCRG2-jTceWpr0.js → mindmap-definition-FBJOCRG2-260WBDxZ.js} +1 -1
- package/out/web/assets/modelManager-CUaGWMWu.js +2 -0
- package/out/web/assets/oauthManager-DlNhXJr6.js +1 -0
- package/out/web/assets/{packet-AYTQ26CC-CJ5RXgol.js → packet-AYTQ26CC-CEeIRKRG.js} +1 -1
- package/out/web/assets/{pegDiagram-XKGWAZYB-y8wMtd0y.js → pegDiagram-XKGWAZYB-BYadI-KO.js} +1 -1
- package/out/web/assets/{pie-WAS4IAKB-72fTBbl4.js → pie-WAS4IAKB-D8uf4dh5.js} +1 -1
- package/out/web/assets/{pieDiagram-E7YTZNPT-e0O02qWq.js → pieDiagram-E7YTZNPT-CjSDy4hO.js} +1 -1
- package/out/web/assets/{quadrantDiagram-AXDQQJYC-C9phYLom.js → quadrantDiagram-AXDQQJYC-B8D3gU5d.js} +1 -1
- package/out/web/assets/{radar-RG4KPBEZ-BAU2hzUT.js → radar-RG4KPBEZ-DZJxnokY.js} +1 -1
- package/out/web/assets/{railroad-74A4TZTK-B0bCAVHC.js → railroad-74A4TZTK-DBysUiUi.js} +1 -1
- package/out/web/assets/{railroad-abnf-HS5TGJTU-WhxO7ndE.js → railroad-abnf-HS5TGJTU-CHWxoJYg.js} +1 -1
- package/out/web/assets/{railroad-ebnf-LZEXJU2U-BkA0m7-F.js → railroad-ebnf-LZEXJU2U-DDv7X6bY.js} +1 -1
- package/out/web/assets/{railroad-peg-WCYAUIDC-BsVpHIaj.js → railroad-peg-WCYAUIDC-DpfcYYNJ.js} +1 -1
- package/out/web/assets/{railroadDiagram-O6MQD6OU-CyOLN65Q.js → railroadDiagram-O6MQD6OU-CYtyv0t3.js} +1 -1
- package/out/web/assets/{requirementDiagram-EFPCY7ZU-DQXJ7Dud.js → requirementDiagram-EFPCY7ZU-D2X-Ewik.js} +1 -1
- package/out/web/assets/{sankeyDiagram-P5KCCOFB-B2XLyIIc.js → sankeyDiagram-P5KCCOFB-Bn2x6C4Y.js} +1 -1
- package/out/web/assets/{sequenceDiagram-WJ2MYXX4-DGQY4wwm.js → sequenceDiagram-WJ2MYXX4-BvFLKtPd.js} +1 -1
- package/out/web/assets/{stateDiagram-HBIQ2CUA-DHZpDwNy.js → stateDiagram-HBIQ2CUA-Bx0GWeaY.js} +1 -1
- package/out/web/assets/stateDiagram-v2-4QOOHH4V--hol1yg_.js +1 -0
- package/out/web/assets/studio-B1mk40wD.js +1 -0
- package/out/web/assets/{swimlanes-XN3QIQJK-Bn_jcZz-.js → swimlanes-XN3QIQJK-CCma0T7L.js} +1 -1
- package/out/web/assets/swimlanesDiagram-VK2B7HYN-BkohoVsU.js +8 -0
- package/out/web/assets/{timeline-definition-24CTP7MA-CdTERuJZ.js → timeline-definition-24CTP7MA-iCRhhJ7C.js} +1 -1
- package/out/web/assets/{treeView-Q6P3EWNA-TKBKQG6R.js → treeView-Q6P3EWNA-DZ-y08qp.js} +1 -1
- package/out/web/assets/{treemap-WGGIJYW6-BP7gQoiC.js → treemap-WGGIJYW6-De0CxzMx.js} +1 -1
- package/out/web/assets/{vennDiagram-4TSXK5OY-BHUmxDuK.js → vennDiagram-4TSXK5OY-w_7aZ6xe.js} +1 -1
- package/out/web/assets/{wardley-WFR3VGLG-9bct5AAN.js → wardley-WFR3VGLG-DT0y0xJ4.js} +1 -1
- package/out/web/assets/{wardleyDiagram-VM6X3IG4-BfJwDDso.js → wardleyDiagram-VM6X3IG4-DAdXELbQ.js} +1 -1
- package/out/web/assets/window-BJCVtyET.js +1 -0
- package/out/web/assets/{xychartDiagram-S5SC5T6Z-Dqd9Mvpo.js → xychartDiagram-S5SC5T6Z-IZIXf3Vo.js} +1 -1
- package/out/web/index-web.html +4 -4
- package/out/web/index.html +4 -4
- package/package.json +80 -20
- package/shared-modules/.github/workflows/ci.yml +120 -0
- package/shared-modules/.github/workflows/update.yaml +28 -0
- package/slash-commands.json +16 -2
- package/src/bin/auto-build.js +66 -0
- package/src/bin/desktop.js +113 -0
- package/src/bin/desktop.test.js +113 -0
- package/src/bin/index.js +18 -25
- package/src/bin/index.test.js +89 -0
- package/src/bin/measure-effects.mjs +53 -0
- package/src/bin/package.test.js +28 -0
- package/src/bin/release-notes.test.js +84 -0
- package/src/bin/styles.test.js +20 -0
- package/src/bin/web.js +5 -0
- package/src/bin/web.test.js +39 -0
- package/src/renderer/index-web.html +1 -0
- package/src/renderer/index.html +1 -2
- package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/renderer/shared/agentProgress.ts +201 -3
- package/src/renderer/shared/components/AgentActivityDrawer.tsx +9 -8
- package/src/renderer/shared/components/AgentActivityTable.tsx +193 -133
- package/src/renderer/shared/components/ApprovalCard.test.tsx +24 -0
- package/src/renderer/shared/components/ApprovalCard.tsx +10 -3
- package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +91 -40
- package/src/renderer/shared/components/AuthGate.tsx +79 -48
- package/src/renderer/shared/components/BackgroundTerminals.tsx +181 -0
- package/src/renderer/shared/components/CalculationCard.tsx +41 -15
- package/src/renderer/shared/components/ChatCodeBlock.tsx +8 -4
- package/src/renderer/shared/components/ChatMessageItem.test.tsx +68 -0
- package/src/renderer/shared/components/ChatMessageItem.tsx +52 -33
- package/src/renderer/shared/components/ChatPanel.test.tsx +21 -0
- package/src/renderer/shared/components/ChatPanel.tsx +676 -415
- package/src/renderer/shared/components/ChunkErrorBoundary.tsx +115 -64
- package/src/renderer/shared/components/CliSessionsView.test.tsx +125 -0
- package/src/renderer/shared/components/CliSessionsView.tsx +543 -0
- package/src/renderer/shared/components/CodeReviewPage.tsx +668 -0
- package/src/renderer/shared/components/CollapsiblePasteInput.tsx +90 -0
- package/src/renderer/shared/components/CommandPalette.tsx +8 -8
- package/src/renderer/shared/components/ContextCompactionStatus.tsx +17 -0
- package/src/renderer/shared/components/DashboardSidebar.tsx +853 -229
- package/src/renderer/shared/components/DesktopTitlebar.tsx +371 -0
- package/src/renderer/shared/components/DiffReviewModal.tsx +3 -3
- package/src/renderer/shared/components/ErrorNotice.tsx +29 -0
- package/src/renderer/shared/components/FormattedMessage.tsx +11 -0
- package/src/renderer/shared/components/GeminiLiveOverlay.css +86 -0
- package/src/renderer/shared/components/GeminiLiveOverlay.test.tsx +24 -0
- package/src/renderer/shared/components/GeminiLiveOverlay.tsx +85 -101
- package/src/renderer/shared/components/GitBranchSelector.tsx +442 -0
- package/src/renderer/shared/components/GlideSelect.css +138 -0
- package/src/renderer/shared/components/GlideSelect.d.ts +31 -0
- package/src/renderer/shared/components/GlideSelect.jsx +266 -0
- package/src/renderer/shared/components/ImageGenCard.tsx +31 -12
- package/src/renderer/shared/components/ImageSearchCard.tsx +76 -65
- package/src/renderer/shared/components/ImageStudioPanel.tsx +49 -49
- package/src/renderer/shared/components/InlineThoughtNote.tsx +27 -0
- package/src/renderer/shared/components/LibraryItemCard.tsx +72 -0
- package/src/renderer/shared/components/LibraryPage.tsx +165 -0
- package/src/renderer/shared/components/LibraryPreview.tsx +59 -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 +51 -30
- package/src/renderer/shared/components/McpToolAllowlist.tsx +2 -4
- package/src/renderer/shared/components/McpToolResults.tsx +99 -0
- package/src/renderer/shared/components/MermaidCard.tsx +14 -8
- package/src/renderer/shared/components/MintDashboard.tsx +1002 -831
- package/src/renderer/shared/components/ModSessionControls.tsx +8 -0
- package/src/renderer/shared/components/ModUi.test.tsx +65 -0
- package/src/renderer/shared/components/ModUi.tsx +26 -0
- package/src/renderer/shared/components/ModelSelectorPopover.tsx +547 -219
- package/src/renderer/shared/components/ModsPanel.tsx +156 -0
- package/src/renderer/shared/components/PlanChecklistWidget.tsx +1 -1
- package/src/renderer/shared/components/PluginsView.tsx +1 -1
- package/src/renderer/shared/components/ProgressActivity.tsx +17 -0
- package/src/renderer/shared/components/ReviewHistory.tsx +42 -0
- package/src/renderer/shared/components/RewindModal.tsx +11 -18
- package/src/renderer/shared/components/RunSummaryDashboard.tsx +8 -14
- package/src/renderer/shared/components/ScheduledTasksView.tsx +11 -10
- package/src/renderer/shared/components/ScreenCaptureDialog.css +221 -0
- package/src/renderer/shared/components/ScreenCaptureDialog.tsx +199 -0
- package/src/renderer/shared/components/SelectField.test.tsx +27 -0
- package/src/renderer/shared/components/SelectField.tsx +38 -0
- package/src/renderer/shared/components/SessionSources.tsx +48 -0
- package/src/renderer/shared/components/SessionSourcesPopover.tsx +61 -0
- package/src/renderer/shared/components/Settings/AgentsTab.tsx +41 -45
- package/src/renderer/shared/components/Settings/AudioTab.tsx +16 -15
- package/src/renderer/shared/components/Settings/AutomationTab.tsx +10 -60
- package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +368 -0
- package/src/renderer/shared/components/Settings/GeneralTab.tsx +368 -113
- package/src/renderer/shared/components/Settings/MemoryTab.test.tsx +37 -0
- package/src/renderer/shared/components/Settings/MemoryTab.tsx +71 -31
- package/src/renderer/shared/components/Settings/NotificationsTab.test.tsx +15 -0
- package/src/renderer/shared/components/Settings/NotificationsTab.tsx +84 -0
- 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 +416 -165
- package/src/renderer/shared/components/SkillsView.tsx +41 -16
- package/src/renderer/shared/components/SlashChoicePanel.tsx +88 -0
- package/src/renderer/shared/components/SlashSuggestions.test.tsx +33 -0
- package/src/renderer/shared/components/SlashSuggestions.tsx +41 -12
- package/src/renderer/shared/components/SourcesBlock.tsx +55 -51
- 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.test.tsx +32 -0
- package/src/renderer/shared/components/ToolSurfacePage.tsx +568 -0
- package/src/renderer/shared/components/ToolsPanelToggle.tsx +13 -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 +22 -37
- package/src/renderer/shared/components/ViewedImages.tsx +36 -0
- package/src/renderer/shared/components/VirtualChatHistory.tsx +82 -0
- package/src/renderer/shared/components/VoicePill.css +85 -0
- package/src/renderer/shared/components/VoicePill.d.ts +12 -0
- package/src/renderer/shared/components/VoicePill.jsx +190 -0
- package/src/renderer/shared/components/VoicePill.test.tsx +22 -0
- package/src/renderer/shared/components/WeatherCard.tsx +3 -3
- package/src/renderer/shared/components/WorkspaceContextPopover.tsx +138 -0
- package/src/renderer/shared/components/WorkspacePaneResizer.tsx +92 -0
- package/src/renderer/shared/components/WorkspacePreviewActions.test.tsx +44 -0
- package/src/renderer/shared/components/WorkspacePreviewActions.tsx +19 -0
- package/src/renderer/shared/components/WorkspaceSelector.tsx +154 -0
- package/src/renderer/shared/components/background-terminals.css +292 -0
- package/src/renderer/shared/components/ui/README.md +52 -0
- package/src/renderer/shared/components/ui/button.tsx +37 -0
- package/src/renderer/shared/components/ui/demo.tsx +26 -0
- package/src/renderer/shared/components/ui/error-recovery.css +35 -0
- package/src/renderer/shared/components/ui/error-recovery.tsx +35 -0
- package/src/renderer/shared/components/ui/voice-powered-orb.css +6 -0
- package/src/renderer/shared/components/ui/voice-powered-orb.tsx +374 -0
- package/src/renderer/shared/components/workspace-selector.css +248 -0
- package/src/renderer/shared/constants/config.ts +57 -3
- package/src/renderer/shared/constants/models.ts +189 -2
- package/src/renderer/shared/constants/slashCommands.ts +1 -1
- package/src/renderer/shared/conversation/coordinator.test.ts +27 -0
- package/src/renderer/shared/conversation/coordinator.ts +82 -0
- package/src/renderer/shared/conversation/lifecycle.test.ts +362 -0
- package/src/renderer/shared/conversation/lifecycle.ts +418 -0
- package/src/renderer/shared/conversation/projectNavigation.test.ts +105 -0
- package/src/renderer/shared/conversation/projectNavigation.ts +39 -0
- package/src/renderer/shared/conversation/sourcesTransport.ts +27 -0
- package/src/renderer/shared/conversation/syncView.test.ts +13 -0
- package/src/renderer/shared/conversation/syncView.ts +6 -0
- package/src/renderer/shared/conversation/transport.test.ts +163 -0
- package/src/renderer/shared/conversation/transport.ts +159 -0
- package/src/renderer/shared/conversation/useConversationCoordinator.ts +64 -0
- package/src/renderer/shared/css/agent-observability.css +46 -15
- package/src/renderer/shared/css/auth-gate.css +114 -15
- package/src/renderer/shared/css/chat-ui-cards.css +495 -0
- package/src/renderer/shared/css/code-review.css +856 -0
- package/src/renderer/shared/css/code-sessions.css +479 -0
- package/src/renderer/shared/css/collapsed-paste.css +35 -0
- package/src/renderer/shared/css/effects.css +15 -0
- package/src/renderer/shared/css/error-notice.css +60 -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/library.css +476 -0
- package/src/renderer/shared/css/management-views.css +415 -27
- package/src/renderer/shared/css/mcp-results.css +11 -0
- package/src/renderer/shared/css/model-selector.css +543 -85
- package/src/renderer/shared/css/mods.css +77 -0
- package/src/renderer/shared/css/notification-notice.css +28 -0
- package/src/renderer/shared/css/session-sources-popover.css +9 -0
- package/src/renderer/shared/css/session-sources.css +23 -0
- 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/memory.css +154 -0
- package/src/renderer/shared/css/settings/notifications.css +11 -0
- 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 +656 -3
- package/src/renderer/shared/css/slash-choice.css +95 -0
- package/src/renderer/shared/css/slash-suggestions.css +68 -0
- package/src/renderer/shared/css/sources-block.css +91 -0
- package/src/renderer/shared/css/thinking-status.css +46 -0
- package/src/renderer/shared/css/tools-panel.css +7 -0
- package/src/renderer/shared/css/ui-tailwind.css +19 -0
- package/src/renderer/shared/css/veo-studio.css +16 -15
- package/src/renderer/shared/css/viewed-images.css +15 -0
- package/src/renderer/shared/fonts.css +46 -0
- package/src/renderer/shared/fonts.ts +10 -37
- package/src/renderer/shared/lib/utils.ts +6 -0
- package/src/renderer/shared/library/controller.test.ts +61 -0
- package/src/renderer/shared/library/controller.ts +57 -0
- package/src/renderer/shared/library/transport.test.ts +26 -0
- package/src/renderer/shared/library/transport.ts +60 -0
- package/src/renderer/shared/library/useLibraryFile.ts +38 -0
- package/src/renderer/shared/mods.ts +58 -0
- package/src/renderer/shared/platform.test.ts +90 -0
- package/src/renderer/shared/platform.ts +219 -7
- package/src/renderer/shared/theme/themeManager.test.ts +43 -0
- package/src/renderer/shared/theme/themeManager.ts +157 -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 +137 -2
- package/src/renderer/shared/utils/agentActivity.test.ts +75 -0
- package/src/renderer/shared/utils/agentActivity.ts +168 -8
- package/src/renderer/shared/utils/approval.tsx +7 -0
- package/src/renderer/shared/utils/artifactBrowser.test.ts +80 -0
- package/src/renderer/shared/utils/artifactBrowser.ts +41 -0
- package/src/renderer/shared/utils/cardJson.test.ts +35 -0
- package/src/renderer/shared/utils/cardJson.ts +30 -0
- package/src/renderer/shared/utils/contextCompaction.test.tsx +48 -0
- package/src/renderer/shared/utils/contextCompaction.ts +21 -0
- package/src/renderer/shared/utils/effectVisibility.ts +9 -0
- package/src/renderer/shared/utils/fileIcons.ts +1 -1
- package/src/renderer/shared/utils/markdown.tsx +330 -81
- package/src/renderer/shared/utils/markdown.webImages.test.tsx +40 -0
- package/src/renderer/shared/utils/mcpResults.test.tsx +91 -0
- package/src/renderer/shared/utils/modViewport.ts +7 -0
- package/src/renderer/shared/utils/nativeRecordingSession.test.ts +61 -0
- package/src/renderer/shared/utils/nativeRecordingSession.ts +46 -0
- package/src/renderer/shared/utils/notificationSettings.test.ts +14 -0
- package/src/renderer/shared/utils/notificationSettings.ts +23 -0
- package/src/renderer/shared/utils/notifications.test.ts +51 -0
- package/src/renderer/shared/utils/notifications.ts +54 -0
- package/src/renderer/shared/utils/pastedText.test.ts +20 -0
- package/src/renderer/shared/utils/pastedText.ts +13 -0
- package/src/renderer/shared/utils/progressCache.test.ts +32 -0
- package/src/renderer/shared/utils/providers.ts +8 -3
- package/src/renderer/shared/utils/screenCapture.ts +43 -0
- package/src/renderer/shared/utils/sessionSources.test.ts +31 -0
- package/src/renderer/shared/utils/sessionSources.ts +34 -0
- package/src/renderer/shared/utils/slashCommandProcessor.ts +5 -2
- package/src/renderer/shared/utils/sourceAttachments.ts +28 -0
- package/src/renderer/shared/utils/speech.ts +51 -9
- package/src/renderer/shared/utils/syntaxHighlight.test.tsx +22 -0
- package/src/renderer/shared/utils/syntaxHighlight.tsx +26 -0
- package/src/renderer/shared/utils/ui.ts +4 -64
- package/src/renderer/shared/utils/useGeminiLiveVoice.ts +49 -5
- package/src/renderer/shared/utils/useLatestCallback.ts +9 -0
- package/src/renderer/shared/utils/useNativeVoiceInput.ts +80 -72
- package/src/renderer/shared/utils/useSessionSources.ts +33 -0
- package/src/renderer/shared/utils/viewedImages.ts +26 -0
- package/src/renderer/shared/utils/workspacePane.test.ts +33 -0
- package/src/renderer/shared/utils/workspacePane.ts +20 -0
- package/src/renderer/shared/utils/workspacePreview.test.ts +24 -0
- package/src/renderer/shared/utils/workspacePreview.ts +16 -0
- package/src/renderer/shared/utils/workspaceSnapshot.test.ts +49 -0
- package/src/renderer/shared/utils/workspaceSnapshot.ts +26 -0
- package/src/renderer/shared/utils/workspaces.ts +10 -0
- package/src/renderer/shared/workspaceUndo.ts +88 -0
- package/src/renderer/src/App.tsx +16 -1
- package/src/renderer/src/components/BrowserShell.tsx +108 -0
- package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +91 -0
- package/src/renderer/src/components/LiveTranslateControls.tsx +65 -0
- package/src/renderer/src/components/MintDashboard.tsx +4 -2
- package/src/renderer/src/components/PicturesLibrary.tsx +2 -287
- package/src/renderer/src/components/ScreenPicker.css +227 -0
- package/src/renderer/src/components/ScreenPicker.tsx +312 -526
- package/src/renderer/src/components/SettingsWindow.tsx +48 -5
- 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 +269 -61
- 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 +44 -0
- package/src/renderer/src/components/liveTranslateFrames.ts +45 -0
- package/src/renderer/src/components/liveTranslateLanguage.ts +6 -0
- package/src/renderer/src/css/app-shell.css +400 -204
- package/src/renderer/src/css/chat.css +1666 -594
- package/src/renderer/src/css/dashboard.css +11 -0
- package/src/renderer/src/css/desktop-tools.css +928 -0
- package/src/renderer/src/css/misc.css +5 -60
- package/src/renderer/src/css/pictures.css +2 -407
- package/src/renderer/src/css/settings/base.css +394 -157
- package/src/renderer/src/css/sidebar-workspace.css +1072 -376
- package/src/renderer/src/css/spotlight.css +15 -26
- package/src/renderer/src/css/tool-surfaces.css +784 -0
- package/src/renderer/src/css/widget.css +36 -8
- package/src/renderer/src/env.d.ts +4 -9
- package/src/renderer/src/hooks/useProviderModels.ts +2 -9
- package/src/renderer/src/index.css +53 -46
- package/src/renderer/src/main.tsx +12 -0
- package/src/renderer/src/proactiveSuggestions.ts +29 -0
- package/src/renderer/src/tauri.ts +302 -339
- package/src/renderer/src/voiceInput.ts +2 -1
- 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 +2 -284
- package/src/renderer/src-web/components/PullToRefresh.tsx +107 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +36 -5
- package/src/renderer/src-web/css/app-shell.css +188 -207
- package/src/renderer/src-web/css/chat.css +1236 -507
- package/src/renderer/src-web/css/dashboard.css +11 -0
- package/src/renderer/src-web/css/misc.css +3 -55
- package/src/renderer/src-web/css/mobile.css +679 -0
- package/src/renderer/src-web/css/pictures.css +2 -415
- package/src/renderer/src-web/css/pull-to-refresh.css +54 -0
- package/src/renderer/src-web/css/settings/base.css +353 -134
- package/src/renderer/src-web/css/settings/mobile.css +320 -0
- package/src/renderer/src-web/css/sidebar-workspace.css +546 -187
- package/src/renderer/src-web/css/spotlight.css +9 -26
- package/src/renderer/src-web/css/widget.css +1 -6
- package/src/renderer/src-web/env.d.ts +4 -12
- 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 +12 -0
- package/src/renderer/src-web/proactiveSuggestions.ts +3 -0
- package/src/renderer/src-web/tauri.ts +309 -364
- package/src/renderer/tests/backgroundTerminals.browser.html +81 -0
- package/src/renderer/tests/chatPanelFixture.ts +23 -0
- package/src/renderer/tests/chatPerformance.browser.html +71 -0
- package/src/renderer/tests/cliSessions.browser.html +82 -0
- package/src/renderer/tests/collapsedPaste.browser.html +70 -0
- package/src/renderer/tests/composerMenus.browser.html +116 -0
- package/src/renderer/tests/contextCompaction.browser.html +17 -0
- package/src/renderer/tests/conversationLifecycle.browser.html +70 -0
- package/src/renderer/tests/effectsPerformance.browser.html +13 -0
- package/src/renderer/tests/errorRecovery.browser.html +21 -0
- package/src/renderer/tests/glideSelect.browser.html +71 -0
- package/src/renderer/tests/glideSelect.preview.html +27 -0
- package/src/renderer/tests/library.browser.html +1 -0
- package/src/renderer/tests/library.browser.tsx +78 -0
- package/src/renderer/tests/liveOrb.browser.html +49 -0
- package/src/renderer/tests/liveVoice.browser.html +29 -0
- package/src/renderer/tests/modInput.browser.html +22 -0
- package/src/renderer/tests/sessionSources.browser.html +1 -0
- package/src/renderer/tests/sessionSources.browser.tsx +107 -0
- package/src/renderer/tests/skills.browser.html +24 -0
- package/src/renderer/tests/toolsPanel.browser.html +1 -0
- package/src/renderer/tests/toolsPanel.browser.tsx +113 -0
- package/src/renderer/tests/viewedImages.browser.html +1 -0
- package/src/renderer/tests/viewedImages.browser.tsx +93 -0
- package/src/renderer/tests/voicePill.browser.html +80 -0
- package/src/renderer/tests/voicePill.preview.html +34 -0
- package/src/renderer/tests/webSources.browser.html +1 -0
- package/src/renderer/tests/webSources.browser.tsx +98 -0
- package/src/renderer/tests/workspaceUndo.browser.html +92 -0
- package/src-tauri/Cargo.toml +40 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/src/app/lifecycle.rs +277 -0
- package/src-tauri/src/app/mod.rs +4 -0
- package/src-tauri/src/app/shortcuts.rs +40 -0
- package/src-tauri/src/app/tray.rs +52 -0
- package/src-tauri/src/background/events.rs +70 -0
- package/src-tauri/src/background/headless.rs +230 -0
- package/src-tauri/src/background/mod.rs +4 -0
- package/src-tauri/src/background/proactive.rs +262 -0
- package/src-tauri/src/commands/approvals.rs +20 -0
- package/src-tauri/src/commands/auth.rs +127 -0
- package/src-tauri/src/commands/conversation.rs +642 -0
- package/src-tauri/src/commands/integrations.rs +157 -0
- package/src-tauri/src/commands/media.rs +179 -0
- package/src-tauri/src/commands/mod.rs +11 -0
- package/src-tauri/src/commands/mods.rs +6 -0
- package/src-tauri/src/commands/settings.rs +141 -0
- package/src-tauri/src/commands/voice.rs +153 -0
- package/src-tauri/src/commands/workspace.rs +501 -0
- package/src-tauri/src/lib.rs +10 -0
- package/src-tauri/src/main.rs +3 -0
- package/src-tauri/src/native/actions.rs +206 -0
- package/src-tauri/src/native/browser.rs +252 -0
- package/src-tauri/src/native/capture.rs +695 -0
- package/src-tauri/src/native/discord_rpc.rs +99 -0
- package/src-tauri/src/native/integrations.rs +126 -0
- package/src-tauri/src/native/mod.rs +12 -0
- package/src-tauri/src/native/notifications.rs +51 -0
- package/src-tauri/src/native/plugins.rs +14 -0
- package/src-tauri/src/native/system.rs +250 -0
- package/src-tauri/src/native/terminal.rs +201 -0
- package/src-tauri/src/native/updater.rs +148 -0
- package/src-tauri/src/native/windows.rs +143 -0
- package/tsconfig.json +2 -0
- package/vite.config.ts +4 -1
- package/vite.config.web.ts +5 -0
- package/.cargo/config.toml +0 -13
- package/.claude/settings.local.json +0 -182
- package/.codex +0 -0
- package/CLAUDE.md +0 -102
- package/out/web/Live2DCubismCore.js +0 -9
- 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-400-normal-BQ9zjSN8.woff2 +0 -0
- package/out/web/assets/prompt-latin-400-normal-CxU4ec_r.woff +0 -0
- package/out/web/assets/prompt-latin-500-normal-B1edBDQ9.woff +0 -0
- package/out/web/assets/prompt-latin-500-normal-CxzxEHZc.woff2 +0 -0
- package/out/web/assets/prompt-latin-600-normal-CSDDLorv.woff +0 -0
- package/out/web/assets/prompt-latin-600-normal-hKZWXsc1.woff2 +0 -0
- 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-thai-400-normal-BrkKv8cO.woff2 +0 -0
- package/out/web/assets/prompt-thai-400-normal-DJypnfr3.woff +0 -0
- package/out/web/assets/prompt-thai-500-normal-C18pDUoL.woff2 +0 -0
- package/out/web/assets/prompt-thai-500-normal-D7YNqUJD.woff +0 -0
- package/out/web/assets/prompt-thai-600-normal-CYPViXds.woff +0 -0
- package/out/web/assets/prompt-thai-600-normal-MrdfU7zR.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
- package/skills-lock.json +0 -41
- package/src/renderer/public/Live2DCubismCore.js +0 -9
- package/src/renderer/src/companionWidget.ts +0 -95
- package/src/renderer/src/components/Live2DStage.tsx +0 -389
- package/src/renderer/src/components/ModelPanel.tsx +0 -154
- package/src/renderer/src/css/model.css +0 -627
- package/src/renderer/src-web/companionWidget.ts +0 -37
- package/src/renderer/src-web/components/ModelPanel.tsx +0 -12
- package/src/renderer/src-web/css/model.css +0 -628
|
@@ -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();
|
|
@@ -202,6 +184,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
202
184
|
input: trimmed.to_string(),
|
|
203
185
|
cwd: Some(session.current_dir.to_string_lossy().to_string()),
|
|
204
186
|
surface: Some("cli".to_string()),
|
|
187
|
+
chat_id: Some(session.chat_id.clone()),
|
|
205
188
|
};
|
|
206
189
|
|
|
207
190
|
if matches!(
|
|
@@ -213,28 +196,17 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
213
196
|
| "/videomodels"
|
|
214
197
|
| "/video-provider"
|
|
215
198
|
) {
|
|
216
|
-
|
|
217
|
-
let _ = std::io::Write::flush(&mut std::io::stdout());
|
|
199
|
+
ui.set_status(vec!["Fetching available models…".into()]);
|
|
218
200
|
}
|
|
219
201
|
|
|
220
202
|
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
|
-
}
|
|
203
|
+
ui.clear_status();
|
|
234
204
|
|
|
235
205
|
match response {
|
|
236
206
|
SlashResponse::Message { markdown } => {
|
|
237
|
-
|
|
207
|
+
if !markdown.is_empty() {
|
|
208
|
+
ui.push_command_output(markdown);
|
|
209
|
+
}
|
|
238
210
|
Some(SlashResult::Handled)
|
|
239
211
|
}
|
|
240
212
|
SlashResponse::Applied { markdown, effects } => {
|
|
@@ -246,7 +218,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
246
218
|
}
|
|
247
219
|
SlashEffect::ProviderChanged { display } => {
|
|
248
220
|
save_needed = true;
|
|
249
|
-
|
|
221
|
+
ui.push_notice(format!("Switched to: {display}"));
|
|
250
222
|
}
|
|
251
223
|
SlashEffect::FastModeChanged { enabled } => {
|
|
252
224
|
session.fast_mode = *enabled;
|
|
@@ -264,6 +236,11 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
264
236
|
}
|
|
265
237
|
SlashEffect::HistoryCleared => {
|
|
266
238
|
session.history.clear();
|
|
239
|
+
ui.clear_transcript();
|
|
240
|
+
}
|
|
241
|
+
SlashEffect::WorkspaceFilesChanged => {}
|
|
242
|
+
SlashEffect::ThinkingChanged { .. } => {
|
|
243
|
+
save_needed = true;
|
|
267
244
|
}
|
|
268
245
|
}
|
|
269
246
|
}
|
|
@@ -271,7 +248,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
271
248
|
let _ = mint_core::save_config(&session.config);
|
|
272
249
|
}
|
|
273
250
|
if !markdown.is_empty() {
|
|
274
|
-
|
|
251
|
+
ui.push_command_output(markdown);
|
|
275
252
|
}
|
|
276
253
|
Some(SlashResult::Handled)
|
|
277
254
|
}
|
|
@@ -280,20 +257,50 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
280
257
|
title,
|
|
281
258
|
options,
|
|
282
259
|
} => {
|
|
283
|
-
let
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
260
|
+
let choice_items: Vec<ChoiceItem> = options
|
|
261
|
+
.iter()
|
|
262
|
+
.map(|opt| ChoiceItem::new(opt.label.clone(), opt.value.clone()))
|
|
263
|
+
.collect();
|
|
264
|
+
let (heading, body) = title.split_once('\n').unwrap_or((&title, ""));
|
|
265
|
+
match ui.prompt_choice(heading, body, &choice_items) {
|
|
266
|
+
Ok(Some(idx)) => {
|
|
267
|
+
let opt = &options[idx];
|
|
268
|
+
if opt.value == "__action:search" {
|
|
269
|
+
let text = ui
|
|
270
|
+
.prompt_text("Search models", "Search query", None)
|
|
271
|
+
.ok()
|
|
272
|
+
.flatten()
|
|
273
|
+
.unwrap_or_default();
|
|
274
|
+
let trimmed = text.trim();
|
|
275
|
+
if !trimmed.is_empty() {
|
|
276
|
+
let next_input = format!("{command} __action:search:{trimmed}");
|
|
277
|
+
return Box::pin(handle_slash_command(session, ui, &next_input)).await;
|
|
278
|
+
}
|
|
279
|
+
ui.push_notice("Cancelled.");
|
|
280
|
+
return Some(SlashResult::Handled);
|
|
281
|
+
}
|
|
282
|
+
if opt.value == "__action:custom" {
|
|
283
|
+
let custom = ui
|
|
284
|
+
.prompt_text("Custom model", "Enter custom model ID", None)
|
|
285
|
+
.ok()
|
|
286
|
+
.flatten()
|
|
287
|
+
.unwrap_or_default();
|
|
288
|
+
let trimmed = custom.trim();
|
|
289
|
+
if !trimmed.is_empty() {
|
|
290
|
+
let next_input = format!("{command} {trimmed}");
|
|
291
|
+
return Box::pin(handle_slash_command(session, ui, &next_input)).await;
|
|
292
|
+
}
|
|
293
|
+
ui.push_notice("Cancelled.");
|
|
294
|
+
return Some(SlashResult::Handled);
|
|
290
295
|
}
|
|
296
|
+
let next_input = format!("{command} {}", opt.value);
|
|
297
|
+
return Box::pin(handle_slash_command(session, ui, &next_input)).await;
|
|
291
298
|
}
|
|
292
299
|
Ok(None) => {
|
|
293
|
-
|
|
300
|
+
ui.push_notice("Command cancelled");
|
|
294
301
|
}
|
|
295
302
|
Err(e) => {
|
|
296
|
-
|
|
303
|
+
ui.report_error(&e);
|
|
297
304
|
}
|
|
298
305
|
}
|
|
299
306
|
Some(SlashResult::Handled)
|
|
@@ -308,7 +315,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
308
315
|
}
|
|
309
316
|
SlashResponse::Navigate { markdown, .. } => {
|
|
310
317
|
if !markdown.is_empty() {
|
|
311
|
-
|
|
318
|
+
ui.push_command_output(markdown);
|
|
312
319
|
}
|
|
313
320
|
Some(SlashResult::Handled)
|
|
314
321
|
}
|
|
@@ -320,6 +327,7 @@ async fn execute_core_slash(session: &mut InteractiveSession, query: &str) -> Op
|
|
|
320
327
|
/// Route `/…` commands. Returns `None` if the input is not a slash command.
|
|
321
328
|
pub async fn handle_slash_command(
|
|
322
329
|
session: &mut InteractiveSession,
|
|
330
|
+
ui: &mut dyn CommandUi,
|
|
323
331
|
query: &str,
|
|
324
332
|
) -> Option<SlashResult> {
|
|
325
333
|
let trimmed = query.trim();
|
|
@@ -333,14 +341,46 @@ pub async fn handle_slash_command(
|
|
|
333
341
|
.map(|(c, r)| (c, r.trim()))
|
|
334
342
|
.unwrap_or((trimmed, ""));
|
|
335
343
|
|
|
344
|
+
if cmd == "/mods" && rest == "run-tools" {
|
|
345
|
+
let chat = mint_core::scoped_chat_id(
|
|
346
|
+
&session.chat_id,
|
|
347
|
+
Some(&session.current_dir.to_string_lossy()),
|
|
348
|
+
);
|
|
349
|
+
match mint_core::mods::pending_requests(&session.current_dir, &chat) {
|
|
350
|
+
Ok(requests) => {
|
|
351
|
+
for request in requests {
|
|
352
|
+
let body = format!(
|
|
353
|
+
"{} requests {}\n{}",
|
|
354
|
+
request["modId"],
|
|
355
|
+
request["action"],
|
|
356
|
+
serde_json::to_string_pretty(&request["input"]).unwrap_or_default()
|
|
357
|
+
);
|
|
358
|
+
if ui.prompt_confirm(&body, false).unwrap_or(false) {
|
|
359
|
+
let operation=serde_json::from_value(serde_json::json!({"workspacePath":session.current_dir,"chatId":chat,"action":"run_request","command":request["id"],"grantPermissions":true})).unwrap();
|
|
360
|
+
if let Err(error) = mint_core::mods::operate_async(operation).await {
|
|
361
|
+
ui.push_command_output(error);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
Err(error) => ui.push_command_output(error),
|
|
367
|
+
}
|
|
368
|
+
return Some(SlashResult::Handled);
|
|
369
|
+
}
|
|
370
|
+
|
|
336
371
|
// Check CLI-specific commands and interactive bare wizards first:
|
|
337
372
|
let is_cli_only_or_wizard = matches!(
|
|
338
373
|
cmd,
|
|
339
374
|
"/plan"
|
|
375
|
+
| "/thought"
|
|
376
|
+
| "/think"
|
|
377
|
+
| "/resume"
|
|
378
|
+
| "/branch"
|
|
340
379
|
| "/bg"
|
|
341
380
|
| "/jobs"
|
|
342
381
|
| "/shells"
|
|
343
382
|
| "/palette"
|
|
383
|
+
| "/theme"
|
|
344
384
|
| "/image"
|
|
345
385
|
| "/paste"
|
|
346
386
|
| "/avatar"
|
|
@@ -352,28 +392,33 @@ pub async fn handle_slash_command(
|
|
|
352
392
|
&& rest.is_empty());
|
|
353
393
|
|
|
354
394
|
if !is_cli_only_or_wizard {
|
|
355
|
-
if let Some(res) = execute_core_slash(session, trimmed).await {
|
|
395
|
+
if let Some(res) = execute_core_slash(session, ui, trimmed).await {
|
|
356
396
|
return Some(res);
|
|
357
397
|
}
|
|
358
398
|
}
|
|
359
399
|
|
|
360
400
|
match cmd {
|
|
361
401
|
"/help" => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
402
|
+
let mut lines = Vec::new();
|
|
403
|
+
lines.push(format!(
|
|
404
|
+
"{BLUE}────────────────────────────────────────────{RESET}"
|
|
405
|
+
));
|
|
406
|
+
lines.push(format!("{MINT} Mint Interactive Commands{RESET}"));
|
|
407
|
+
lines.push(format!(
|
|
408
|
+
"{BLUE}────────────────────────────────────────────{RESET}"
|
|
409
|
+
));
|
|
365
410
|
for spec in SLASH_COMMANDS.iter() {
|
|
366
411
|
let label = if spec.usage.is_empty() {
|
|
367
412
|
spec.token.to_string()
|
|
368
413
|
} else {
|
|
369
414
|
format!("{} {}", spec.token, spec.usage)
|
|
370
415
|
};
|
|
371
|
-
|
|
416
|
+
lines.push(format!(
|
|
372
417
|
" {MINT}{:<30}{RESET} {DIM}{}{RESET}",
|
|
373
418
|
label, spec.description
|
|
374
|
-
);
|
|
419
|
+
));
|
|
375
420
|
}
|
|
376
|
-
|
|
421
|
+
ui.push_command_output(lines.join("\n"));
|
|
377
422
|
Some(SlashResult::Handled)
|
|
378
423
|
}
|
|
379
424
|
|
|
@@ -384,34 +429,20 @@ pub async fn handle_slash_command(
|
|
|
384
429
|
"/fast" => {
|
|
385
430
|
let choice = if rest.is_empty() {
|
|
386
431
|
let options = vec![
|
|
387
|
-
"on (hide thinking traces)"
|
|
388
|
-
"off (show thinking traces)"
|
|
432
|
+
ChoiceItem::new("on (hide thinking traces)", "on"),
|
|
433
|
+
ChoiceItem::new("off (show thinking traces)", "off"),
|
|
389
434
|
];
|
|
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
|
-
}
|
|
435
|
+
match ui.prompt_choice("Select Fast Mode", "", &options) {
|
|
436
|
+
Ok(Some(0)) => Some(true),
|
|
437
|
+
Ok(Some(1)) => Some(false),
|
|
438
|
+
_ => None,
|
|
408
439
|
}
|
|
409
440
|
} else {
|
|
410
441
|
match rest {
|
|
411
442
|
"on" => Some(true),
|
|
412
443
|
"off" => Some(false),
|
|
413
444
|
_ => {
|
|
414
|
-
|
|
445
|
+
ui.push_notice("/fast usage: /fast [on|off]");
|
|
415
446
|
None
|
|
416
447
|
}
|
|
417
448
|
}
|
|
@@ -420,16 +451,82 @@ pub async fn handle_slash_command(
|
|
|
420
451
|
if let Some(mode) = choice {
|
|
421
452
|
session.fast_mode = mode;
|
|
422
453
|
if session.fast_mode {
|
|
423
|
-
|
|
454
|
+
ui.push_notice("[Fast] mode ON — thinking traces hidden");
|
|
424
455
|
} else {
|
|
425
|
-
|
|
456
|
+
ui.push_notice("[Fast] mode OFF");
|
|
426
457
|
}
|
|
427
458
|
}
|
|
428
459
|
Some(SlashResult::Handled)
|
|
429
460
|
}
|
|
430
461
|
|
|
462
|
+
"/resume" => {
|
|
463
|
+
let target_session_id = if rest.is_empty() {
|
|
464
|
+
prompt_resume_session_picker_with_ui(ui, &session.current_dir, &session.chat_id)
|
|
465
|
+
.ok()
|
|
466
|
+
.flatten()
|
|
467
|
+
} else {
|
|
468
|
+
let trimmed = rest.trim();
|
|
469
|
+
let candidate =
|
|
470
|
+
if !trimmed.starts_with("cli::") && trimmed != mint_core::CHAT_CLI_ID {
|
|
471
|
+
format!("cli::{trimmed}")
|
|
472
|
+
} else {
|
|
473
|
+
trimmed.to_string()
|
|
474
|
+
};
|
|
475
|
+
if let Ok(memory) = MemoryStore::open_default()
|
|
476
|
+
&& let Ok(sessions) = memory.list_chat_sessions()
|
|
477
|
+
&& sessions.iter().any(|s| s.id == candidate)
|
|
478
|
+
{
|
|
479
|
+
Some(candidate)
|
|
480
|
+
} else {
|
|
481
|
+
Some(trimmed.to_string())
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
if let Some(target_id) = target_session_id {
|
|
486
|
+
if target_id == session.chat_id {
|
|
487
|
+
ui.push_notice(format!("Already in session '{target_id}'."));
|
|
488
|
+
} else {
|
|
489
|
+
session.chat_id = target_id.clone();
|
|
490
|
+
mint_core::live_sync::update_live_sync_chat_id(&session.chat_id);
|
|
491
|
+
ui.reload_transcript(&session.chat_id, &session.current_dir);
|
|
492
|
+
if let Ok(memory) = MemoryStore::open_default() {
|
|
493
|
+
let sessions = memory.list_chat_sessions().unwrap_or_default();
|
|
494
|
+
let title = sessions
|
|
495
|
+
.iter()
|
|
496
|
+
.find(|s| s.id == session.chat_id)
|
|
497
|
+
.map(|s| s.title.as_str())
|
|
498
|
+
.unwrap_or("Conversation");
|
|
499
|
+
ui.push_notice(format!("Switched to session: {title} ({target_id})"));
|
|
500
|
+
if let Ok(recent) = memory.get_session_preview(&session.chat_id, 2)
|
|
501
|
+
&& let Some(last) = recent.first()
|
|
502
|
+
{
|
|
503
|
+
let snippet = crate::interactive::truncate_utf8(&last.user_text, 80);
|
|
504
|
+
ui.push_notice(format!("Last turn: {snippet}"));
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
ui.push_notice(format!("Switched to session: {target_id}"));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
ui.push_notice("Resume cancelled.");
|
|
512
|
+
}
|
|
513
|
+
Some(SlashResult::Handled)
|
|
514
|
+
}
|
|
515
|
+
|
|
431
516
|
"/rewind" => {
|
|
432
|
-
let checkpoints =
|
|
517
|
+
let (target_rewind_chat_id, checkpoints) = {
|
|
518
|
+
let local_cps = mint_core::git::list_checkpoints(&session.chat_id);
|
|
519
|
+
if local_cps.is_empty() && session.chat_id != mint_core::CHAT_CLI_ID {
|
|
520
|
+
let fallback_cps = mint_core::git::list_checkpoints(mint_core::CHAT_CLI_ID);
|
|
521
|
+
if !fallback_cps.is_empty() {
|
|
522
|
+
(mint_core::CHAT_CLI_ID.to_string(), fallback_cps)
|
|
523
|
+
} else {
|
|
524
|
+
(session.chat_id.clone(), local_cps)
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
(session.chat_id.clone(), local_cps)
|
|
528
|
+
}
|
|
529
|
+
};
|
|
433
530
|
if rest.is_empty() {
|
|
434
531
|
if checkpoints.is_empty() {
|
|
435
532
|
println!("\n{DIM}No git checkpoints recorded for this CLI session yet.{RESET}");
|
|
@@ -472,7 +569,7 @@ pub async fn handle_slash_command(
|
|
|
472
569
|
Ok(step) => {
|
|
473
570
|
match mint_core::git::rollback_to_step(
|
|
474
571
|
&session.current_dir,
|
|
475
|
-
|
|
572
|
+
&target_rewind_chat_id,
|
|
476
573
|
step,
|
|
477
574
|
) {
|
|
478
575
|
Ok(msg) => {
|
|
@@ -493,67 +590,125 @@ pub async fn handle_slash_command(
|
|
|
493
590
|
}
|
|
494
591
|
}
|
|
495
592
|
|
|
593
|
+
"/theme" => {
|
|
594
|
+
let current = crate::terminal_theme::TuiTheme::from_config(&session.config.tui_theme);
|
|
595
|
+
match ui.prompt_theme(current.index()) {
|
|
596
|
+
Ok(Some(index)) => {
|
|
597
|
+
let selected = crate::terminal_theme::TuiTheme::from_index(index);
|
|
598
|
+
let previous = std::mem::replace(
|
|
599
|
+
&mut session.config.tui_theme,
|
|
600
|
+
selected.config_value().to_owned(),
|
|
601
|
+
);
|
|
602
|
+
if let Err(error) = mint_core::save_config(&session.config) {
|
|
603
|
+
session.config.tui_theme = previous;
|
|
604
|
+
ui.report_error(&error.into());
|
|
605
|
+
} else {
|
|
606
|
+
ui.set_theme(selected);
|
|
607
|
+
ui.push_notice(format!("Theme: {}", selected.label()));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
Ok(None) => {}
|
|
611
|
+
Err(error) => ui.report_error(&error),
|
|
612
|
+
}
|
|
613
|
+
Some(SlashResult::Handled)
|
|
614
|
+
}
|
|
615
|
+
|
|
496
616
|
"/palette" => {
|
|
497
617
|
let options = vec![
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
618
|
+
ChoiceItem::with_description(
|
|
619
|
+
"Quick Actions Menu",
|
|
620
|
+
"Show general quick actions",
|
|
621
|
+
"tip",
|
|
622
|
+
),
|
|
623
|
+
ChoiceItem::with_description(
|
|
624
|
+
"Switch AI Model (/models)",
|
|
625
|
+
"Change active model or provider",
|
|
626
|
+
"/models",
|
|
627
|
+
),
|
|
628
|
+
ChoiceItem::with_description(
|
|
629
|
+
"Toggle Plan Mode (/plan)",
|
|
630
|
+
"Turn plan mode on or off",
|
|
631
|
+
"/plan",
|
|
632
|
+
),
|
|
633
|
+
ChoiceItem::with_description(
|
|
634
|
+
"Toggle Fast Mode (/fast)",
|
|
635
|
+
"Turn fast mode on or off",
|
|
636
|
+
"/fast",
|
|
637
|
+
),
|
|
638
|
+
ChoiceItem::with_description(
|
|
639
|
+
"Git Checkpoint Rollback (/rewind)",
|
|
640
|
+
"Undo recent AI edits",
|
|
641
|
+
"/rewind",
|
|
642
|
+
),
|
|
643
|
+
ChoiceItem::with_description(
|
|
644
|
+
"Manage MCP Servers (/mcp)",
|
|
645
|
+
"Add, remove, or configure tools",
|
|
646
|
+
"/mcp",
|
|
647
|
+
),
|
|
648
|
+
ChoiceItem::with_description(
|
|
649
|
+
"Manage Memory & Facts (/memory)",
|
|
650
|
+
"View or clear project memory",
|
|
651
|
+
"/memory",
|
|
652
|
+
),
|
|
653
|
+
ChoiceItem::with_description(
|
|
654
|
+
"Change Directory (/cd)",
|
|
655
|
+
"Change workspace directory",
|
|
656
|
+
"/cd",
|
|
657
|
+
),
|
|
658
|
+
ChoiceItem::with_description(
|
|
659
|
+
"Clear Conversation (/clear)",
|
|
660
|
+
"Clear current conversation history",
|
|
661
|
+
"/clear",
|
|
662
|
+
),
|
|
663
|
+
ChoiceItem::with_description(
|
|
664
|
+
"Help & Documentation (/help)",
|
|
665
|
+
"List all available slash commands",
|
|
666
|
+
"/help",
|
|
667
|
+
),
|
|
508
668
|
];
|
|
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"
|
|
669
|
+
match ui.prompt_choice(
|
|
670
|
+
"Universal Command Palette",
|
|
671
|
+
"Select command to run",
|
|
672
|
+
&options,
|
|
673
|
+
) {
|
|
674
|
+
Ok(Some(idx)) => {
|
|
675
|
+
let val = &options[idx].value;
|
|
676
|
+
if val == "tip" {
|
|
677
|
+
ui.push_notice(
|
|
678
|
+
"Tip: Type /<command> or press Tab to autocomplete commands anytime.",
|
|
530
679
|
);
|
|
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)
|
|
680
|
+
Some(SlashResult::Handled)
|
|
681
|
+
} else {
|
|
682
|
+
Box::pin(handle_slash_command(session, ui, val)).await
|
|
683
|
+
}
|
|
539
684
|
}
|
|
685
|
+
_ => Some(SlashResult::Handled),
|
|
540
686
|
}
|
|
541
687
|
}
|
|
542
688
|
|
|
689
|
+
"/thought" | "/think" => {
|
|
690
|
+
if let Some(record) = get_last_thought() {
|
|
691
|
+
let _ = show_thought_viewer(&record.thought, &record.elapsed_str);
|
|
692
|
+
} else {
|
|
693
|
+
ui.push_notice("No recent thought process recorded for this turn.");
|
|
694
|
+
}
|
|
695
|
+
Some(SlashResult::Handled)
|
|
696
|
+
}
|
|
697
|
+
|
|
543
698
|
"/plan" if rest == "list" => {
|
|
544
699
|
let files = list_plan_files(&session.current_dir);
|
|
545
700
|
if files.is_empty() {
|
|
546
|
-
|
|
701
|
+
ui.push_notice("No saved plans yet.");
|
|
547
702
|
} else {
|
|
548
|
-
|
|
703
|
+
let mut lines = vec![format!("{BLUE}Saved plans:{RESET}")];
|
|
549
704
|
for (idx, path) in files.iter().enumerate() {
|
|
550
|
-
|
|
705
|
+
lines.push(format!(
|
|
551
706
|
" {DIM}[{}]{RESET} {BLUE}{}{RESET}",
|
|
552
707
|
idx + 1,
|
|
553
708
|
first_line_preview(path)
|
|
554
|
-
);
|
|
709
|
+
));
|
|
555
710
|
}
|
|
556
|
-
|
|
711
|
+
ui.push_command_output(lines.join("\n"));
|
|
557
712
|
}
|
|
558
713
|
Some(SlashResult::Handled)
|
|
559
714
|
}
|
|
@@ -572,12 +727,12 @@ pub async fn handle_slash_command(
|
|
|
572
727
|
};
|
|
573
728
|
match found {
|
|
574
729
|
Some(path) => match std::fs::read_to_string(path) {
|
|
575
|
-
Ok(content) =>
|
|
576
|
-
Err(e) =>
|
|
730
|
+
Ok(content) => ui.push_command_output(content),
|
|
731
|
+
Err(e) => ui.push_notice(format!("Could not read plan file: {e}")),
|
|
577
732
|
},
|
|
578
|
-
None =>
|
|
579
|
-
"
|
|
580
|
-
),
|
|
733
|
+
None => ui.push_notice(format!(
|
|
734
|
+
"No saved plan matching \"{arg}\" — run /plan list to see saved plans."
|
|
735
|
+
)),
|
|
581
736
|
}
|
|
582
737
|
Some(SlashResult::Handled)
|
|
583
738
|
}
|
|
@@ -585,27 +740,13 @@ pub async fn handle_slash_command(
|
|
|
585
740
|
"/plan" => {
|
|
586
741
|
let choice = if rest.is_empty() {
|
|
587
742
|
let options = vec![
|
|
588
|
-
"on (read-only until plan is approved)"
|
|
589
|
-
"off (edits/shell run immediately)"
|
|
743
|
+
ChoiceItem::new("on (read-only until plan is approved)", "on"),
|
|
744
|
+
ChoiceItem::new("off (edits/shell run immediately)", "off"),
|
|
590
745
|
];
|
|
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
|
-
}
|
|
746
|
+
match ui.prompt_choice("Select Plan Mode", "", &options) {
|
|
747
|
+
Ok(Some(0)) => Some(true),
|
|
748
|
+
Ok(Some(1)) => Some(false),
|
|
749
|
+
_ => None,
|
|
609
750
|
}
|
|
610
751
|
} else {
|
|
611
752
|
match rest {
|
|
@@ -613,9 +754,7 @@ pub async fn handle_slash_command(
|
|
|
613
754
|
"off" => Some(false),
|
|
614
755
|
task => {
|
|
615
756
|
session.plan_mode = true;
|
|
616
|
-
|
|
617
|
-
"{DIM}[Plan] mode ON — agent will investigate read-only and present a plan before editing{RESET}\n"
|
|
618
|
-
);
|
|
757
|
+
ui.push_notice("[Plan] mode ON — agent will investigate read-only and present a plan before editing");
|
|
619
758
|
return Some(SlashResult::ForwardToAgent(task.to_string()));
|
|
620
759
|
}
|
|
621
760
|
}
|
|
@@ -624,11 +763,9 @@ pub async fn handle_slash_command(
|
|
|
624
763
|
if let Some(mode) = choice {
|
|
625
764
|
session.plan_mode = mode;
|
|
626
765
|
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
|
-
);
|
|
766
|
+
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
767
|
} else {
|
|
631
|
-
|
|
768
|
+
ui.push_notice("[Plan] mode OFF");
|
|
632
769
|
}
|
|
633
770
|
}
|
|
634
771
|
Some(SlashResult::Handled)
|
|
@@ -937,7 +1074,7 @@ pub async fn handle_slash_command(
|
|
|
937
1074
|
session.config.auto_skill_writing = rest == "on";
|
|
938
1075
|
match mint_core::save_config(&session.config) {
|
|
939
1076
|
Ok(()) => println!(
|
|
940
|
-
"{DIM}
|
|
1077
|
+
"{DIM}Skill suggestions (approval required to save) set to: {}{RESET}\n",
|
|
941
1078
|
if session.config.auto_skill_writing {
|
|
942
1079
|
"Enabled"
|
|
943
1080
|
} else {
|
|
@@ -950,7 +1087,7 @@ pub async fn handle_slash_command(
|
|
|
950
1087
|
println!("{WARN}Usage: /autoskill [on|off]{RESET}\n");
|
|
951
1088
|
} else {
|
|
952
1089
|
println!(
|
|
953
|
-
"{DIM}When enabled, the agent
|
|
1090
|
+
"{DIM}When enabled, the agent proposes a skill after a reusable task. Each proposed diff needs your approval before saving.{RESET}"
|
|
954
1091
|
);
|
|
955
1092
|
let options = vec!["on (enable)".to_string(), "off (disable)".to_string()];
|
|
956
1093
|
let current = if session.config.auto_skill_writing {
|
|
@@ -958,12 +1095,12 @@ pub async fn handle_slash_command(
|
|
|
958
1095
|
} else {
|
|
959
1096
|
&options[1]
|
|
960
1097
|
};
|
|
961
|
-
match prompt_interactive_select("
|
|
1098
|
+
match prompt_interactive_select("Skill Suggestions", &options, current) {
|
|
962
1099
|
Ok(Some(sel)) => {
|
|
963
1100
|
session.config.auto_skill_writing = sel.starts_with("on");
|
|
964
1101
|
match mint_core::save_config(&session.config) {
|
|
965
1102
|
Ok(()) => println!(
|
|
966
|
-
"{DIM}
|
|
1103
|
+
"{DIM}Skill suggestions (approval required to save) set to: {}{RESET}\n",
|
|
967
1104
|
if session.config.auto_skill_writing {
|
|
968
1105
|
"Enabled"
|
|
969
1106
|
} else {
|
|
@@ -1438,18 +1575,15 @@ pub async fn handle_slash_command(
|
|
|
1438
1575
|
|
|
1439
1576
|
"/bg" => {
|
|
1440
1577
|
if rest.is_empty() {
|
|
1441
|
-
|
|
1578
|
+
ui.push_notice("Usage: /bg <query>");
|
|
1442
1579
|
return Some(SlashResult::Handled);
|
|
1443
1580
|
}
|
|
1444
1581
|
let id = session
|
|
1445
1582
|
.jobs
|
|
1446
1583
|
.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
|
-
);
|
|
1584
|
+
ui.push_notice(format!(
|
|
1585
|
+
"Started background job #{id}. Keep chatting — check on it with /jobs, or view its result with /jobs show {id}."
|
|
1586
|
+
));
|
|
1453
1587
|
Some(SlashResult::Handled)
|
|
1454
1588
|
}
|
|
1455
1589
|
|
|
@@ -1457,9 +1591,9 @@ pub async fn handle_slash_command(
|
|
|
1457
1591
|
if rest.is_empty() {
|
|
1458
1592
|
let list = session.jobs.list();
|
|
1459
1593
|
if list.is_empty() {
|
|
1460
|
-
|
|
1594
|
+
ui.push_notice("No background jobs yet. Start one with /bg <query>.");
|
|
1461
1595
|
} else {
|
|
1462
|
-
|
|
1596
|
+
let mut lines = vec![format!("{BLUE}Background jobs{RESET}")];
|
|
1463
1597
|
for job in &list {
|
|
1464
1598
|
let status_str = match job.status {
|
|
1465
1599
|
JobStatus::Running => format!("{MINT}running{RESET}"),
|
|
@@ -1473,13 +1607,13 @@ pub async fn handle_slash_command(
|
|
|
1473
1607
|
} else {
|
|
1474
1608
|
""
|
|
1475
1609
|
};
|
|
1476
|
-
|
|
1610
|
+
lines.push(format!(
|
|
1477
1611
|
" #{:<3} [{status_str}] {DIM}{:>4}s{RESET} {preview}{suffix}",
|
|
1478
1612
|
job.id,
|
|
1479
1613
|
job.elapsed_secs(),
|
|
1480
|
-
);
|
|
1614
|
+
));
|
|
1481
1615
|
}
|
|
1482
|
-
|
|
1616
|
+
ui.push_command_output(lines.join("\n"));
|
|
1483
1617
|
}
|
|
1484
1618
|
return Some(SlashResult::Handled);
|
|
1485
1619
|
}
|
|
@@ -1492,40 +1626,52 @@ pub async fn handle_slash_command(
|
|
|
1492
1626
|
match sub {
|
|
1493
1627
|
"show" => match arg.parse::<u32>().ok().and_then(|id| session.jobs.show(id)) {
|
|
1494
1628
|
Some(job) => {
|
|
1495
|
-
|
|
1496
|
-
|
|
1629
|
+
let mut lines = vec![
|
|
1630
|
+
format!("{BLUE}Job #{} — {}{RESET}", job.id, job.status),
|
|
1631
|
+
format!("{DIM}Query:{RESET} {}", job.query),
|
|
1632
|
+
];
|
|
1497
1633
|
match job.result {
|
|
1498
|
-
Some(result) =>
|
|
1499
|
-
|
|
1634
|
+
Some(result) => {
|
|
1635
|
+
lines.push(String::new());
|
|
1636
|
+
lines.push(result);
|
|
1637
|
+
}
|
|
1638
|
+
None => {
|
|
1639
|
+
lines.push(format!("{DIM}(still running — no result yet){RESET}"))
|
|
1640
|
+
}
|
|
1500
1641
|
}
|
|
1642
|
+
ui.push_command_output(lines.join("\n"));
|
|
1501
1643
|
}
|
|
1502
|
-
None =>
|
|
1644
|
+
None => ui.push_notice("No such job."),
|
|
1503
1645
|
},
|
|
1504
1646
|
"cancel" => match arg.parse::<u32>().ok() {
|
|
1505
1647
|
Some(id) if session.jobs.cancel(id) => {
|
|
1506
|
-
|
|
1648
|
+
ui.push_notice(format!("Cancelling job #{id}..."));
|
|
1507
1649
|
}
|
|
1508
|
-
_ =>
|
|
1650
|
+
_ => ui.push_notice("No such running job."),
|
|
1509
1651
|
},
|
|
1510
|
-
_ =>
|
|
1652
|
+
_ => ui.push_notice("Usage: /jobs [show <id>|cancel <id>]"),
|
|
1511
1653
|
}
|
|
1512
1654
|
Some(SlashResult::Handled)
|
|
1513
1655
|
}
|
|
1514
1656
|
|
|
1515
1657
|
"/shells" => {
|
|
1516
|
-
if rest.is_empty() {
|
|
1517
|
-
let jobs = mint_core::bg_shell::list_jobs()
|
|
1658
|
+
if rest.is_empty() || rest == "all" {
|
|
1659
|
+
let jobs: Vec<_> = mint_core::bg_shell::list_jobs()
|
|
1660
|
+
.into_iter()
|
|
1661
|
+
.filter(|job| rest == "all" || job.workspace == session.current_dir)
|
|
1662
|
+
.collect();
|
|
1518
1663
|
if jobs.is_empty() {
|
|
1519
|
-
|
|
1520
|
-
"
|
|
1664
|
+
ui.push_notice(
|
|
1665
|
+
"No background shell jobs. The agent starts one when it calls run_shell with background: true."
|
|
1521
1666
|
);
|
|
1522
1667
|
} else {
|
|
1523
|
-
|
|
1668
|
+
let mut lines = vec![format!("{BLUE}Background shell jobs{RESET}")];
|
|
1524
1669
|
for job in &jobs {
|
|
1525
1670
|
let status_str = match &job.status {
|
|
1526
1671
|
mint_core::bg_shell::JobStatus::Running => {
|
|
1527
1672
|
format!("{MINT}running{RESET}")
|
|
1528
1673
|
}
|
|
1674
|
+
mint_core::bg_shell::JobStatus::Stopping => "stopping".to_string(),
|
|
1529
1675
|
mint_core::bg_shell::JobStatus::Exited(0) => "exited(0)".to_string(),
|
|
1530
1676
|
mint_core::bg_shell::JobStatus::Exited(code) => {
|
|
1531
1677
|
format!("{ERROR}exited({code}){RESET}")
|
|
@@ -1543,12 +1689,17 @@ pub async fn handle_slash_command(
|
|
|
1543
1689
|
} else {
|
|
1544
1690
|
""
|
|
1545
1691
|
};
|
|
1546
|
-
|
|
1547
|
-
" {:<6} [{status_str}] {DIM}{:>4}s{RESET} {preview}{suffix}",
|
|
1548
|
-
job.id,
|
|
1549
|
-
|
|
1692
|
+
lines.push(format!(
|
|
1693
|
+
" {:<6} [{status_str}] {DIM}{:>4}s{RESET} {preview}{suffix} · {}",
|
|
1694
|
+
job.id,
|
|
1695
|
+
job.elapsed_secs,
|
|
1696
|
+
job.workspace.display(),
|
|
1697
|
+
));
|
|
1698
|
+
if let Some(url) = &job.server_url {
|
|
1699
|
+
lines.push(format!(" Ready URL: {url}"));
|
|
1700
|
+
}
|
|
1550
1701
|
}
|
|
1551
|
-
|
|
1702
|
+
ui.push_command_output(lines.join("\n"));
|
|
1552
1703
|
}
|
|
1553
1704
|
return Some(SlashResult::Handled);
|
|
1554
1705
|
}
|
|
@@ -1561,48 +1712,55 @@ pub async fn handle_slash_command(
|
|
|
1561
1712
|
match sub {
|
|
1562
1713
|
"show" if !arg.is_empty() => match mint_core::bg_shell::snapshot(arg) {
|
|
1563
1714
|
Ok(job) => {
|
|
1564
|
-
|
|
1715
|
+
let mut lines =
|
|
1716
|
+
vec![format!("{BLUE}Job {} — {}{RESET}", job.id, job.command)];
|
|
1717
|
+
lines.push(format!("Status: {:?} · {}s", job.status, job.elapsed_secs));
|
|
1718
|
+
if let Some(url) = &job.server_url {
|
|
1719
|
+
lines.push(format!("Ready URL: {url}"));
|
|
1720
|
+
}
|
|
1565
1721
|
if let Some(pid) = job.pid {
|
|
1566
|
-
|
|
1722
|
+
lines.push(format!(
|
|
1723
|
+
"{DIM}pid: {pid} cwd: {}{RESET}",
|
|
1724
|
+
job.cwd.display()
|
|
1725
|
+
));
|
|
1567
1726
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1727
|
+
lines.push(format!("{DIM}stdout:{RESET}\n{}", job.stdout));
|
|
1728
|
+
lines.push(format!("{DIM}stderr:{RESET}\n{}", job.stderr));
|
|
1729
|
+
if job.truncated {
|
|
1730
|
+
lines.push("[Older output truncated]".into());
|
|
1731
|
+
}
|
|
1732
|
+
ui.push_command_output(lines.join("\n"));
|
|
1570
1733
|
}
|
|
1571
|
-
Err(_) =>
|
|
1734
|
+
Err(_) => ui.push_notice("No such job."),
|
|
1572
1735
|
},
|
|
1573
|
-
"kill" if !arg.is_empty() => match mint_core::bg_shell::kill_job(arg) {
|
|
1574
|
-
Ok(msg) =>
|
|
1575
|
-
Err(_) =>
|
|
1736
|
+
"kill" | "stop" if !arg.is_empty() => match mint_core::bg_shell::kill_job(arg) {
|
|
1737
|
+
Ok(msg) => ui.push_notice(msg),
|
|
1738
|
+
Err(_) => ui.push_notice("No such job."),
|
|
1576
1739
|
},
|
|
1577
|
-
_ =>
|
|
1740
|
+
_ => ui.push_notice("Usage: /shells [all|show <id>|stop <id>|kill <id>]"),
|
|
1578
1741
|
}
|
|
1579
1742
|
Some(SlashResult::Handled)
|
|
1580
1743
|
}
|
|
1581
1744
|
|
|
1582
1745
|
"/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
|
-
|
|
1746
|
+
let choice = ui
|
|
1747
|
+
.prompt_confirm("Clear conversation history?", false)
|
|
1748
|
+
.unwrap_or(false);
|
|
1596
1749
|
if choice {
|
|
1750
|
+
clear_last_thought();
|
|
1751
|
+
session.history.clear();
|
|
1752
|
+
ui.clear_transcript();
|
|
1597
1753
|
if let Ok(memory) = MemoryStore::open_default() {
|
|
1598
|
-
match memory.
|
|
1599
|
-
Ok(count) =>
|
|
1600
|
-
|
|
1754
|
+
match memory.clear_interactions_for_chat(&session.chat_id) {
|
|
1755
|
+
Ok(count) => {
|
|
1756
|
+
ui.push_notice(format!("Cleared {count} interactions for session."));
|
|
1757
|
+
}
|
|
1758
|
+
Err(error) => ui.push_notice(format!("Memory error: {error}")),
|
|
1601
1759
|
}
|
|
1602
1760
|
}
|
|
1603
|
-
|
|
1761
|
+
ui.push_notice("Conversation context cleared.");
|
|
1604
1762
|
} else {
|
|
1605
|
-
|
|
1763
|
+
ui.push_notice("Cancelled.");
|
|
1606
1764
|
}
|
|
1607
1765
|
Some(SlashResult::Handled)
|
|
1608
1766
|
}
|
|
@@ -1616,7 +1774,7 @@ pub async fn handle_slash_command(
|
|
|
1616
1774
|
session.current_dir = new_dir.canonicalize().unwrap_or(new_dir);
|
|
1617
1775
|
println!(
|
|
1618
1776
|
"{DIM}Workspace: {}{RESET}\n",
|
|
1619
|
-
|
|
1777
|
+
format_workspace_with_branch(&session.current_dir)
|
|
1620
1778
|
);
|
|
1621
1779
|
} else {
|
|
1622
1780
|
println!("{ERROR}Directory not found:{RESET} {rest}\n");
|
|
@@ -1625,6 +1783,8 @@ pub async fn handle_slash_command(
|
|
|
1625
1783
|
Some(SlashResult::Handled)
|
|
1626
1784
|
}
|
|
1627
1785
|
|
|
1786
|
+
"/branch" => Some(handle_branch_slash(session, ui, rest)),
|
|
1787
|
+
|
|
1628
1788
|
"/veo" => {
|
|
1629
1789
|
if rest.is_empty() {
|
|
1630
1790
|
println!(
|
|
@@ -1746,17 +1906,16 @@ pub async fn handle_slash_command(
|
|
|
1746
1906
|
mask_data_uri: None,
|
|
1747
1907
|
mode: Some("edit".to_owned()),
|
|
1748
1908
|
};
|
|
1749
|
-
match mint_core::
|
|
1909
|
+
match mint_core::image_workflow::generate_and_save_images(&session.config, &req)
|
|
1910
|
+
.await
|
|
1911
|
+
{
|
|
1750
1912
|
Ok(resp) => {
|
|
1751
1913
|
if let Some(first) = resp.images.first() {
|
|
1752
1914
|
println!(
|
|
1753
1915
|
"{DIM}Image edited successfully using {} ({}){RESET}\n",
|
|
1754
1916
|
resp.provider, resp.model
|
|
1755
1917
|
);
|
|
1756
|
-
println!(
|
|
1757
|
-
"{DIM}Image data URI prefix: {}{RESET}\n",
|
|
1758
|
-
&first.data_uri[..first.data_uri.len().min(60)]
|
|
1759
|
-
);
|
|
1918
|
+
println!("{DIM}Saved image: {}{RESET}\n", first.path.display());
|
|
1760
1919
|
}
|
|
1761
1920
|
}
|
|
1762
1921
|
Err(e) => {
|
|
@@ -1813,49 +1972,34 @@ pub async fn handle_slash_command(
|
|
|
1813
1972
|
|
|
1814
1973
|
"/learn" => {
|
|
1815
1974
|
if rest.is_empty() {
|
|
1816
|
-
let
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1975
|
+
let unique_skills = match MemoryStore::open_default()
|
|
1976
|
+
.map_err(|e| e.to_string())
|
|
1977
|
+
.and_then(|memory| {
|
|
1978
|
+
mint_core::skill_inventory::list_available_skills(
|
|
1979
|
+
&memory,
|
|
1980
|
+
Some(&session.current_dir),
|
|
1981
|
+
)
|
|
1982
|
+
.map_err(|e| e.to_string())
|
|
1983
|
+
}) {
|
|
1984
|
+
Ok(skills) => skills,
|
|
1985
|
+
Err(error) => {
|
|
1986
|
+
println!("{ERROR}Error loading skills: {error}{RESET}\n");
|
|
1826
1987
|
return Some(SlashResult::Handled);
|
|
1827
1988
|
}
|
|
1828
1989
|
};
|
|
1829
|
-
|
|
1830
|
-
if let Some(home) = dirs::home_dir() {
|
|
1831
|
-
let global_skills_path = home.join(".config").join("mint").join("mint-skills");
|
|
1832
|
-
mint_core::skills::load_skills_from_dir(&global_skills_path, &mut skills);
|
|
1833
|
-
}
|
|
1834
|
-
let workspace_skills_path1 = session.current_dir.join(".agents").join("skills");
|
|
1835
|
-
mint_core::skills::load_skills_from_dir(&workspace_skills_path1, &mut skills);
|
|
1836
|
-
let workspace_skills_path2 = session.current_dir.join("skills");
|
|
1837
|
-
mint_core::skills::load_skills_from_dir(&workspace_skills_path2, &mut skills);
|
|
1838
|
-
|
|
1839
|
-
let mut unique_skills = std::collections::BTreeMap::new();
|
|
1840
|
-
for skill in skills {
|
|
1841
|
-
let loc = if skill.source_path.contains("/.config/mint/mint-skills") {
|
|
1842
|
-
"Global"
|
|
1843
|
-
} else if skill.source_path.contains("/skills")
|
|
1844
|
-
|| skill.source_path.contains("/.agents/skills")
|
|
1845
|
-
{
|
|
1846
|
-
"Workspace"
|
|
1847
|
-
} else {
|
|
1848
|
-
"Taught"
|
|
1849
|
-
};
|
|
1850
|
-
unique_skills.insert(skill.name.clone(), (skill, loc));
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
1990
|
if unique_skills.is_empty() {
|
|
1854
1991
|
println!("No learned skills found. Use '/learn <path>' to add one.\n");
|
|
1855
1992
|
} else {
|
|
1856
1993
|
println!("Learned AI Skills:");
|
|
1857
|
-
for
|
|
1858
|
-
|
|
1994
|
+
for entry in &unique_skills {
|
|
1995
|
+
let skill = &entry.skill;
|
|
1996
|
+
let name = &skill.name;
|
|
1997
|
+
let loc = match entry.location {
|
|
1998
|
+
mint_core::skill_inventory::SkillLocation::Database => "Taught",
|
|
1999
|
+
mint_core::skill_inventory::SkillLocation::Global => "Global",
|
|
2000
|
+
mint_core::skill_inventory::SkillLocation::Workspace => "Workspace",
|
|
2001
|
+
};
|
|
2002
|
+
if loc == "Taught" {
|
|
1859
2003
|
println!(" ● [{}] {}", loc, name);
|
|
1860
2004
|
} else {
|
|
1861
2005
|
println!(" ● [{}] {} (Source: {})", loc, name, skill.source_path);
|
|
@@ -2272,8 +2416,35 @@ pub async fn handle_slash_command(
|
|
|
2272
2416
|
}
|
|
2273
2417
|
}
|
|
2274
2418
|
}
|
|
2419
|
+
"refresh" => match mint_core::refresh_linked_folder(args) {
|
|
2420
|
+
Ok(status) => println!("Indexed {} files in {args}.\n", status.indexed_files),
|
|
2421
|
+
Err(e) => println!("{ERROR}Error:{RESET} {e}\n"),
|
|
2422
|
+
},
|
|
2423
|
+
"notes" => match mint_core::list_linked_folder_notes(args) {
|
|
2424
|
+
Ok(notes) => {
|
|
2425
|
+
for note in notes {
|
|
2426
|
+
println!("{} [{}] {}", note.created_at, note.status, note.path);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
Err(e) => println!("{ERROR}Error:{RESET} {e}\n"),
|
|
2430
|
+
},
|
|
2431
|
+
"save" => {
|
|
2432
|
+
if let Some((name, content)) = args.split_once('|') {
|
|
2433
|
+
match mint_core::save_linked_folder_note(name.trim(), content.trim()) {
|
|
2434
|
+
Ok(note) if note.status == "saved" => {
|
|
2435
|
+
println!("Saved note: {}", note.path)
|
|
2436
|
+
}
|
|
2437
|
+
Ok(note) => {
|
|
2438
|
+
println!("{ERROR}{}{RESET}", note.error.unwrap_or_default())
|
|
2439
|
+
}
|
|
2440
|
+
Err(e) => println!("{ERROR}Error:{RESET} {e}"),
|
|
2441
|
+
}
|
|
2442
|
+
} else {
|
|
2443
|
+
println!("{WARN}/link save <name> | <text>{RESET}");
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2275
2446
|
_ => println!(
|
|
2276
|
-
"{WARN}/link usage: list | add <name> | <path> | <description> | remove <name>{RESET}\n"
|
|
2447
|
+
"{WARN}/link usage: list | add <name> | <path> | <description> | remove <name> | refresh <name> | notes <name> | save <name> | <text>{RESET}\n"
|
|
2277
2448
|
),
|
|
2278
2449
|
}
|
|
2279
2450
|
Some(SlashResult::Handled)
|
|
@@ -2337,7 +2508,7 @@ pub async fn handle_slash_command(
|
|
|
2337
2508
|
.split_once(char::is_whitespace)
|
|
2338
2509
|
.map(|(c, a)| (c, a.trim()))
|
|
2339
2510
|
.unwrap_or((rest, ""));
|
|
2340
|
-
handle_mcp_slash(session, subcmd, args);
|
|
2511
|
+
handle_mcp_slash(session, ui, subcmd, args);
|
|
2341
2512
|
Some(SlashResult::Handled)
|
|
2342
2513
|
}
|
|
2343
2514
|
|
|
@@ -2356,36 +2527,41 @@ pub async fn handle_slash_command(
|
|
|
2356
2527
|
})
|
|
2357
2528
|
.map(|v| v.len())
|
|
2358
2529
|
.unwrap_or(0);
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2530
|
+
let mut out = Vec::new();
|
|
2531
|
+
out.push(format!(
|
|
2532
|
+
"\n{BLUE}─ Session Stats ─────────────────────────{RESET}"
|
|
2533
|
+
));
|
|
2534
|
+
out.push(format!(" Provider : {MINT}{provider}{RESET}"));
|
|
2535
|
+
out.push(format!(" Model : {model}"));
|
|
2536
|
+
out.push(format!(
|
|
2363
2537
|
" Workspace: {}",
|
|
2364
|
-
|
|
2365
|
-
);
|
|
2366
|
-
|
|
2538
|
+
format_workspace_with_branch(&session.current_dir)
|
|
2539
|
+
));
|
|
2540
|
+
out.push(format!(
|
|
2367
2541
|
" Fast mode: {}",
|
|
2368
2542
|
if session.fast_mode { "on" } else { "off" }
|
|
2369
|
-
);
|
|
2370
|
-
|
|
2543
|
+
));
|
|
2544
|
+
out.push(format!(" Memory : {interactions} interactions"));
|
|
2371
2545
|
if let Some(ref img_data) = session.pending_image {
|
|
2372
2546
|
let count = img_data.split_whitespace().count();
|
|
2373
2547
|
if count > 1 {
|
|
2374
|
-
|
|
2548
|
+
out.push(format!(
|
|
2549
|
+
" Images : {WARN}{} images attached{RESET}",
|
|
2550
|
+
count
|
|
2551
|
+
));
|
|
2375
2552
|
} else {
|
|
2376
|
-
|
|
2553
|
+
out.push(format!(" Image : {WARN}attached{RESET}"));
|
|
2377
2554
|
}
|
|
2378
2555
|
}
|
|
2379
|
-
|
|
2380
|
-
println!();
|
|
2556
|
+
ui.push_command_output(out.join("\n"));
|
|
2381
2557
|
Some(SlashResult::Handled)
|
|
2382
2558
|
}
|
|
2383
2559
|
|
|
2384
2560
|
"/release-notes" => {
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2561
|
+
ui.push_command_output(format!(
|
|
2562
|
+
"\n{BLUE}────────────────────────────────────────────{RESET}\n{}\n{BLUE}────────────────────────────────────────────{RESET}",
|
|
2563
|
+
mint_core::release_notes::text()
|
|
2564
|
+
));
|
|
2389
2565
|
Some(SlashResult::Handled)
|
|
2390
2566
|
}
|
|
2391
2567
|
|
|
@@ -2541,7 +2717,7 @@ pub async fn handle_slash_command(
|
|
|
2541
2717
|
} else if e.is_oauth {
|
|
2542
2718
|
format!("{DIM}{:<22}{RESET}", plain_status)
|
|
2543
2719
|
} else {
|
|
2544
|
-
format!("
|
|
2720
|
+
format!("{ANSI_BLUE}{:<22}\x1b[0m", plain_status)
|
|
2545
2721
|
};
|
|
2546
2722
|
println!(
|
|
2547
2723
|
" {BLUE}❯ {:<18}{RESET}{} {DIM}{short_desc}{RESET}",
|
|
@@ -2824,6 +3000,136 @@ pub async fn handle_slash_command(
|
|
|
2824
3000
|
}
|
|
2825
3001
|
}
|
|
2826
3002
|
|
|
3003
|
+
fn handle_branch_slash(
|
|
3004
|
+
session: &mut InteractiveSession,
|
|
3005
|
+
ui: &mut dyn CommandUi,
|
|
3006
|
+
requested: &str,
|
|
3007
|
+
) -> SlashResult {
|
|
3008
|
+
use mint_core::git::{BranchChange, BranchChangeOutcome, change_branch, read_branch_info};
|
|
3009
|
+
|
|
3010
|
+
let info = match read_branch_info(&session.current_dir) {
|
|
3011
|
+
Ok(info) if info.is_repository => info,
|
|
3012
|
+
Ok(_) => {
|
|
3013
|
+
ui.push_notice("Current workspace is not a Git repository.");
|
|
3014
|
+
return SlashResult::Handled;
|
|
3015
|
+
}
|
|
3016
|
+
Err(error) => {
|
|
3017
|
+
ui.push_notice(format!("Git error: {error}"));
|
|
3018
|
+
return SlashResult::Handled;
|
|
3019
|
+
}
|
|
3020
|
+
};
|
|
3021
|
+
|
|
3022
|
+
let (remote, branch) = if requested.is_empty() {
|
|
3023
|
+
let mut choices = info
|
|
3024
|
+
.branches
|
|
3025
|
+
.iter()
|
|
3026
|
+
.map(|branch| {
|
|
3027
|
+
(
|
|
3028
|
+
format!(
|
|
3029
|
+
"{branch}{}",
|
|
3030
|
+
if info.current_branch.as_deref() == Some(branch) {
|
|
3031
|
+
" (current)"
|
|
3032
|
+
} else {
|
|
3033
|
+
""
|
|
3034
|
+
}
|
|
3035
|
+
),
|
|
3036
|
+
false,
|
|
3037
|
+
branch.clone(),
|
|
3038
|
+
)
|
|
3039
|
+
})
|
|
3040
|
+
.collect::<Vec<_>>();
|
|
3041
|
+
choices.extend(
|
|
3042
|
+
info.remote_branches
|
|
3043
|
+
.iter()
|
|
3044
|
+
.map(|branch| (format!("{branch} (remote · track)"), true, branch.clone())),
|
|
3045
|
+
);
|
|
3046
|
+
if choices.is_empty() {
|
|
3047
|
+
ui.push_notice("No branches found in this workspace.");
|
|
3048
|
+
return SlashResult::Handled;
|
|
3049
|
+
}
|
|
3050
|
+
let items: Vec<ChoiceItem> = choices
|
|
3051
|
+
.iter()
|
|
3052
|
+
.map(|(label, is_remote, name)| {
|
|
3053
|
+
let desc = if *is_remote {
|
|
3054
|
+
"remote · track"
|
|
3055
|
+
} else {
|
|
3056
|
+
"local"
|
|
3057
|
+
};
|
|
3058
|
+
ChoiceItem::with_description(label.clone(), desc, name.clone())
|
|
3059
|
+
})
|
|
3060
|
+
.collect();
|
|
3061
|
+
match ui.prompt_choice("Switch Git Branch", "Select a branch to switch to", &items) {
|
|
3062
|
+
Ok(Some(idx)) => {
|
|
3063
|
+
let (_, is_remote, ref b) = choices[idx];
|
|
3064
|
+
(is_remote, b.clone())
|
|
3065
|
+
}
|
|
3066
|
+
_ => {
|
|
3067
|
+
ui.push_notice("Branch selection cancelled.");
|
|
3068
|
+
return SlashResult::Handled;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
} else if info.branches.iter().any(|branch| branch == requested) {
|
|
3072
|
+
(false, requested.to_string())
|
|
3073
|
+
} else if info
|
|
3074
|
+
.remote_branches
|
|
3075
|
+
.iter()
|
|
3076
|
+
.any(|branch| branch == requested)
|
|
3077
|
+
{
|
|
3078
|
+
(true, requested.to_string())
|
|
3079
|
+
} else {
|
|
3080
|
+
ui.push_notice(format!("Branch not found: {requested}"));
|
|
3081
|
+
return SlashResult::Handled;
|
|
3082
|
+
};
|
|
3083
|
+
|
|
3084
|
+
if !remote && info.current_branch.as_deref() == Some(branch.as_str()) {
|
|
3085
|
+
ui.push_notice(format!("Already on branch {branch}."));
|
|
3086
|
+
return SlashResult::Handled;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
let change = if remote {
|
|
3090
|
+
BranchChange::Track {
|
|
3091
|
+
remote_branch: branch.clone(),
|
|
3092
|
+
}
|
|
3093
|
+
} else {
|
|
3094
|
+
BranchChange::Switch {
|
|
3095
|
+
branch: branch.clone(),
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
let result = match change_branch(&session.current_dir, &change, false) {
|
|
3099
|
+
Ok(BranchChangeOutcome::Changed { info }) => Ok(info),
|
|
3100
|
+
Ok(BranchChangeOutcome::ConfirmationRequired { .. }) => {
|
|
3101
|
+
match ui.prompt_confirm(
|
|
3102
|
+
"Workspace has uncommitted changes. Continue switching branches?",
|
|
3103
|
+
false,
|
|
3104
|
+
) {
|
|
3105
|
+
Ok(true) => {
|
|
3106
|
+
change_branch(&session.current_dir, &change, true).and_then(|outcome| {
|
|
3107
|
+
match outcome {
|
|
3108
|
+
BranchChangeOutcome::Changed { info } => Ok(info),
|
|
3109
|
+
BranchChangeOutcome::ConfirmationRequired { .. } => {
|
|
3110
|
+
Err("Workspace still requires confirmation.".to_string())
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
})
|
|
3114
|
+
}
|
|
3115
|
+
_ => {
|
|
3116
|
+
ui.push_notice("Branch change cancelled.");
|
|
3117
|
+
return SlashResult::Handled;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
Err(error) => Err(error),
|
|
3122
|
+
};
|
|
3123
|
+
match result {
|
|
3124
|
+
Ok(next) => {
|
|
3125
|
+
let current = next.current_branch.as_deref().unwrap_or("detached HEAD");
|
|
3126
|
+
ui.push_notice(format!("Switched to {current}."));
|
|
3127
|
+
}
|
|
3128
|
+
Err(error) => ui.push_notice(format!("Git error: {error}")),
|
|
3129
|
+
}
|
|
3130
|
+
SlashResult::Handled
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2827
3133
|
// ── /mcp interactive handling ────────────────────────────────────────────────
|
|
2828
3134
|
|
|
2829
3135
|
/// Pull the just-persisted config back into the session so subsequent turns
|
|
@@ -2834,9 +3140,14 @@ fn reload_session_config(session: &mut InteractiveSession) {
|
|
|
2834
3140
|
}
|
|
2835
3141
|
}
|
|
2836
3142
|
|
|
2837
|
-
fn handle_mcp_slash(
|
|
3143
|
+
fn handle_mcp_slash(
|
|
3144
|
+
session: &mut InteractiveSession,
|
|
3145
|
+
ui: &mut dyn CommandUi,
|
|
3146
|
+
subcmd: &str,
|
|
3147
|
+
args: &str,
|
|
3148
|
+
) {
|
|
2838
3149
|
match subcmd {
|
|
2839
|
-
"list" | "" => mcp_interactive_picker(session),
|
|
3150
|
+
"list" | "" => mcp_interactive_picker(session, ui),
|
|
2840
3151
|
|
|
2841
3152
|
"add" => {
|
|
2842
3153
|
let mut tokens: Vec<&str> = args.split_whitespace().collect();
|
|
@@ -2857,54 +3168,52 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2857
3168
|
if allow_all {
|
|
2858
3169
|
let _ = crate::mcp::allow(name, "*");
|
|
2859
3170
|
}
|
|
2860
|
-
|
|
2861
|
-
"
|
|
3171
|
+
ui.push_notice(format!(
|
|
3172
|
+
"Added MCP server '{name}'{}.",
|
|
2862
3173
|
if allow_all {
|
|
2863
3174
|
" (all tools allowed)"
|
|
2864
3175
|
} else {
|
|
2865
3176
|
""
|
|
2866
3177
|
}
|
|
2867
|
-
);
|
|
3178
|
+
));
|
|
2868
3179
|
reload_session_config(session);
|
|
2869
3180
|
}
|
|
2870
|
-
Err(e) =>
|
|
3181
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2871
3182
|
}
|
|
2872
3183
|
}
|
|
2873
|
-
_ =>
|
|
2874
|
-
"{WARN}/mcp add usage:{RESET} <name> <command|url> [args...] [--allow-all]\n"
|
|
2875
|
-
),
|
|
3184
|
+
_ => ui.push_notice("/mcp add usage: <name> <command|url> [args...] [--allow-all]"),
|
|
2876
3185
|
}
|
|
2877
3186
|
}
|
|
2878
3187
|
|
|
2879
3188
|
"remove" if !args.is_empty() => match crate::mcp::remove(args) {
|
|
2880
3189
|
Ok(true) => {
|
|
2881
|
-
|
|
3190
|
+
ui.push_notice(format!("Removed '{args}'."));
|
|
2882
3191
|
reload_session_config(session);
|
|
2883
3192
|
}
|
|
2884
|
-
Ok(false) =>
|
|
2885
|
-
Err(e) =>
|
|
3193
|
+
Ok(false) => ui.push_notice(format!("No MCP server named '{args}'.")),
|
|
3194
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2886
3195
|
},
|
|
2887
3196
|
|
|
2888
3197
|
"clear" => match crate::mcp::clear() {
|
|
2889
3198
|
Ok(()) => {
|
|
2890
|
-
|
|
3199
|
+
ui.push_notice("Removed all MCP servers.");
|
|
2891
3200
|
reload_session_config(session);
|
|
2892
3201
|
}
|
|
2893
|
-
Err(e) =>
|
|
3202
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2894
3203
|
},
|
|
2895
3204
|
|
|
2896
3205
|
"enable" | "disable" if !args.is_empty() => {
|
|
2897
3206
|
let disable = subcmd == "disable";
|
|
2898
3207
|
match crate::mcp::set_disabled(args, disable) {
|
|
2899
3208
|
Ok(true) => {
|
|
2900
|
-
|
|
2901
|
-
"{
|
|
3209
|
+
ui.push_notice(format!(
|
|
3210
|
+
"{} '{args}'.",
|
|
2902
3211
|
if disable { "Disabled" } else { "Enabled" }
|
|
2903
|
-
);
|
|
3212
|
+
));
|
|
2904
3213
|
reload_session_config(session);
|
|
2905
3214
|
}
|
|
2906
|
-
Ok(false) =>
|
|
2907
|
-
Err(e) =>
|
|
3215
|
+
Ok(false) => ui.push_notice(format!("No MCP server named '{args}'.")),
|
|
3216
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2908
3217
|
}
|
|
2909
3218
|
}
|
|
2910
3219
|
|
|
@@ -2918,9 +3227,7 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2918
3227
|
crate::mcp::edit(name, Some(value.to_string()), None, None, None)
|
|
2919
3228
|
}
|
|
2920
3229
|
"command" => {
|
|
2921
|
-
|
|
2922
|
-
"{WARN}/mcp edit <name> command <value> needs a value{RESET}\n"
|
|
2923
|
-
);
|
|
3230
|
+
ui.push_notice("/mcp edit <name> command <value> needs a value");
|
|
2924
3231
|
return;
|
|
2925
3232
|
}
|
|
2926
3233
|
"args" => crate::mcp::edit(
|
|
@@ -2937,21 +3244,24 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2937
3244
|
None,
|
|
2938
3245
|
Some((!value.is_empty()).then(|| value.to_string())),
|
|
2939
3246
|
),
|
|
3247
|
+
"timeout" | "timeoutsecs" | "timeout-secs" => {
|
|
3248
|
+
crate::mcp::set_timeout(name, value)
|
|
3249
|
+
}
|
|
2940
3250
|
_ => {
|
|
2941
|
-
|
|
3251
|
+
ui.push_notice("Editable fields: command | args | icon | timeout");
|
|
2942
3252
|
return;
|
|
2943
3253
|
}
|
|
2944
3254
|
};
|
|
2945
3255
|
match result {
|
|
2946
3256
|
Ok(true) => {
|
|
2947
|
-
|
|
3257
|
+
ui.push_notice(format!("Updated '{name}'."));
|
|
2948
3258
|
reload_session_config(session);
|
|
2949
3259
|
}
|
|
2950
|
-
Ok(false) =>
|
|
2951
|
-
Err(e) =>
|
|
3260
|
+
Ok(false) => ui.push_notice(format!("No MCP server named '{name}'.")),
|
|
3261
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2952
3262
|
}
|
|
2953
3263
|
}
|
|
2954
|
-
_ =>
|
|
3264
|
+
_ => ui.push_notice("/mcp edit usage: <name> command|args|icon|timeout <value>"),
|
|
2955
3265
|
}
|
|
2956
3266
|
}
|
|
2957
3267
|
|
|
@@ -2966,45 +3276,43 @@ fn handle_mcp_slash(session: &mut InteractiveSession, subcmd: &str, args: &str)
|
|
|
2966
3276
|
};
|
|
2967
3277
|
match result {
|
|
2968
3278
|
Ok(true) => {
|
|
2969
|
-
|
|
3279
|
+
ui.push_notice(format!("{verb} {server}/{tool}."));
|
|
2970
3280
|
reload_session_config(session);
|
|
2971
3281
|
}
|
|
2972
|
-
Ok(false) =>
|
|
2973
|
-
Err(e) =>
|
|
3282
|
+
Ok(false) => ui.push_notice(format!("No change for {server}/{tool}.")),
|
|
3283
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2974
3284
|
}
|
|
2975
3285
|
}
|
|
2976
|
-
_ =>
|
|
3286
|
+
_ => ui.push_notice(format!("/mcp {subcmd} usage: <server> <tool>")),
|
|
2977
3287
|
}
|
|
2978
3288
|
}
|
|
2979
3289
|
|
|
2980
3290
|
"reauth" if !args.is_empty() => {
|
|
2981
|
-
|
|
2982
|
-
"
|
|
2983
|
-
);
|
|
3291
|
+
ui.push_notice(format!(
|
|
3292
|
+
"Re-authenticating MCP server '{args}'... (a browser tab may open)"
|
|
3293
|
+
));
|
|
2984
3294
|
match crate::mcp::reauth(args) {
|
|
2985
|
-
Ok(true) =>
|
|
2986
|
-
Ok(false) =>
|
|
2987
|
-
|
|
2988
|
-
),
|
|
2989
|
-
Err(e) => println!("{ERROR}MCP error:{RESET} {e}\n"),
|
|
3295
|
+
Ok(true) => ui.push_notice(format!("Re-authentication succeeded for '{args}'.")),
|
|
3296
|
+
Ok(false) => ui.push_notice(format!("Re-authentication failed for '{args}'.")),
|
|
3297
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
2990
3298
|
}
|
|
2991
3299
|
}
|
|
2992
3300
|
|
|
2993
|
-
_ =>
|
|
2994
|
-
"
|
|
3301
|
+
_ => ui.push_notice(
|
|
3302
|
+
"/mcp usage: list | add <name> <cmd> [args...] | remove <name> | \
|
|
2995
3303
|
enable|disable <name> | edit <name> command|args|icon <value> | \
|
|
2996
|
-
allow|disallow <server> <tool> | reauth <server> | clear
|
|
3304
|
+
allow|disallow <server> <tool> | reauth <server> | clear",
|
|
2997
3305
|
),
|
|
2998
3306
|
}
|
|
2999
3307
|
}
|
|
3000
3308
|
|
|
3001
|
-
/// The
|
|
3309
|
+
/// The `/mcp` flow: an "add" row plus every configured server; picking
|
|
3002
3310
|
/// a server opens a per-server action menu.
|
|
3003
|
-
fn mcp_interactive_picker(session: &mut InteractiveSession) {
|
|
3311
|
+
fn mcp_interactive_picker(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3004
3312
|
let servers = match crate::mcp::list() {
|
|
3005
3313
|
Ok(servers) => servers,
|
|
3006
3314
|
Err(e) => {
|
|
3007
|
-
|
|
3315
|
+
ui.push_notice(format!("MCP error: {e}"));
|
|
3008
3316
|
return;
|
|
3009
3317
|
}
|
|
3010
3318
|
};
|
|
@@ -3019,100 +3327,114 @@ fn mcp_interactive_picker(session: &mut InteractiveSession) {
|
|
|
3019
3327
|
.and_then(|v| v.as_object())
|
|
3020
3328
|
.cloned();
|
|
3021
3329
|
|
|
3022
|
-
let mut choices = vec![
|
|
3330
|
+
let mut choices = vec![ChoiceItem::with_description(
|
|
3331
|
+
ADD_ROW,
|
|
3332
|
+
"Connect a new MCP server (catalog, remote SSE, or local stdio)",
|
|
3333
|
+
ADD_ROW,
|
|
3334
|
+
)];
|
|
3023
3335
|
let mut names = Vec::new();
|
|
3024
|
-
let width = servers.keys().map(|k| k.len()).max().unwrap_or(6) + 2;
|
|
3025
3336
|
for (name, srv) in &servers {
|
|
3026
3337
|
let tools = allowed
|
|
3027
3338
|
.as_ref()
|
|
3028
3339
|
.and_then(|m| m.get(name))
|
|
3029
3340
|
.and_then(|v| v.as_array());
|
|
3030
3341
|
let label = match tools {
|
|
3031
|
-
Some(a) if a.iter().any(|t| t.as_str() == Some("*")) =>
|
|
3032
|
-
format!("{MINT}[tools: *]{RESET}")
|
|
3033
|
-
}
|
|
3342
|
+
Some(a) if a.iter().any(|t| t.as_str() == Some("*")) => "[tools: *]".to_string(),
|
|
3034
3343
|
Some(a) if !a.is_empty() => format!(
|
|
3035
|
-
"
|
|
3344
|
+
"[tools: {}]",
|
|
3036
3345
|
a.iter()
|
|
3037
3346
|
.filter_map(|t| t.as_str())
|
|
3038
3347
|
.collect::<Vec<_>>()
|
|
3039
3348
|
.join(", ")
|
|
3040
3349
|
),
|
|
3041
|
-
_ =>
|
|
3042
|
-
};
|
|
3043
|
-
let disabled = if srv.disabled {
|
|
3044
|
-
format!(" {DIM}(disabled){RESET}")
|
|
3045
|
-
} else {
|
|
3046
|
-
String::new()
|
|
3350
|
+
_ => "[no tools allowed]".to_string(),
|
|
3047
3351
|
};
|
|
3352
|
+
let disabled = if srv.disabled { " (disabled)" } else { "" };
|
|
3048
3353
|
let desc = if let Some(url) = srv.remote_url() {
|
|
3049
3354
|
format!("(url: {url})")
|
|
3050
3355
|
} else {
|
|
3051
3356
|
format!("({} {})", srv.command, srv.args.join(" "))
|
|
3052
3357
|
};
|
|
3053
|
-
choices.push(
|
|
3054
|
-
|
|
3358
|
+
choices.push(ChoiceItem::with_description(
|
|
3359
|
+
name.clone(),
|
|
3360
|
+
format!("{label}{disabled} {desc}"),
|
|
3361
|
+
name.clone(),
|
|
3055
3362
|
));
|
|
3056
3363
|
names.push(name.clone());
|
|
3057
3364
|
}
|
|
3058
|
-
choices.push(
|
|
3059
|
-
|
|
3060
|
-
|
|
3365
|
+
choices.push(ChoiceItem::with_description(
|
|
3366
|
+
CANCEL,
|
|
3367
|
+
"Close this menu",
|
|
3368
|
+
CANCEL,
|
|
3369
|
+
));
|
|
3370
|
+
|
|
3371
|
+
let Ok(Some(idx)) = ui.prompt_choice(
|
|
3372
|
+
"MCP Servers",
|
|
3373
|
+
"Select a server to manage or add a new one",
|
|
3374
|
+
&choices,
|
|
3375
|
+
) else {
|
|
3061
3376
|
return;
|
|
3062
3377
|
};
|
|
3378
|
+
let pick = &choices[idx].label;
|
|
3063
3379
|
if pick == CANCEL {
|
|
3064
3380
|
return;
|
|
3065
3381
|
}
|
|
3066
3382
|
if pick == ADD_ROW {
|
|
3067
|
-
mcp_add_flow(session);
|
|
3383
|
+
mcp_add_flow(session, ui);
|
|
3068
3384
|
return;
|
|
3069
3385
|
}
|
|
3070
|
-
let
|
|
3071
|
-
return;
|
|
3072
|
-
};
|
|
3073
|
-
let name = names[pos - 1].clone();
|
|
3386
|
+
let name = names[idx - 1].clone();
|
|
3074
3387
|
let disabled = servers.get(&name).map(|s| s.disabled).unwrap_or(false);
|
|
3075
|
-
mcp_server_actions(session, &name, disabled);
|
|
3388
|
+
mcp_server_actions(session, ui, &name, disabled);
|
|
3076
3389
|
}
|
|
3077
3390
|
|
|
3078
|
-
fn mcp_add_flow(session: &mut InteractiveSession) {
|
|
3391
|
+
fn mcp_add_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3079
3392
|
let choices = vec![
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3393
|
+
ChoiceItem::with_description(
|
|
3394
|
+
"From catalog",
|
|
3395
|
+
"Install from known MCP tools catalog",
|
|
3396
|
+
"catalog",
|
|
3397
|
+
),
|
|
3398
|
+
ChoiceItem::with_description(
|
|
3399
|
+
"Remote URL (SSE)",
|
|
3400
|
+
"Connect to remote HTTP/SSE MCP endpoint",
|
|
3401
|
+
"remote",
|
|
3402
|
+
),
|
|
3403
|
+
ChoiceItem::with_description(
|
|
3404
|
+
"Local Command (stdio)",
|
|
3405
|
+
"Run local command (e.g. npx, uvx)",
|
|
3406
|
+
"local",
|
|
3407
|
+
),
|
|
3083
3408
|
];
|
|
3084
|
-
match
|
|
3085
|
-
Ok(Some(
|
|
3086
|
-
Ok(Some(
|
|
3087
|
-
Ok(Some(
|
|
3409
|
+
match ui.prompt_choice("Add MCP server", "Select server type", &choices) {
|
|
3410
|
+
Ok(Some(0)) => mcp_add_from_catalog(session, ui),
|
|
3411
|
+
Ok(Some(1)) => mcp_add_remote_flow(session, ui),
|
|
3412
|
+
Ok(Some(2)) => mcp_add_custom_flow(session, ui),
|
|
3088
3413
|
_ => {}
|
|
3089
3414
|
}
|
|
3090
3415
|
}
|
|
3091
3416
|
|
|
3092
|
-
fn mcp_add_from_catalog(session: &mut InteractiveSession) {
|
|
3417
|
+
fn mcp_add_from_catalog(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3093
3418
|
let entries = crate::mcp::registry();
|
|
3094
|
-
let
|
|
3419
|
+
let choices: Vec<ChoiceItem> = entries
|
|
3095
3420
|
.iter()
|
|
3096
3421
|
.map(|e| {
|
|
3097
3422
|
let needs = if e.required_env.is_empty() {
|
|
3098
3423
|
String::new()
|
|
3099
3424
|
} else {
|
|
3100
|
-
|
|
3425
|
+
" (needs a key)".to_string()
|
|
3101
3426
|
};
|
|
3102
|
-
format!("{
|
|
3427
|
+
ChoiceItem::with_description(&e.name, format!("{}{needs}", e.desc), &e.key)
|
|
3103
3428
|
})
|
|
3104
3429
|
.collect();
|
|
3105
|
-
let Ok(Some(
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
.iter()
|
|
3111
|
-
.position(|l| *l == picked)
|
|
3112
|
-
.map(|i| &entries[i])
|
|
3113
|
-
else {
|
|
3430
|
+
let Ok(Some(idx)) = ui.prompt_choice(
|
|
3431
|
+
"MCP server catalog",
|
|
3432
|
+
"Select an MCP server from catalog",
|
|
3433
|
+
&choices,
|
|
3434
|
+
) else {
|
|
3114
3435
|
return;
|
|
3115
3436
|
};
|
|
3437
|
+
let entry = &entries[idx];
|
|
3116
3438
|
|
|
3117
3439
|
// One prompt per declared argInput (path / connection string / …).
|
|
3118
3440
|
let mut extra_args = Vec::new();
|
|
@@ -3122,10 +3444,14 @@ fn mcp_add_from_catalog(session: &mut InteractiveSession) {
|
|
|
3122
3444
|
} else {
|
|
3123
3445
|
format!("{} (e.g. {})", input.label, input.placeholder)
|
|
3124
3446
|
};
|
|
3125
|
-
let value =
|
|
3447
|
+
let value = ui
|
|
3448
|
+
.prompt_text(&entry.name, &hint, None)
|
|
3449
|
+
.ok()
|
|
3450
|
+
.flatten()
|
|
3451
|
+
.unwrap_or_default();
|
|
3126
3452
|
let value = value.trim();
|
|
3127
3453
|
if value.is_empty() {
|
|
3128
|
-
|
|
3454
|
+
ui.push_notice(format!("Cancelled ('{}' is required).", input.label));
|
|
3129
3455
|
return;
|
|
3130
3456
|
}
|
|
3131
3457
|
extra_args.push(value.to_string());
|
|
@@ -3134,65 +3460,96 @@ fn mcp_add_from_catalog(session: &mut InteractiveSession) {
|
|
|
3134
3460
|
// One prompt per required env var.
|
|
3135
3461
|
let mut env = Vec::new();
|
|
3136
3462
|
for var in &entry.required_env {
|
|
3137
|
-
if let Some(help) = &var.help {
|
|
3138
|
-
|
|
3139
|
-
}
|
|
3140
|
-
|
|
3463
|
+
let prompt_body = if let Some(help) = &var.help {
|
|
3464
|
+
format!("{} (get one at {help})", var.label)
|
|
3465
|
+
} else {
|
|
3466
|
+
var.label.clone()
|
|
3467
|
+
};
|
|
3468
|
+
let value = ui
|
|
3469
|
+
.prompt_text(&entry.name, &prompt_body, None)
|
|
3470
|
+
.ok()
|
|
3471
|
+
.flatten()
|
|
3472
|
+
.unwrap_or_default();
|
|
3141
3473
|
let value = value.trim();
|
|
3142
3474
|
if value.is_empty() {
|
|
3143
|
-
|
|
3475
|
+
ui.push_notice(format!("Cancelled ('{}' is required).", var.label));
|
|
3144
3476
|
return;
|
|
3145
3477
|
}
|
|
3146
3478
|
env.push(format!("{}={value}", var.key));
|
|
3147
3479
|
}
|
|
3148
3480
|
|
|
3149
|
-
let name =
|
|
3481
|
+
let name = ui
|
|
3482
|
+
.prompt_text(
|
|
3483
|
+
"Server name",
|
|
3484
|
+
"Choose a unique name for this MCP server",
|
|
3485
|
+
Some(&entry.key),
|
|
3486
|
+
)
|
|
3487
|
+
.ok()
|
|
3488
|
+
.flatten()
|
|
3489
|
+
.unwrap_or_default();
|
|
3150
3490
|
let name = if name.trim().is_empty() {
|
|
3151
3491
|
entry.key.clone()
|
|
3152
3492
|
} else {
|
|
3153
3493
|
name.trim().to_string()
|
|
3154
3494
|
};
|
|
3155
3495
|
|
|
3156
|
-
let allow_all =
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3496
|
+
let allow_all = ui
|
|
3497
|
+
.prompt_confirm(
|
|
3498
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3499
|
+
false,
|
|
3500
|
+
)
|
|
3501
|
+
.unwrap_or(false);
|
|
3160
3502
|
|
|
3161
3503
|
match crate::mcp::registry_add(&entry.key, Some(&name), extra_args, env, allow_all) {
|
|
3162
3504
|
Ok(saved) => {
|
|
3163
|
-
|
|
3164
|
-
"
|
|
3505
|
+
ui.push_notice(format!(
|
|
3506
|
+
"Added MCP server '{saved}'{}.",
|
|
3165
3507
|
if allow_all {
|
|
3166
3508
|
" (all tools allowed)"
|
|
3167
3509
|
} else {
|
|
3168
3510
|
""
|
|
3169
3511
|
}
|
|
3170
|
-
);
|
|
3512
|
+
));
|
|
3171
3513
|
reload_session_config(session);
|
|
3172
3514
|
}
|
|
3173
|
-
Err(e) =>
|
|
3515
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3174
3516
|
}
|
|
3175
3517
|
}
|
|
3176
3518
|
|
|
3177
|
-
fn mcp_add_remote_flow(session: &mut InteractiveSession) {
|
|
3178
|
-
let name =
|
|
3519
|
+
fn mcp_add_remote_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3520
|
+
let name = ui
|
|
3521
|
+
.prompt_text("Remote MCP Server", "Server name", None)
|
|
3522
|
+
.ok()
|
|
3523
|
+
.flatten()
|
|
3524
|
+
.unwrap_or_default();
|
|
3179
3525
|
let name = name.trim().to_string();
|
|
3180
3526
|
if name.is_empty() {
|
|
3181
|
-
|
|
3527
|
+
ui.push_notice("Cancelled (no name).");
|
|
3182
3528
|
return;
|
|
3183
3529
|
}
|
|
3184
|
-
let url =
|
|
3530
|
+
let url = ui
|
|
3531
|
+
.prompt_text(
|
|
3532
|
+
"Remote MCP Server",
|
|
3533
|
+
"Server URL (e.g. https://example.com/sse)",
|
|
3534
|
+
None,
|
|
3535
|
+
)
|
|
3536
|
+
.ok()
|
|
3537
|
+
.flatten()
|
|
3185
3538
|
.unwrap_or_default();
|
|
3186
3539
|
let url = url.trim().to_string();
|
|
3187
3540
|
if url.is_empty() {
|
|
3188
|
-
|
|
3541
|
+
ui.push_notice("Cancelled (no URL).");
|
|
3189
3542
|
return;
|
|
3190
3543
|
}
|
|
3191
|
-
let auth =
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3544
|
+
let auth = ui
|
|
3545
|
+
.prompt_text(
|
|
3546
|
+
"Remote MCP Server",
|
|
3547
|
+
"Auth header or bearer token (blank for none, e.g. Bearer token)",
|
|
3548
|
+
None,
|
|
3549
|
+
)
|
|
3550
|
+
.ok()
|
|
3551
|
+
.flatten()
|
|
3552
|
+
.unwrap_or_default();
|
|
3196
3553
|
let headers = if auth.trim().is_empty() {
|
|
3197
3554
|
None
|
|
3198
3555
|
} else {
|
|
@@ -3212,46 +3569,65 @@ fn mcp_add_remote_flow(session: &mut InteractiveSession) {
|
|
|
3212
3569
|
|
|
3213
3570
|
match crate::mcp::add_remote(&name, &url, headers) {
|
|
3214
3571
|
Ok(()) => {
|
|
3215
|
-
let allow_all =
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3572
|
+
let allow_all = ui
|
|
3573
|
+
.prompt_confirm(
|
|
3574
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3575
|
+
false,
|
|
3576
|
+
)
|
|
3577
|
+
.unwrap_or(false);
|
|
3219
3578
|
if allow_all {
|
|
3220
3579
|
let _ = crate::mcp::allow(&name, "*");
|
|
3221
3580
|
}
|
|
3222
|
-
|
|
3223
|
-
"
|
|
3581
|
+
ui.push_notice(format!(
|
|
3582
|
+
"Added Remote MCP server '{name}'{}.",
|
|
3224
3583
|
if allow_all {
|
|
3225
3584
|
" (all tools allowed)"
|
|
3226
3585
|
} else {
|
|
3227
3586
|
""
|
|
3228
3587
|
}
|
|
3229
|
-
);
|
|
3588
|
+
));
|
|
3230
3589
|
reload_session_config(session);
|
|
3231
3590
|
}
|
|
3232
|
-
Err(e) =>
|
|
3591
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3233
3592
|
}
|
|
3234
3593
|
}
|
|
3235
3594
|
|
|
3236
|
-
fn mcp_add_custom_flow(session: &mut InteractiveSession) {
|
|
3237
|
-
let name =
|
|
3595
|
+
fn mcp_add_custom_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi) {
|
|
3596
|
+
let name = ui
|
|
3597
|
+
.prompt_text("Custom MCP Server", "Server name", None)
|
|
3598
|
+
.ok()
|
|
3599
|
+
.flatten()
|
|
3600
|
+
.unwrap_or_default();
|
|
3238
3601
|
let name = name.trim().to_string();
|
|
3239
3602
|
if name.is_empty() {
|
|
3240
|
-
|
|
3603
|
+
ui.push_notice("Cancelled (no name).");
|
|
3241
3604
|
return;
|
|
3242
3605
|
}
|
|
3243
|
-
let command =
|
|
3606
|
+
let command = ui
|
|
3607
|
+
.prompt_text(
|
|
3608
|
+
"Custom MCP Server",
|
|
3609
|
+
"Command or URL (e.g. npx or https://...)",
|
|
3610
|
+
None,
|
|
3611
|
+
)
|
|
3612
|
+
.ok()
|
|
3613
|
+
.flatten()
|
|
3244
3614
|
.unwrap_or_default();
|
|
3245
3615
|
let command = command.trim().to_string();
|
|
3246
3616
|
if command.is_empty() {
|
|
3247
|
-
|
|
3617
|
+
ui.push_notice("Cancelled (no command).");
|
|
3248
3618
|
return;
|
|
3249
3619
|
}
|
|
3250
3620
|
|
|
3251
3621
|
if command.starts_with("http://") || command.starts_with("https://") {
|
|
3252
|
-
let auth =
|
|
3253
|
-
|
|
3254
|
-
|
|
3622
|
+
let auth = ui
|
|
3623
|
+
.prompt_text(
|
|
3624
|
+
"Remote MCP Server",
|
|
3625
|
+
"Auth header or bearer token (blank for none)",
|
|
3626
|
+
None,
|
|
3627
|
+
)
|
|
3628
|
+
.ok()
|
|
3629
|
+
.flatten()
|
|
3630
|
+
.unwrap_or_default();
|
|
3255
3631
|
let headers = if auth.trim().is_empty() {
|
|
3256
3632
|
None
|
|
3257
3633
|
} else {
|
|
@@ -3270,35 +3646,48 @@ fn mcp_add_custom_flow(session: &mut InteractiveSession) {
|
|
|
3270
3646
|
};
|
|
3271
3647
|
match crate::mcp::add_remote(&name, &command, headers) {
|
|
3272
3648
|
Ok(()) => {
|
|
3273
|
-
let allow_all =
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3649
|
+
let allow_all = ui
|
|
3650
|
+
.prompt_confirm(
|
|
3651
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3652
|
+
false,
|
|
3653
|
+
)
|
|
3654
|
+
.unwrap_or(false);
|
|
3277
3655
|
if allow_all {
|
|
3278
3656
|
let _ = crate::mcp::allow(&name, "*");
|
|
3279
3657
|
}
|
|
3280
|
-
|
|
3281
|
-
"
|
|
3658
|
+
ui.push_notice(format!(
|
|
3659
|
+
"Added Remote MCP server '{name}'{}.",
|
|
3282
3660
|
if allow_all {
|
|
3283
3661
|
" (all tools allowed)"
|
|
3284
3662
|
} else {
|
|
3285
3663
|
""
|
|
3286
3664
|
}
|
|
3287
|
-
);
|
|
3665
|
+
));
|
|
3288
3666
|
reload_session_config(session);
|
|
3289
3667
|
}
|
|
3290
|
-
Err(e) =>
|
|
3668
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3291
3669
|
}
|
|
3292
3670
|
return;
|
|
3293
3671
|
}
|
|
3294
3672
|
|
|
3295
|
-
let args =
|
|
3673
|
+
let args = ui
|
|
3674
|
+
.prompt_text(
|
|
3675
|
+
"Custom MCP Server",
|
|
3676
|
+
"Arguments (space-separated, blank for none)",
|
|
3677
|
+
None,
|
|
3678
|
+
)
|
|
3679
|
+
.ok()
|
|
3680
|
+
.flatten()
|
|
3681
|
+
.unwrap_or_default();
|
|
3682
|
+
let env_raw = ui
|
|
3683
|
+
.prompt_text(
|
|
3684
|
+
"Custom MCP Server",
|
|
3685
|
+
"Env vars (KEY=VALUE, comma-separated, blank for none)",
|
|
3686
|
+
None,
|
|
3687
|
+
)
|
|
3688
|
+
.ok()
|
|
3689
|
+
.flatten()
|
|
3296
3690
|
.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
3691
|
|
|
3303
3692
|
let args: Vec<String> = args.split_whitespace().map(str::to_string).collect();
|
|
3304
3693
|
let env: Vec<String> = env_raw
|
|
@@ -3310,79 +3699,115 @@ fn mcp_add_custom_flow(session: &mut InteractiveSession) {
|
|
|
3310
3699
|
|
|
3311
3700
|
match crate::mcp::add(&name, &command, args, env) {
|
|
3312
3701
|
Ok(()) => {
|
|
3313
|
-
let allow_all =
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3702
|
+
let allow_all = ui
|
|
3703
|
+
.prompt_confirm(
|
|
3704
|
+
"Allow the agent to call all of this server's tools now? (else approve them one by one)",
|
|
3705
|
+
false,
|
|
3706
|
+
)
|
|
3707
|
+
.unwrap_or(false);
|
|
3317
3708
|
if allow_all {
|
|
3318
3709
|
let _ = crate::mcp::allow(&name, "*");
|
|
3319
3710
|
}
|
|
3320
|
-
|
|
3321
|
-
"
|
|
3711
|
+
ui.push_notice(format!(
|
|
3712
|
+
"Added MCP server '{name}'{}.",
|
|
3322
3713
|
if allow_all {
|
|
3323
3714
|
" (all tools allowed)"
|
|
3324
3715
|
} else {
|
|
3325
3716
|
""
|
|
3326
3717
|
}
|
|
3327
|
-
);
|
|
3718
|
+
));
|
|
3328
3719
|
reload_session_config(session);
|
|
3329
3720
|
}
|
|
3330
|
-
Err(e) =>
|
|
3721
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3331
3722
|
}
|
|
3332
3723
|
}
|
|
3333
3724
|
|
|
3334
|
-
fn mcp_server_actions(
|
|
3725
|
+
fn mcp_server_actions(
|
|
3726
|
+
session: &mut InteractiveSession,
|
|
3727
|
+
ui: &mut dyn CommandUi,
|
|
3728
|
+
name: &str,
|
|
3729
|
+
disabled: bool,
|
|
3730
|
+
) {
|
|
3335
3731
|
let toggle_label = if disabled {
|
|
3336
3732
|
"Enable server"
|
|
3337
3733
|
} else {
|
|
3338
3734
|
"Disable server"
|
|
3339
3735
|
};
|
|
3340
3736
|
let actions = vec![
|
|
3341
|
-
"Check connection"
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3737
|
+
ChoiceItem::with_description("Check connection", "Test tool discovery", "check"),
|
|
3738
|
+
ChoiceItem::with_description(
|
|
3739
|
+
"Allow all tools (*)",
|
|
3740
|
+
"Grant unrestricted tool call access",
|
|
3741
|
+
"allow_all",
|
|
3742
|
+
),
|
|
3743
|
+
ChoiceItem::with_description(
|
|
3744
|
+
"Allow a specific tool",
|
|
3745
|
+
"Whitelist an individual tool name",
|
|
3746
|
+
"allow_tool",
|
|
3747
|
+
),
|
|
3748
|
+
ChoiceItem::with_description(
|
|
3749
|
+
"Disallow a tool",
|
|
3750
|
+
"Remove a tool from allowlist",
|
|
3751
|
+
"disallow_tool",
|
|
3752
|
+
),
|
|
3753
|
+
ChoiceItem::with_description(
|
|
3754
|
+
"Re-authenticate (OAuth)",
|
|
3755
|
+
"Trigger browser OAuth login flow",
|
|
3756
|
+
"reauth",
|
|
3757
|
+
),
|
|
3758
|
+
ChoiceItem::with_description(toggle_label, "Toggle server active status", "toggle"),
|
|
3759
|
+
ChoiceItem::with_description(
|
|
3760
|
+
"Edit (command / args / icon)",
|
|
3761
|
+
"Update server settings",
|
|
3762
|
+
"edit",
|
|
3763
|
+
),
|
|
3764
|
+
ChoiceItem::with_description("Remove server", "Delete server from config", "remove"),
|
|
3765
|
+
ChoiceItem::with_description("Back", "Return to MCP servers list", "back"),
|
|
3350
3766
|
];
|
|
3351
|
-
let Ok(Some(
|
|
3352
|
-
prompt_interactive_select(&format!("MCP: {name}"), &actions, &actions[0])
|
|
3353
|
-
else {
|
|
3767
|
+
let Ok(Some(idx)) = ui.prompt_choice(&format!("MCP: {name}"), "Choose action", &actions) else {
|
|
3354
3768
|
return;
|
|
3355
3769
|
};
|
|
3356
3770
|
|
|
3357
|
-
match
|
|
3358
|
-
"
|
|
3359
|
-
Ok(_) =>
|
|
3360
|
-
Err(e) =>
|
|
3771
|
+
match actions[idx].value.as_str() {
|
|
3772
|
+
"check" => match mint_core::list_server_tools(&session.config, name) {
|
|
3773
|
+
Ok(_) => ui.push_notice("● Connected"),
|
|
3774
|
+
Err(e) => ui.push_notice(format!("● Not connected: {e}")),
|
|
3361
3775
|
},
|
|
3362
|
-
"
|
|
3776
|
+
"allow_all" => match crate::mcp::allow(name, "*") {
|
|
3363
3777
|
Ok(_) => {
|
|
3364
|
-
|
|
3778
|
+
ui.push_notice(format!("Allowed all tools for '{name}'."));
|
|
3365
3779
|
reload_session_config(session);
|
|
3366
3780
|
}
|
|
3367
|
-
Err(e) =>
|
|
3781
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3368
3782
|
},
|
|
3369
|
-
"
|
|
3370
|
-
let tool =
|
|
3783
|
+
"allow_tool" => {
|
|
3784
|
+
let tool = ui
|
|
3785
|
+
.prompt_text(&format!("MCP: {name}"), "Tool name", None)
|
|
3786
|
+
.ok()
|
|
3787
|
+
.flatten()
|
|
3788
|
+
.unwrap_or_default();
|
|
3371
3789
|
let tool = tool.trim();
|
|
3372
3790
|
if tool.is_empty() {
|
|
3373
3791
|
return;
|
|
3374
3792
|
}
|
|
3375
3793
|
match crate::mcp::allow(name, tool) {
|
|
3376
3794
|
Ok(true) => {
|
|
3377
|
-
|
|
3795
|
+
ui.push_notice(format!("Allowed {name}/{tool}."));
|
|
3378
3796
|
reload_session_config(session);
|
|
3379
3797
|
}
|
|
3380
|
-
Ok(false) =>
|
|
3381
|
-
Err(e) =>
|
|
3798
|
+
Ok(false) => ui.push_notice(format!("{name}/{tool} was already allowed.")),
|
|
3799
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3382
3800
|
}
|
|
3383
3801
|
}
|
|
3384
|
-
"
|
|
3385
|
-
let tool =
|
|
3802
|
+
"disallow_tool" => {
|
|
3803
|
+
let tool = ui
|
|
3804
|
+
.prompt_text(
|
|
3805
|
+
&format!("MCP: {name}"),
|
|
3806
|
+
"Tool to remove (* to clear all)",
|
|
3807
|
+
None,
|
|
3808
|
+
)
|
|
3809
|
+
.ok()
|
|
3810
|
+
.flatten()
|
|
3386
3811
|
.unwrap_or_default();
|
|
3387
3812
|
let tool = tool.trim();
|
|
3388
3813
|
if tool.is_empty() {
|
|
@@ -3390,42 +3815,45 @@ fn mcp_server_actions(session: &mut InteractiveSession, name: &str, disabled: bo
|
|
|
3390
3815
|
}
|
|
3391
3816
|
match crate::mcp::disallow(name, tool) {
|
|
3392
3817
|
Ok(true) => {
|
|
3393
|
-
|
|
3818
|
+
ui.push_notice(format!("Removed {name}/{tool} from the allowlist."));
|
|
3394
3819
|
reload_session_config(session);
|
|
3395
3820
|
}
|
|
3396
|
-
Ok(false) =>
|
|
3397
|
-
Err(e) =>
|
|
3821
|
+
Ok(false) => ui.push_notice(format!("{name}/{tool} was not in the allowlist.")),
|
|
3822
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3398
3823
|
}
|
|
3399
3824
|
}
|
|
3400
|
-
"
|
|
3401
|
-
|
|
3825
|
+
"reauth" => {
|
|
3826
|
+
ui.push_notice(format!(
|
|
3827
|
+
"Re-authenticating '{name}'... (a browser tab may open)"
|
|
3828
|
+
));
|
|
3402
3829
|
match crate::mcp::reauth(name) {
|
|
3403
|
-
Ok(true) =>
|
|
3404
|
-
Ok(false) =>
|
|
3405
|
-
|
|
3406
|
-
}
|
|
3407
|
-
Err(e) => println!("{ERROR}MCP error:{RESET} {e}\n"),
|
|
3830
|
+
Ok(true) => ui.push_notice("Re-authentication succeeded."),
|
|
3831
|
+
Ok(false) => ui.push_notice("Re-authentication failed (see output above)."),
|
|
3832
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3408
3833
|
}
|
|
3409
3834
|
}
|
|
3410
|
-
|
|
3835
|
+
"toggle" => match crate::mcp::set_disabled(name, !disabled) {
|
|
3411
3836
|
Ok(_) => {
|
|
3412
|
-
|
|
3413
|
-
"{
|
|
3837
|
+
ui.push_notice(format!(
|
|
3838
|
+
"{} '{name}'.",
|
|
3414
3839
|
if disabled { "Enabled" } else { "Disabled" }
|
|
3415
|
-
);
|
|
3840
|
+
));
|
|
3416
3841
|
reload_session_config(session);
|
|
3417
3842
|
}
|
|
3418
|
-
Err(e) =>
|
|
3843
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3419
3844
|
},
|
|
3420
|
-
"
|
|
3421
|
-
"
|
|
3422
|
-
if
|
|
3845
|
+
"edit" => mcp_edit_flow(session, ui, name),
|
|
3846
|
+
"remove" => {
|
|
3847
|
+
if ui
|
|
3848
|
+
.prompt_confirm(&format!("Remove MCP server '{name}'?"), false)
|
|
3849
|
+
.unwrap_or(false)
|
|
3850
|
+
{
|
|
3423
3851
|
match crate::mcp::remove(name) {
|
|
3424
3852
|
Ok(_) => {
|
|
3425
|
-
|
|
3853
|
+
ui.push_notice(format!("Removed '{name}'."));
|
|
3426
3854
|
reload_session_config(session);
|
|
3427
3855
|
}
|
|
3428
|
-
Err(e) =>
|
|
3856
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3429
3857
|
}
|
|
3430
3858
|
}
|
|
3431
3859
|
}
|
|
@@ -3433,25 +3861,44 @@ fn mcp_server_actions(session: &mut InteractiveSession, name: &str, disabled: bo
|
|
|
3433
3861
|
}
|
|
3434
3862
|
}
|
|
3435
3863
|
|
|
3436
|
-
fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
3864
|
+
fn mcp_edit_flow(session: &mut InteractiveSession, ui: &mut dyn CommandUi, name: &str) {
|
|
3437
3865
|
let Some(srv) = crate::mcp::list().ok().and_then(|m| m.get(name).cloned()) else {
|
|
3438
|
-
|
|
3866
|
+
ui.push_notice(format!("No MCP server named '{name}'."));
|
|
3439
3867
|
return;
|
|
3440
3868
|
};
|
|
3441
3869
|
let fields = vec![
|
|
3442
|
-
"command".
|
|
3443
|
-
"args".
|
|
3444
|
-
"icon".
|
|
3445
|
-
|
|
3870
|
+
ChoiceItem::with_description("command", &srv.command, "command"),
|
|
3871
|
+
ChoiceItem::with_description("args", srv.args.join(" "), "args"),
|
|
3872
|
+
ChoiceItem::with_description("icon", srv.icon.as_deref().unwrap_or("(none)"), "icon"),
|
|
3873
|
+
ChoiceItem::with_description(
|
|
3874
|
+
"Tool timeout",
|
|
3875
|
+
format!("{} seconds", srv.timeout_secs.unwrap_or(300)),
|
|
3876
|
+
"timeout",
|
|
3877
|
+
),
|
|
3878
|
+
ChoiceItem::with_description("Back", "Cancel edit", "back"),
|
|
3446
3879
|
];
|
|
3447
|
-
let Ok(Some(
|
|
3880
|
+
let Ok(Some(idx)) = ui.prompt_choice(&format!("Edit {name}"), "Select field to edit", &fields)
|
|
3448
3881
|
else {
|
|
3449
3882
|
return;
|
|
3450
3883
|
};
|
|
3451
|
-
let result = match
|
|
3884
|
+
let result = match fields[idx].value.as_str() {
|
|
3885
|
+
"timeout" => {
|
|
3886
|
+
let current = srv.timeout_secs.unwrap_or(300).to_string();
|
|
3887
|
+
let Ok(Some(value)) = ui.prompt_text(
|
|
3888
|
+
&format!("Edit {name}"),
|
|
3889
|
+
"Tool timeout (seconds, 1–3600)",
|
|
3890
|
+
Some(¤t),
|
|
3891
|
+
) else {
|
|
3892
|
+
return;
|
|
3893
|
+
};
|
|
3894
|
+
crate::mcp::set_timeout(name, &value)
|
|
3895
|
+
}
|
|
3452
3896
|
"command" => {
|
|
3453
|
-
let value =
|
|
3454
|
-
|
|
3897
|
+
let value = ui
|
|
3898
|
+
.prompt_text(&format!("Edit {name}"), "Command", Some(&srv.command))
|
|
3899
|
+
.ok()
|
|
3900
|
+
.flatten()
|
|
3901
|
+
.unwrap_or_default();
|
|
3455
3902
|
let value = value.trim();
|
|
3456
3903
|
if value.is_empty() {
|
|
3457
3904
|
return;
|
|
@@ -3460,7 +3907,14 @@ fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
|
3460
3907
|
}
|
|
3461
3908
|
"args" => {
|
|
3462
3909
|
let current = srv.args.join(" ");
|
|
3463
|
-
let value =
|
|
3910
|
+
let value = ui
|
|
3911
|
+
.prompt_text(
|
|
3912
|
+
&format!("Edit {name}"),
|
|
3913
|
+
"Arguments (space-separated)",
|
|
3914
|
+
Some(¤t),
|
|
3915
|
+
)
|
|
3916
|
+
.ok()
|
|
3917
|
+
.flatten()
|
|
3464
3918
|
.unwrap_or_default();
|
|
3465
3919
|
crate::mcp::edit(
|
|
3466
3920
|
name,
|
|
@@ -3472,7 +3926,14 @@ fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
|
3472
3926
|
}
|
|
3473
3927
|
"icon" => {
|
|
3474
3928
|
let current = srv.icon.clone().unwrap_or_default();
|
|
3475
|
-
let value =
|
|
3929
|
+
let value = ui
|
|
3930
|
+
.prompt_text(
|
|
3931
|
+
&format!("Edit {name}"),
|
|
3932
|
+
"Icon (blank to clear)",
|
|
3933
|
+
Some(¤t),
|
|
3934
|
+
)
|
|
3935
|
+
.ok()
|
|
3936
|
+
.flatten()
|
|
3476
3937
|
.unwrap_or_default();
|
|
3477
3938
|
let value = value.trim();
|
|
3478
3939
|
crate::mcp::edit(
|
|
@@ -3487,10 +3948,10 @@ fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
|
3487
3948
|
};
|
|
3488
3949
|
match result {
|
|
3489
3950
|
Ok(_) => {
|
|
3490
|
-
|
|
3951
|
+
ui.push_notice(format!("Updated '{name}'."));
|
|
3491
3952
|
reload_session_config(session);
|
|
3492
3953
|
}
|
|
3493
|
-
Err(e) =>
|
|
3954
|
+
Err(e) => ui.push_notice(format!("MCP error: {e}")),
|
|
3494
3955
|
}
|
|
3495
3956
|
}
|
|
3496
3957
|
|
|
@@ -3498,9 +3959,114 @@ fn mcp_edit_flow(session: &mut InteractiveSession, name: &str) {
|
|
|
3498
3959
|
mod tests {
|
|
3499
3960
|
use super::*;
|
|
3500
3961
|
|
|
3962
|
+
#[test]
|
|
3963
|
+
fn review_tui_timeout_edit_persists_and_invalid_or_cancel_preserves_it() {
|
|
3964
|
+
if std::env::var_os("MINT_TIMEOUT_REVIEW_CHILD").is_none() {
|
|
3965
|
+
let dir = std::env::temp_dir().join(format!("mint-tui-timeout-{}", std::process::id()));
|
|
3966
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
3967
|
+
let path = dir.join("config.json");
|
|
3968
|
+
let mut cfg = MintConfig::default();
|
|
3969
|
+
cfg.extra.insert(
|
|
3970
|
+
"mcpServers".into(),
|
|
3971
|
+
serde_json::json!({"timer":{"command":"cat"}}),
|
|
3972
|
+
);
|
|
3973
|
+
std::fs::write(&path, serde_json::to_vec(&cfg).unwrap()).unwrap();
|
|
3974
|
+
let output=std::process::Command::new(std::env::current_exe().unwrap())
|
|
3975
|
+
.args(["--exact","interactive::slash_commands::tests::review_tui_timeout_edit_persists_and_invalid_or_cancel_preserves_it","--nocapture"])
|
|
3976
|
+
.env("MINT_TIMEOUT_REVIEW_CHILD","1").env("MINT_CONFIG_PATH",&path).output().unwrap();
|
|
3977
|
+
std::fs::remove_dir_all(dir).unwrap();
|
|
3978
|
+
assert!(
|
|
3979
|
+
output.status.success(),
|
|
3980
|
+
"{}\n{}",
|
|
3981
|
+
String::from_utf8_lossy(&output.stdout),
|
|
3982
|
+
String::from_utf8_lossy(&output.stderr)
|
|
3983
|
+
);
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
3986
|
+
struct TimeoutUi {
|
|
3987
|
+
answer: Option<String>,
|
|
3988
|
+
}
|
|
3989
|
+
impl CommandUi for TimeoutUi {
|
|
3990
|
+
fn push_notice_str(&mut self, _: &str) {}
|
|
3991
|
+
fn push_command_output_str(&mut self, _: &str) {}
|
|
3992
|
+
fn set_status(&mut self, _: Vec<String>) {}
|
|
3993
|
+
fn clear_status(&mut self) {}
|
|
3994
|
+
fn prompt_choice(
|
|
3995
|
+
&mut self,
|
|
3996
|
+
_: &str,
|
|
3997
|
+
_: &str,
|
|
3998
|
+
options: &[ChoiceItem],
|
|
3999
|
+
) -> anyhow::Result<Option<usize>> {
|
|
4000
|
+
Ok(options.iter().position(|o| o.value == "timeout"))
|
|
4001
|
+
}
|
|
4002
|
+
fn prompt_text(
|
|
4003
|
+
&mut self,
|
|
4004
|
+
_: &str,
|
|
4005
|
+
_: &str,
|
|
4006
|
+
_: Option<&str>,
|
|
4007
|
+
) -> anyhow::Result<Option<String>> {
|
|
4008
|
+
Ok(self.answer.take())
|
|
4009
|
+
}
|
|
4010
|
+
fn prompt_confirm(&mut self, _: &str, _: bool) -> anyhow::Result<bool> {
|
|
4011
|
+
Ok(false)
|
|
4012
|
+
}
|
|
4013
|
+
fn prompt_multi_choice(
|
|
4014
|
+
&mut self,
|
|
4015
|
+
_: &str,
|
|
4016
|
+
_: &str,
|
|
4017
|
+
_: &[ChoiceItem],
|
|
4018
|
+
) -> anyhow::Result<Option<Vec<usize>>> {
|
|
4019
|
+
Ok(None)
|
|
4020
|
+
}
|
|
4021
|
+
fn report_error(&mut self, _: &anyhow::Error) {}
|
|
4022
|
+
}
|
|
4023
|
+
let mut session = InteractiveSession {
|
|
4024
|
+
chat_id: mint_core::CHAT_CLI_ID.into(),
|
|
4025
|
+
config: mint_core::load_config().unwrap(),
|
|
4026
|
+
current_dir: std::env::current_dir().unwrap(),
|
|
4027
|
+
fast_mode: false,
|
|
4028
|
+
plan_mode: false,
|
|
4029
|
+
pending_image: None,
|
|
4030
|
+
history: Vec::new(),
|
|
4031
|
+
jobs: BackgroundJobs::new(),
|
|
4032
|
+
};
|
|
4033
|
+
for value in [1, 300, 3600] {
|
|
4034
|
+
mcp_edit_flow(
|
|
4035
|
+
&mut session,
|
|
4036
|
+
&mut TimeoutUi {
|
|
4037
|
+
answer: Some(value.to_string()),
|
|
4038
|
+
},
|
|
4039
|
+
"timer",
|
|
4040
|
+
);
|
|
4041
|
+
assert_eq!(
|
|
4042
|
+
session.config.extra["mcpServers"]["timer"]["timeoutSecs"],
|
|
4043
|
+
value
|
|
4044
|
+
);
|
|
4045
|
+
assert_eq!(
|
|
4046
|
+
mint_core::load_config().unwrap().extra["mcpServers"]["timer"]["timeoutSecs"],
|
|
4047
|
+
value
|
|
4048
|
+
);
|
|
4049
|
+
}
|
|
4050
|
+
let before = std::fs::read(mint_core::config_path().unwrap()).unwrap();
|
|
4051
|
+
for answer in [Some("0"), Some("3601"), Some("1.5"), Some("nope"), None] {
|
|
4052
|
+
mcp_edit_flow(
|
|
4053
|
+
&mut session,
|
|
4054
|
+
&mut TimeoutUi {
|
|
4055
|
+
answer: answer.map(str::to_string),
|
|
4056
|
+
},
|
|
4057
|
+
"timer",
|
|
4058
|
+
);
|
|
4059
|
+
assert_eq!(
|
|
4060
|
+
std::fs::read(mint_core::config_path().unwrap()).unwrap(),
|
|
4061
|
+
before
|
|
4062
|
+
);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
|
|
3501
4066
|
/// Commands that are dispatched but deliberately left undocumented —
|
|
3502
4067
|
/// shortcuts for another command's own token, not gaps in `SLASH_COMMANDS`.
|
|
3503
|
-
const UNDOCUMENTED_ALIASES: &[&str] =
|
|
4068
|
+
const UNDOCUMENTED_ALIASES: &[&str] =
|
|
4069
|
+
&["/quit", "/reset", "/plugin", "/searchProvider", "/think"];
|
|
3504
4070
|
|
|
3505
4071
|
/// Regression guard for the exact bug that motivated `SLASH_COMMANDS`:
|
|
3506
4072
|
/// `/edit-image`, `/gen-image`, `/shells`, and `/subagent` all worked but
|
|
@@ -3555,6 +4121,7 @@ mod tests {
|
|
|
3555
4121
|
#[tokio::test]
|
|
3556
4122
|
async fn core_slash_clear_clears_history() {
|
|
3557
4123
|
let mut session = InteractiveSession {
|
|
4124
|
+
chat_id: mint_core::CHAT_CLI_ID.to_string(),
|
|
3558
4125
|
config: MintConfig::default(),
|
|
3559
4126
|
current_dir: std::env::current_dir().unwrap(),
|
|
3560
4127
|
fast_mode: false,
|
|
@@ -3564,7 +4131,8 @@ mod tests {
|
|
|
3564
4131
|
jobs: BackgroundJobs::new(),
|
|
3565
4132
|
};
|
|
3566
4133
|
|
|
3567
|
-
let
|
|
4134
|
+
let mut ui = MockCommandUi::new();
|
|
4135
|
+
let res = handle_slash_command(&mut session, &mut ui, "/clear").await;
|
|
3568
4136
|
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
3569
4137
|
assert!(session.history.is_empty());
|
|
3570
4138
|
}
|
|
@@ -3572,6 +4140,7 @@ mod tests {
|
|
|
3572
4140
|
#[tokio::test]
|
|
3573
4141
|
async fn core_slash_fast_mode_toggle() {
|
|
3574
4142
|
let mut session = InteractiveSession {
|
|
4143
|
+
chat_id: mint_core::CHAT_CLI_ID.to_string(),
|
|
3575
4144
|
config: MintConfig::default(),
|
|
3576
4145
|
current_dir: std::env::current_dir().unwrap(),
|
|
3577
4146
|
fast_mode: false,
|
|
@@ -3581,7 +4150,8 @@ mod tests {
|
|
|
3581
4150
|
jobs: BackgroundJobs::new(),
|
|
3582
4151
|
};
|
|
3583
4152
|
|
|
3584
|
-
let
|
|
4153
|
+
let mut ui = MockCommandUi::new();
|
|
4154
|
+
let res = handle_slash_command(&mut session, &mut ui, "/fast on").await;
|
|
3585
4155
|
assert!(matches!(res, Some(SlashResult::Handled)));
|
|
3586
4156
|
assert!(session.fast_mode);
|
|
3587
4157
|
}
|