Skip to content

feat(web-ui): interactive capabilities editor with live YAML sync - #142

Merged
Minitour merged 3 commits into
version-2.0from
feat/web-ui-capabilities-editor
Jul 31, 2026
Merged

feat(web-ui): interactive capabilities editor with live YAML sync#142
Minitour merged 3 commits into
version-2.0from
feat/web-ui-capabilities-editor

Conversation

@Minitour

@Minitour Minitour commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

  • Add interactive project capabilities editing in the web UI (skills, servers, tools, plugins, rules, hooks, subagents, options) with YAML-preserving mutations
  • Keep the UI and on-disk capabilities file in sync via a file watcher and SSE (capabilities-changed), including drag-and-drop reorder within each section
  • Improve registry browse/preview, tool↔skill refs (@server.tool vs bare command ids), ID validation, command tools, and UI motion polish

Test plan

  • Open a project in the web UI and add/edit/delete entries across skills, tools/servers, rules, hooks, subagents, and plugins
  • Confirm edits rewrite capabilities.yaml without scrambling unrelated comments/keys
  • Drag rows within a section and verify YAML order updates; confirm reorder is disabled while search is active
  • Edit the capabilities file on disk and confirm the UI refreshes without a full page reload
  • Browse a registry, preview a skill (file tree), and add skill/plugin from registry
  • Associate MCP vs command tools with skills using the correct requires ref forms

Made with Cursor

Add project capability CRUD, registry browse, drag-and-drop section ordering, and file-watcher SSE so the UI and capabilities.yaml stay in sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Web UI capabilities editor with YAML-preserving mutations and live sync

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add full CRUD + reorder UI for project capabilities
 (skills/tools/servers/rules/hooks/subagents/plugins/options).
• Preserve capabilities.yaml structure/comments by editing via YAML AST and targeted mutations.
• Keep UI in sync with on-disk edits using a capabilities file watcher and SSE push events.
Diagram

graph TD
  UI["Web UI"] --> Hooks["projects/hooks.ts"] --> API["projects/api.ts"] --> Srv["server/index.ts"] --> CapsRoutes["capabilities-routes.ts"] --> YAMLEdit["shared/capabilities.ts"] --> CapsFile[("capabilities.yaml/json")]
  CapsFile --> Watcher["capabilities-watcher.ts"] --> Srv --> SSE["/api/projects/:id/events (SSE)"] --> UI
  subgraph Legend
    direction LR
    _ui["UI"] ~~~ _svc["Server"] ~~~ _file[("File")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. WebSockets instead of SSE
  • ➕ Bi-directional channel for future collaborative editing/conflict resolution
  • ➕ More flexible eventing model (ack/retry semantics can be explicit)
  • ➖ More infrastructure and lifecycle complexity than SSE for one-way notifications
  • ➖ Harder to proxy/cache; SSE is simpler and widely supported
2. Centralize all mutations as full-document PATCH (replace file)
  • ➕ Simpler server code (single validation + rewrite)
  • ➕ Avoids needing per-section mutation logic and cascade updates
  • ➖ Much harder to preserve YAML comments/key ordering reliably
  • ➖ Higher risk of scrambling unrelated fields; worse diffs for users
3. Adopt a drag-and-drop library (dnd-kit/react-beautiful-dnd)
  • ➕ Better accessibility and touch/keyboard support out of the box
  • ➕ Less bespoke drag state management
  • ➖ Adds dependency weight and integration cost
  • ➖ Existing lightweight HTML5 approach is adequate for section-local reorder

Recommendation: The chosen approach (YAML AST targeted mutations + SSE push + per-section reorder) is appropriate because it optimizes for preserving user-authored YAML/comments while still enabling rich UI edits. SSE is a pragmatic fit for one-way invalidation. If reorder UX/accessibility becomes a priority, consider migrating ReorderableList to a dedicated DnD library later.

Files changed (36) +7432 / -871

Enhancement (32) +7026 / -790
capabilities-routes.tsAdd capabilities mutation API (CRUD/reorder/options/registry installs) +553/-0

Add capabilities mutation API (CRUD/reorder/options/registry installs)

• Introduces a route dispatcher for /api/projects/:id/capabilities endpoints covering append/update/delete, section ordering, options patching, and installing skills/plugins from registries. Uses YAML/JSON-preserving shared mutation helpers and triggers configure + SSE notifications after writes, including cascade updates for renamed servers/tools.

src/server/capabilities-routes.ts

capabilities-watcher.tsAdd per-project capabilities file watcher with debounce and self-write suppression +196/-0

Add per-project capabilities file watcher with debounce and self-write suppression

• Implements a watcher that tracks each project's capabilities.yaml/json using fs.watch plus periodic mtime polling for dropped events. Debounces notifications and ignores events shortly after server-owned writes to prevent feedback loops.

src/server/capabilities-watcher.ts

index.tsWire capabilities mutations, SSE events, disk reload, and variable item endpoints +292/-23

Wire capabilities mutations, SSE events, disk reload, and variable item endpoints

• Adds an SSE endpoint (/api/projects/:id/events) emitting capabilities-changed and integrates the capabilities file watcher to reload+configure on external edits. Routes new capabilities mutation APIs, expands project detail shaping (skills inline content, tool metadata, server oauth/env/headers/cwd/tls flags, rules/hook inline content, authored plugins), and adds PUT/DELETE for individual variable catalog entries plus a richer variables response (required + catalog).

src/server/index.ts

skill-content.tsSupport fetching skill content for uninstalled remote/github/gitlab skills +77/-3

Support fetching skill content for uninstalled remote/github/gitlab skills

• Makes resolveSkillContentById async and adds a fallback that fetches SKILL.md for uninstalled skills using authenticated fetch and the shared git snapshot cache (or direct HTTP for remote URLs). Prefers local/inline copies first, and rejects likely HTML login pages.

src/server/skill-content.ts

capabilities.tsAdd YAML AST mutation helpers (remove/update/reorder/options upsert) +269/-3

Add YAML AST mutation helpers (remove/update/reorder/options upsert)

• Extends shared capabilities utilities with targeted mutations for array-valued sections (remove/update/reorder) that preserve YAML comments and node identity, plus an options upsert that shallow-merges while allowing key deletion via undefined. Adds YAML map/seq helpers and enforces reorder permutations/uniqueness.

src/shared/capabilities.ts

FileTree.tsxIntroduce reusable file tree viewer +98/-0

Introduce reusable file tree viewer

• Adds a generic FileTree component that builds a directory tree from path strings and renders expandable nodes with consistent styling/animation hooks.

web-ui/src/components/common/FileTree.tsx

ReorderableList.tsxAdd lightweight drag-and-drop reorder list +153/-0

Add lightweight drag-and-drop reorder list

• Introduces a reusable ReorderableList with explicit drag-handle arming, HTML5 drag/drop behavior, and a helper to compute reordered id arrays. Supports disabled mode and provides visual feedback for dragging/hover targets.

web-ui/src/components/common/ReorderableList.tsx

api.tsAdd capabilities mutation and variable-item API methods +67/-1

Add capabilities mutation and variable-item API methods

• Adds client methods for capabilities append/update/delete/reorder, patching top-level options, and installing skills/plugins from registries. Adds PUT/DELETE endpoints for single variable catalog entries.

web-ui/src/features/projects/api.ts

CapabilitiesSection.tsxReplace static lists with interactive editors and registry add flows +245/-82

Replace static lists with interactive editors and registry add flows

• Refactors the project capabilities area into collapsible sections with add actions and dialogs, introduces a unified ToolsSection, and adds a plugins editor plus registry browsing dialogs for skills/plugins (including inline skill creation). Search now drives forced-open sections and disables reorder within lists.

web-ui/src/features/projects/components/CapabilitiesSection.tsx

CapabilityCollapsible.tsxAdd reusable collapsible section wrapper for capability editors +96/-0

Add reusable collapsible section wrapper for capability editors

• Introduces a shared collapsible component used to render each capabilities subsection with consistent header, count, add/actions slots, and mount/animation behavior.

web-ui/src/features/projects/components/CapabilityCollapsible.tsx

HooksList.tsxMake hooks editable with CRUD + reorder and inline editor dialog +267/-125

Make hooks editable with CRUD + reorder and inline editor dialog

• Replaces a read-only list with an interactive editor supporting add/edit/delete and drag-and-drop reorder (disabled while searching). Adds validation for ids and an inline editing dialog for hook fields/content.

web-ui/src/features/projects/components/HooksList.tsx

OptionsSection.tsxMake options editable (tool exposure + required commands) +108/-66

Make options editable (tool exposure + required commands)

• Converts options display into an editor that patches capabilities options via API. Adds toolExposure toggles and CRUD UI for required commands while continuing to display security settings if present.

web-ui/src/features/projects/components/OptionsSection.tsx

PluginPreviewDialog.tsxAdd plugin preview dialog with registry fallback rendering +152/-0

Add plugin preview dialog with registry fallback rendering

• Adds a dialog that attempts to load plugin preview/metadata from registries and falls back to a locally constructed preview. Renders markdown safely (DOMPurify) and shows install snippet + resolved plugin-derived details.

web-ui/src/features/projects/components/PluginPreviewDialog.tsx

PluginsEditor.tsxAdd authored plugin editor with reorder/delete and preview +151/-0

Add authored plugin editor with reorder/delete and preview

• Introduces a plugins editor that supports reorder for id-addressable plugins, delete with confirmation, and a preview dialog. Displays resolved plugin info and highlights plugin-derived/locked behavior.

web-ui/src/features/projects/components/PluginsEditor.tsx

RegistryBrowseDialog.tsxAdd registry browser/preview dialog for skills and plugins +510/-0

Add registry browser/preview dialog for skills and plugins

• Implements a searchable registry browsing UI with item preview rendering (markdown + file tree) and install actions. Supports inline skill creation with id validation and uses capabilities mutation APIs for installs.

web-ui/src/features/projects/components/RegistryBrowseDialog.tsx

RulesList.tsxMake rules editable with CRUD + reorder and inline editor dialog +225/-112

Make rules editable with CRUD + reorder and inline editor dialog

• Replaces a read-only rules list with an interactive editor supporting add/edit/delete and drag reorder (disabled while searching). Adds inline rule editing support and id validation messaging.

web-ui/src/features/projects/components/RulesList.tsx

ServerToolsPanel.tsxAdopt shared motion classes for tool cards +3/-2

Adopt shared motion classes for tool cards

• Updates chevron rotation and expanded-panel styling to use the new ui-chevron/ui-panel-enter motion classes for consistent animation behavior.

web-ui/src/features/projects/components/ServerToolsPanel.tsx

SkillDetailDialog.tsxImprove skill preview (file tree) and enable inline skill editing +132/-121

Improve skill preview (file tree) and enable inline skill editing

• Refactors to use the shared FileTree component and adds editing support for inline, non-plugin skills (content + description) via capabilities update mutation. Introduces draft state and error handling for save operations.

web-ui/src/features/projects/components/SkillDetailDialog.tsx

SkillsList.tsxEnable skill reorder and deletion with plugin-lock UI +74/-44

Enable skill reorder and deletion with plugin-lock UI

• Adds drag-and-drop reorder and delete actions to skills, disabling reorder while searching. Shows a lock indicator for plugin-sourced skills and routes delete/reorder through capabilities mutation hooks.

web-ui/src/features/projects/components/SkillsList.tsx

SubagentsList.tsxMake sub-agents editable with CRUD + reorder and tool ref helpers +283/-105

Make sub-agents editable with CRUD + reorder and tool ref helpers

• Converts the sub-agent list into an interactive editor with add/edit/delete and reorder (disabled while searching). Incorporates tool reference matching helpers to keep sub-agent tool refs consistent.

web-ui/src/features/projects/components/SubagentsList.tsx

TokenSavingsBar.tsxAdd loading/empty states for token savings stats +65/-19

Add loading/empty states for token savings stats

• Updates the token savings bar to accept null stats and a loading flag, displaying spinners/placeholders and preserving layout during async refresh.

web-ui/src/features/projects/components/TokenSavingsBar.tsx

ToolsSection.tsxAdd full tools+servers editor with linking, OAuth actions, and reorder +2351/-0

Add full tools+servers editor with linking, OAuth actions, and reorder

• Introduces a comprehensive tools/servers editing UI: create/edit/delete servers and tools (MCP + command), manage OAuth connect/disconnect, validate ids, reorder within sections, and maintain consistent skill↔tool references (e.g., @server.tool for MCP). Adds UI polish for linking/anchors and tool association flows, including defaults/formatter editing for MCP tools.

web-ui/src/features/projects/components/ToolsSection.tsx

VariablesForm.tsxAdd variable catalog management (add/delete) and show required vs catalog +119/-32

Add variable catalog management (add/delete) and show required vs catalog

• Extends the variables UI to show both required variables (from ${VAR} references) and the stored catalog, with badges indicating referenced/unused. Adds create/delete variable operations via new variable-item endpoints while preserving bulk save for value updates.

web-ui/src/features/projects/components/VariablesForm.tsx

hooks.tsAdd capabilities mutation hooks and SSE live-sync invalidation +207/-3

Add capabilities mutation hooks and SSE live-sync invalidation

• Adds an SSE-backed live sync hook that invalidates project-related queries on capabilities-changed events. Introduces React Query mutations for capabilities CRUD/reorder/options and variable item operations, plus broader query invalidation after auth changes.

web-ui/src/features/projects/hooks.ts

AddRegistryDialog.tsxAdopt new dialog motion classes +2/-2

Adopt new dialog motion classes

• Updates Radix dialog overlay/content classes to use the new ui-overlay/ui-dialog motion styling for consistent animations.

web-ui/src/features/registries/admin/AddRegistryDialog.tsx

EditRegistryDialog.tsxAdopt new dialog motion classes +2/-2

Adopt new dialog motion classes

• Updates Radix dialog overlay/content classes to use the new ui-overlay/ui-dialog motion styling for consistent animations.

web-ui/src/features/registries/admin/EditRegistryDialog.tsx

index.cssAdd shared UI motion system and scrollbar theming +173/-0

Add shared UI motion system and scrollbar theming

• Introduces CSS variables and keyframes for overlays, dialogs, dropdowns, collapsible panels, and chevrons, including reduced-motion handling. Adds thin themed scrollbar styling applied across the app.

web-ui/src/index.css

api.tsAdd PUT helper to API wrapper +7/-0

Add PUT helper to API wrapper

• Adds a typed api.put method to align with new server endpoints (variable item PUT and capability order PUT).

web-ui/src/lib/api.ts

ids.tsAdd capa id validation/sanitization helpers +27/-0

Add capa id validation/sanitization helpers

• Defines a canonical id regex, helpers to validate ids, sanitize input while typing, and return structured validation issues for localized error messaging.

web-ui/src/lib/ids.ts

toolRefs.tsAdd tool reference canonicalization and matching for skills/subagents +47/-0

Add tool reference canonicalization and matching for skills/subagents

• Introduces helpers to compute canonical requires refs (MCP: @server.tool; command: tool id), match refs across accepted dialects, and update requires arrays without duplicates.

web-ui/src/lib/toolRefs.ts

ProjectDetailPage.tsxEnable capabilities live sync and restructure sections +17/-43

Enable capabilities live sync and restructure sections

• Hooks up SSE-based live sync on the project page, always renders the capabilities/options sections with empty states driven by server-provided data, and updates navigation/scroll targets accordingly. Passes new plugin-related props into CapabilitiesSection and projectId into OptionsSection.

web-ui/src/pages/ProjectDetailPage.tsx

api.tsExpand API types for new capabilities editor fields and mutation responses +58/-2

Expand API types for new capabilities editor fields and mutation responses

• Extends project capability types to include authored plugins, richer server oauth/env/header fields, tool metadata (description/defaults/formatter), inline content fields for skills/rules/hooks, and variables responses with catalog. Adds a CapabilitiesMutationResponse type and a CapabilitySection union used by new mutation APIs.

web-ui/src/types/api.ts

Bug fix (1) +2 / -1
api.tsAlways send q parameter for registry search +2/-1

Always send q parameter for registry search

• Adjusts registry search requests to always include the q param (including empty string) so adapters can return initial browse results without requiring a non-empty query.

web-ui/src/features/registries/api.ts

Refactor (1) +2 / -77
ItemDetail.tsxRefactor registry item detail to reuse FileTree component +2/-77

Refactor registry item detail to reuse FileTree component

• Removes embedded file tree logic and switches to the shared FileTree component for previewing registry item file lists, reducing duplication and aligning styling.

web-ui/src/features/registries/components/ItemDetail.tsx

Tests (1) +277 / -0
capabilities.test.tsAdd coverage for YAML-preserving capability mutations +277/-0

Add coverage for YAML-preserving capability mutations

• Adds tests for removeCapabilityEntry, updateCapabilityEntry, reorderCapabilityEntries, and upsertOptions across YAML and JSON formats, including comment-preservation expectations and permutation/edge-case validation.

src/shared/tests/capabilities.test.ts

Documentation (1) +125 / -3
projects.jsonAdd translations for new editor actions, validation, and variables catalog +125/-3

Add translations for new editor actions, validation, and variables catalog

• Adds a large set of new i18n strings covering capability editor actions, confirmations, id validation errors, registry browsing UI, tool/server editor labels, and expanded variable catalog messaging.

web-ui/src/locales/en/projects.json

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 31, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. filePath used before declared ✓ Resolved 🐞 Bug ≡ Correctness
Description
CapabilitiesFileWatcher.fire() references filePath inside the self-write grace branch before
filePath is declared, which triggers a temporal-dead-zone error (and typically a TS diagnostic).
This breaks the self-write suppression path and can throw at runtime when that branch executes.
Code

src/server/capabilities-watcher.ts[R156-162]

+    const until = this.selfWriteUntil.get(projectId) ?? 0;
+    if (Date.now() < until) {
+      // Swallow our own write; adopt the new mtime so we don't re-fire later.
+      if (filePath && existsSync(filePath)) {
+        this.mtimes.set(projectId, safeMtime(filePath));
+      }
+      return;
Evidence
The watcher’s self-write suppression branch checks if (filePath && existsSync(filePath)) before
filePath is declared later in the function, which is an unambiguous TDZ/use-before-declaration
bug.

src/server/capabilities-watcher.ts[151-166]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`CapabilitiesFileWatcher.fire()` references `filePath` before its `const filePath = …` declaration. Because `const` variables are in the temporal dead zone until initialized, this is a compile-time error in TS and a runtime `ReferenceError` if the self-write branch runs.
## Issue Context
The self-write grace path is exercised when the server mutates `capabilities.yaml/json` itself (e.g., via the new capabilities mutation routes). The watcher must be able to swallow these events without throwing.
## Fix Focus Areas
- src/server/capabilities-watcher.ts[151-186]
## Implementation notes
- Define `const filePath = this.filePaths.get(projectId);` at the top of `fire()` (before checking `selfWriteUntil`).
- In the grace branch, only update mtime if `filePath` is non-null and exists.
- Ensure the rest of the function still uses the same `filePath` variable (avoid duplicate lookups).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Remote skill fetch SSRF ✓ Resolved 🐞 Bug ⛨ Security
Description
Remote skill content resolution fetches skill.def.url server-side with no destination validation,
and the skill content API returns the fetched text to the caller. Since this PR also adds
capabilities mutation endpoints that accept arbitrary skill definitions, an authenticated client can
set a remote URL and use the content endpoint to make the server fetch and exfiltrate data from
arbitrary HTTP(S) endpoints reachable by the server.
Code

src/server/skill-content.ts[R214-216]

+    if (skill.type === 'remote' && skill.def?.url) {
+      const response = await authFetch.fetch(skill.def.url);
+      if (!response.ok) return null;
Evidence
The code path fetches a user-configurable URL for remote skills and returns it via the skill content
API, and the new mutation routes accept arbitrary skill bodies (including type: remote and
def.url), making the URL controllable by an authenticated API client. The shared
AuthenticatedFetch wrapper does not add any destination restrictions beyond optional auth headers
and ultimately calls fetch(url, …).

src/server/skill-content.ts[188-224]
src/server/index.ts[881-941]
src/server/capabilities-routes.ts[185-216]
src/server/capabilities-routes.ts[247-306]
src/shared/authenticated-fetch.ts[170-228]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new fallback in `fetchUninstalledSkillContent()` performs a server-side fetch to `skill.def.url` for `skill.type === 'remote'` without restricting scheme/host/IP range or redirects, and the `/api/projects/:id/skills/:skillId/content` endpoint returns the fetched body as `content`. Combined with the new capabilities mutation routes (which allow creating/updating skills with arbitrary JSON bodies), this enables SSRF and response exfiltration.
## Issue Context
- Remote URL is taken directly from capabilities (`skill.def.url`).
- Capabilities can now be mutated via the API (`POST/PATCH /api/projects/:id/capabilities/skills…`).
- The skill content API returns the resolved markdown content to the client.
- `AuthenticatedFetch.fetch()` adds auth headers for known git hosts but does not enforce any URL allowlist/denylist; it ultimately calls global `fetch(url, …)`.
## Fix Focus Areas
- src/server/skill-content.ts[188-256]
- src/server/index.ts[881-941]
- src/server/capabilities-routes.ts[185-216]
- src/shared/authenticated-fetch.ts[170-228]
## Implementation notes
- Add URL validation for `skill.def.url` before fetching:
- Allow only `https:` (and possibly `http:` only when explicitly enabled).
- Block loopback/link-local/private network destinations (including after DNS resolution), and block `localhost`.
- Limit redirects and prevent redirecting into blocked address ranges.
- Enforce tight timeouts and maximum response size.
- Consider an explicit allowlist (e.g., only registry-approved domains) or a config flag to disable remote URL fetching entirely.
- Optionally enforce server-side validation in capabilities mutation routes to reject `remote` skills with disallowed URLs so the unsafe state cannot be persisted.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/server/capabilities-watcher.ts
Comment thread src/server/skill-content.ts Outdated
Minitour and others added 2 commits July 31, 2026 17:51
Resolve plugins on configure/GET so MCP servers appear after add, discover OAuth endpoints on connect, and surface Needs OAuth on the Tools section plus connect-first when listing tools.

Co-authored-by: Cursor <cursoragent@cursor.com>
Declare capabilities filePath before the self-write grace path, and fetch remote skill URLs only over public HTTPS with DNS/private-IP checks, redirect limits, and size caps.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Minitour
Minitour merged commit 5652a76 into version-2.0 Jul 31, 2026
@Minitour
Minitour deleted the feat/web-ui-capabilities-editor branch July 31, 2026 15:31
@Minitour Minitour mentioned this pull request Aug 2, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant