Skip to content

Add an Agents & CLI settings tab - #15193

Open
schacon wants to merge 4 commits into
agents-cli-apifrom
agents-cli-settings-ui
Open

Add an Agents & CLI settings tab#15193
schacon wants to merge 4 commits into
agents-cli-apifrom
agents-cli-settings-ui

Conversation

@schacon

@schacon schacon commented Aug 5, 2026

Copy link
Copy Markdown
Member

The product surface for everything the previous three PRs built.

Global settings showing skills and moving the CLI installer here:

Screenshot 2026-08-05 at 12 33 25 PM

Customizing the skill:

Screenshot 2026-08-05 at 12 33 29 PM

Per-project skill settings:

Screenshot 2026-08-05 at 12 33 45 PM

All of the available ones (even if not detected):

Screenshot 2026-08-05 at 12 33 52 PM

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 projectId prop 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 dismissLabel and onDismiss 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; 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 returnTo that is restored on close.

Three bugs found by using it, all fixed here:

  • Every option in Customize rendered unchecked, including the two that default to on. 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.
  • The option list was taller than the modal with no way to scroll to the last one.
  • Group headings repeated. WorkflowOption::ALL is 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__actions 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 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 registering but mcp serve with an agent remains a manual step. Deliberately left out of scope.

Verification

svelte-check 0 errors / 0 warnings across 2642 files; pnpm lint clean. Clicked through manually: install, update, uninstall, customize round-trip, and the startup prompt.

Note for reviewers testing locally: in a dev build every skill reads as "Outdated", because installed skills are stamped with the release CLI version while a dev build reports dev. Pressing "Update all" there restamps them as version: dev. Worth confirming VERSION is set in every gitbutler-tauri CI path before this ships — that is the one risk I could not verify locally.

schacon added 4 commits August 5, 2026 12:32
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.
Copilot AI lite review requested due to automatic review settings August 5, 2026 11:25
@github-actions github-actions Bot added rust Pull requests that update Rust code @gitbutler/desktop labels Aug 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment on lines +93 to +96
toastStore.update((items) => {
items.find((m) => m.id === messageId)?.onDismiss?.();
return items.filter((m) => m.id !== messageId);
});
Comment on lines +23 to +25
dismissLabel?: string;
/** Runs when the toast is dismissed, but not when it is replaced by id. */
onDismiss?: () => void;
Comment on lines +95 to +100
<Button
style="gray"
kind="outline"
disabled={showSymlink}
onclick={() => (showSymlink = !showSymlink)}>Show command</Button
>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

@gitbutler/desktop rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants