Add an Agents & CLI settings tab - #15193
Conversation
None of the agent-skill machinery was reachable from the GUI: the only trace of it was an "Install But CLI" button buried in global settings, and a user who never ran the CLI never learned the feature existed. Adds a tab to both the global and project settings modals that lists detected agents and lets skills be installed, uninstalled, and customized. The CLI card moves here from the General page and now reports installed state with an Uninstall action, replacing the install-only button. Nothing deep-linked to its old location. One component serves both modals with a projectId prop deciding scope, because the two show the same list at different scopes rather than different settings. That mirrors the distinction the backend already models. Each agent row is its own component so that per-row mutation state gives only the row being changed a spinner; flattening the list would make all rows spin together. Detected agents are listed first with the rest behind "Show all". An installed skill keeps its row visible even when its agent is no longer detected, so a skill can always be uninstalled from where it was installed. Customize is only offered where the agent has an instruction file to write to. The customize modal renders from backend-supplied option descriptors rather than hardcoding the ten labels, and keeps a local edit buffer so in-progress edits survive a background refetch. Repo-local-only options are disabled outside repository scope, since the policy is rendered once and written everywhere the setup targets.
The settings tab is only found by users who go looking. Adds a one-time toast for users who have neither the CLI nor any skill installed, with a guided setup behind it, so the feature is discoverable without nagging anyone who has already made a choice. Uses the persistent toast system rather than chip toasts: chips auto-dismiss after four seconds and carry a single action, and this needs both a call to action and a distinct "Don't ask again" that persists. That required a small addition to the toast type. Dismissal previously had no hook and the label was hardcoded, so persisting a choice behind a button reading "Dismiss" would have implied "just this time". Both additions are optional and every existing caller is unchanged. The prompt mounts in the root layout, which mounts once per window and survives project switches; the project layout would re-fire on every switch. It reads through one-shot fetches rather than queries, because a component that renders nothing would never release a subscription, and an effect would fire once while still loading and again with data - showing the toast against the loading pass. Any failure is swallowed: a nudge is not worth blocking startup over. The wizard is a router modal so the toast callback can open it without a component reference. Since the router holds one modal at a time, opening it from settings carries a returnTo that is restored on close. Steps mirror the CLI wizard, and nothing is written until the review step is confirmed.
The tab flagged outdated skills with a badge but offered no way to act on it, so the only fix was dropping to the CLI. Adds an Update action per outdated row and an Update all banner above the list. This needed its own command rather than reusing install. Skills are identified by their SKILL.md frontmatter, not their folder name, so one installed into a custom folder lives somewhere other than the canonical path. Reusing install would have written a fresh copy to the canonical path and left the outdated one sitting beside it. agent_skills_update rewrites each skill where it was actually discovered, and skips any already current. The banner counts across every framework rather than the visible ones, so a skill hidden behind "Show all" is still covered by Update all. Adding a third button overflowed the row: the shared actions column sets only display:flex, with no gap and nothing stopping it shrinking. Rather than change a component used across the app, only the action worth reading stays labelled and the other two become icon-only with tooltips, inside a non-shrinking wrapper. The install path also truncates, since a long unbreakable path was setting the content column's minimum width and squeezing the actions out.
Ten flat checkboxes were overwhelming, the list was taller than the modal with no way to scroll to the last one, and every box rendered unchecked even though two are on by default. The unchecked boxes were a bug, not a missing default. The edit buffer was seeded inside show(), but the policy query is usually still in flight when the button is clicked, so it seeded from undefined. Seeding now happens once the data lands. With no managed block installed, current is null and the backend defaults apply. Options are now grouped under Committing, Branches & pull requests, and Automation, with separators. The grouping lives in the Rust catalogue next to the labels and help text rather than in the component, so there is one source of truth and the CLI wizard can use it later. A test asserts every option lands in exactly one non-empty group, so adding an option without grouping it fails the build instead of silently vanishing from the UI. WorkflowOption::ALL is deliberately left in its existing order. It drives the CLI wizard's checkbox order and the parser's output ordering, so reordering it to match the visual grouping would change but agent setup for a cosmetic reason. The UI groups without depending on that order. The option list also scrolls now, so the last option is reachable at any window height.
There was a problem hiding this comment.
🟡 Changes recommended
The new toast onDismiss hook can be async but is currently invoked without promise/error handling, and the new CLI card’s “Show command” toggle is disabled after first use, preventing it from being hidden again.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds an “Agents & CLI” settings surface to the Desktop app, making agent-skill installation/update/customization and CLI install state discoverable from both global and project settings, plus a one-time startup prompt to guide initial setup.
Changes:
- Introduces shared “Agents & CLI” settings UI (agent skill lists, per-skill actions, policy customization modal) for both global and project scope.
- Adds startup toast + setup wizard modal flow for users missing both the CLI and any installed skills.
- Wires new backend commands/types through Tauri/but-server and regenerates SDK typings for workflow option grouping.
File summaries
| File | Description |
|---|---|
| packages/but-sdk/src/generated/linear/index.d.ts | Regenerated SDK typings to include workflow grouping metadata. |
| packages/but-sdk/src/generated/graph/index.d.ts | Regenerated SDK typings to include workflow grouping metadata. |
| crates/gitbutler-tauri/src/main.rs | Registers the new agent_skills_update invoke handler. |
| crates/but-skill/src/policy.rs | Adds workflow option grouping (enum + mapping) and grouping integrity tests. |
| crates/but-server/src/lib.rs | Exposes /agent_skills_update route in the server transport. |
| crates/but-api/src/agents.rs | Adds workflow group DTOs, emits grouped catalogue, and implements agent_skills_update. |
| apps/desktop/src/routes/+layout.svelte | Mounts the agent setup prompt at the root layout. |
| apps/desktop/src/lib/state/uiState.svelte.ts | Adds settings page IDs and global modal type for agent setup wizard. |
| apps/desktop/src/lib/state/tags.ts | Adds RTK query tags for CLI install state, agents status, and agent policy. |
| apps/desktop/src/lib/settings/projectSettingsPages.ts | Adds “Agents & CLI” tab to project settings pages. |
| apps/desktop/src/lib/settings/generalSettingsPages.ts | Adds “Agents & CLI” tab to global settings pages. |
| apps/desktop/src/lib/notifications/toasts.ts | Extends toast model with dismiss label + dismiss hook. |
| apps/desktop/src/lib/config/cli.ts | Adds CLI uninstall + CLI install state query endpoints and tags. |
| apps/desktop/src/lib/bootstrap/deps.ts | Registers AgentsService in dependency injection bootstrap. |
| apps/desktop/src/lib/agents/setupPrompt.ts | Adds one-per-process guard for evaluating the startup prompt. |
| apps/desktop/src/lib/agents/agentsService.ts | Adds an agents service with status/policy/install/update/setPolicy endpoints. |
| apps/desktop/src/components/views/GlobalModalRouter.svelte | Adds agent setup modal rendering + optional return-to restoration. |
| apps/desktop/src/components/shared/ToastController.svelte | Supports custom dismiss label when rendering toasts with/without extra action. |
| apps/desktop/src/components/settings/ProjectSettingsModalContent.svelte | Renders the shared Agents & CLI settings component in project settings. |
| apps/desktop/src/components/settings/GeneralSettingsModalContent.svelte | Renders the shared Agents & CLI settings component in global settings. |
| apps/desktop/src/components/settings/GeneralSettings.svelte | Removes the old CLI install card from the General settings page. |
| apps/desktop/src/components/settings/CliInstallCard.svelte | New CLI install/uninstall card used by the new settings tab and wizard. |
| apps/desktop/src/components/settings/AgentWorkflowPolicyModal.svelte | New modal for editing grouped workflow preferences. |
| apps/desktop/src/components/settings/AgentSkillRow.svelte | New per-agent row with install/update/customize/uninstall actions. |
| apps/desktop/src/components/settings/AgentSkillList.svelte | New agent list with detected-first, “show all”, and “update all” banner. |
| apps/desktop/src/components/settings/AgentSetupPrompt.svelte | New startup toast that launches the setup wizard and can persist dismissal. |
| apps/desktop/src/components/settings/AgentSetupModalContent.svelte | New multi-step setup wizard modal for CLI + skills install. |
| apps/desktop/src/components/settings/AgentsAndCliSettings.svelte | Shared settings page used by both global and project settings modals. |
Review details
- Files reviewed: 26/28 changed files
- Comments generated: 3
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| toastStore.update((items) => { | ||
| items.find((m) => m.id === messageId)?.onDismiss?.(); | ||
| return items.filter((m) => m.id !== messageId); | ||
| }); |
| dismissLabel?: string; | ||
| /** Runs when the toast is dismissed, but not when it is replaced by id. */ | ||
| onDismiss?: () => void; |
| <Button | ||
| style="gray" | ||
| kind="outline" | ||
| disabled={showSymlink} | ||
| onclick={() => (showSymlink = !showSymlink)}>Show command</Button | ||
| > |
The product surface for everything the previous three PRs built.
Global settings showing skills and moving the CLI installer here:
Customizing the skill:
Per-project skill settings:
All of the available ones (even if not detected):
Why
None of the agent-skill machinery was reachable from the GUI. The only trace of it was an "Install But CLI" button buried in global settings → General, so a user who never ran the CLI never learned the feature existed.
Adds a tab to both the global and project settings modals that lists detected coding agents and lets their skills be installed, updated, uninstalled, and customized — plus a one-time startup nudge for users who have neither the CLI nor any skill.
What's here
The tab. One component serves both modals, with a
projectIdprop deciding scope, because the two show the same list at different scopes rather than different settings — mirroring the distinction the backend already models. Detected agents list first, the rest behind "Show all". An installed skill keeps its row visible even when its agent is no longer detected, so a skill can always be uninstalled from where it was installed.The CLI card moves here from the General page and now reports installed state with an Uninstall action, replacing the install-only button. Nothing deep-linked to its old location.
Update actions for outdated skills, per row and an "Update all" banner. The banner counts across every framework rather than the visible ones, so a skill hidden behind "Show all" is still covered.
Customize exposes the ten workflow preferences, grouped under Committing / Branches & pull requests / Automation. The grouping lives in the Rust catalogue next to the labels and help text, not in the component, so there is one source of truth — and a test asserts every option lands in exactly one non-empty group, so adding an option without grouping it fails the build instead of silently vanishing from the UI.
A startup toast for users with neither CLI nor skills, opening a guided setup that mirrors the CLI wizard. Nothing is written until the review step is confirmed.
Things worth a second look
Why the toast system and not chip toasts. Chips auto-dismiss after four seconds and carry a single action; this needs a call to action and a distinct "Don't ask again" that persists. That required adding an optional
dismissLabelandonDismissto the toast type — dismissal previously had no hook and the label was hardcoded, so persisting a choice behind a button reading "Dismiss" would have implied "just this time". Both additions are optional; every existing caller is unchanged.Where the prompt mounts. The root layout, which mounts once per window and survives project switches — the project layout would re-fire on every switch. It reads through one-shot fetches rather than queries, because a component that renders nothing would never release a subscription, and an effect would fire once while still loading and again with data, showing the toast against the loading pass. Any failure is swallowed: a nudge is not worth blocking startup over.
The wizard replaces whatever opened it. The global modal router holds one modal at a time, so opening the wizard from settings carries a
returnTothat is restored on close.Three bugs found by using it, all fixed here:
show(), but the policy query is usually still in flight when the button is clicked, so it seeded fromundefined. Seeding now happens once the data lands.WorkflowOption::ALLis ordered for the CLI wizard's prompts, which interleaves groups, so rendering a heading whenever the group changed between neighbours emitted "Branches & pull requests" and "Automation" twice each. The API now emits the catalogue with each group contiguous, the component buckets rather than watching for changes, and a test pins the ordering.One layout compromise. Adding a third button to each agent row overflowed the card: the shared
.card-group-item__actionssets onlydisplay: flex, with no gap and nothing stopping it shrinking. Rather than change a component used across the app, only the action worth reading stays labelled and the other two became icon-only with tooltips. Happy to fix the shared component instead if that is preferred.Known limitation
MCP is not included. Nothing in the repo writes MCP client config (
.mcp.json,~/.claude.json— zero hits), so registeringbut mcp servewith an agent remains a manual step. Deliberately left out of scope.Verification
svelte-check0 errors / 0 warnings across 2642 files;pnpm lintclean. Clicked through manually: install, update, uninstall, customize round-trip, and the startup prompt.