feat(dashboard): rehaul Skills hub browser — connected hubs, featured, preview + security scan (#40384)
The Browse-hub tab was a blank search box with sparse result cards (name + source + one Install button), no way to read a skill before installing, no visual security scan, and no indication it was even connected to any hubs. Backend (web_server.py): - GET /api/skills/hub/sources — lists the configured hubs (label + trust tier + GitHub rate-limit + index availability) and featured skills pulled from the centralized index (zero extra API calls), plus installed-skill provenance so the UI can mark already-installed results. - GET /api/skills/hub/preview — fetches a skill's SKILL.md text + file manifest WITHOUT installing (decodes byte-stored text, masks binaries). - GET /api/skills/hub/scan — runs the SAME quarantine + scan_skill + should_allow_install pipeline the CLI installer uses, then cleans up quarantine, returning verdict / per-finding detail / severity tally / install-policy decision. - search now returns per-source counts + timed-out sources + installed map. Frontend (SkillsPage HubBrowser): - Landing state: connected-hubs strip + featured skill grid (no more blank page). - Rich cards: trust-level color coding, source, tags, identifier, Details + Install (or Installed state). - Detail dialog: read the actual SKILL.md, on-demand visual security scan (verdict pill, severity tally, per-finding list, allow/block policy), GitHub repo link. - Search meta line: result count + timing + per-source breakdown (the 'feels slow / no feedback' complaint). Tests: 4 new endpoint test classes (sources/preview/scan + updated search shape) in test_dashboard_admin_endpoints.py.
This commit is contained in:
+82
-1
@@ -916,9 +916,19 @@ export const api = {
|
||||
updateSkillsFromHub: () =>
|
||||
fetchJSON<ActionResponse>("/api/skills/hub/update", { method: "POST" }),
|
||||
searchSkillsHub: (q: string, source = "all", limit = 20) =>
|
||||
fetchJSON<{ results: SkillHubResult[] }>(
|
||||
fetchJSON<SkillHubSearchResponse>(
|
||||
`/api/skills/hub/search?q=${encodeURIComponent(q)}&source=${encodeURIComponent(source)}&limit=${limit}`,
|
||||
),
|
||||
getSkillHubSources: () =>
|
||||
fetchJSON<SkillHubSourcesResponse>("/api/skills/hub/sources"),
|
||||
previewSkillFromHub: (identifier: string) =>
|
||||
fetchJSON<SkillHubPreview>(
|
||||
`/api/skills/hub/preview?identifier=${encodeURIComponent(identifier)}`,
|
||||
),
|
||||
scanSkillFromHub: (identifier: string) =>
|
||||
fetchJSON<SkillHubScan>(
|
||||
`/api/skills/hub/scan?identifier=${encodeURIComponent(identifier)}`,
|
||||
),
|
||||
};
|
||||
|
||||
/** Identity payload returned by ``GET /api/auth/me`` (Phase 7).
|
||||
@@ -975,6 +985,77 @@ export interface SkillHubResult {
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
/** Lock-entry summary for an already-installed hub skill (keyed by identifier). */
|
||||
export interface SkillHubInstalledEntry {
|
||||
name: string | null;
|
||||
trust_level: string | null;
|
||||
scan_verdict: string | null;
|
||||
}
|
||||
|
||||
export interface SkillHubSearchResponse {
|
||||
results: SkillHubResult[];
|
||||
/** source_id -> number of results returned by that source. */
|
||||
source_counts: Record<string, number>;
|
||||
/** source ids that didn't return within the parallel-search timeout. */
|
||||
timed_out: string[];
|
||||
/** identifier -> installed lock entry (for "already installed" badges). */
|
||||
installed: Record<string, SkillHubInstalledEntry>;
|
||||
}
|
||||
|
||||
export interface SkillHubSource {
|
||||
id: string;
|
||||
label: string;
|
||||
/** GitHub only: whether the API is currently rate-limited. */
|
||||
rate_limited?: boolean;
|
||||
/** hermes-index only: whether the centralized index loaded. */
|
||||
available?: boolean;
|
||||
}
|
||||
|
||||
export interface SkillHubSourcesResponse {
|
||||
sources: SkillHubSource[];
|
||||
index_available: boolean;
|
||||
/** Featured/popular skills from the centralized index (zero extra API calls). */
|
||||
featured: SkillHubResult[];
|
||||
installed: Record<string, SkillHubInstalledEntry>;
|
||||
}
|
||||
|
||||
export interface SkillHubPreview {
|
||||
name: string;
|
||||
description: string;
|
||||
source: string;
|
||||
identifier: string;
|
||||
trust_level: string;
|
||||
repo: string | null;
|
||||
tags: string[];
|
||||
/** Rendered SKILL.md content (the actual skill text). */
|
||||
skill_md: string;
|
||||
/** Relative paths of every file in the bundle. */
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface SkillHubScanFinding {
|
||||
severity: string;
|
||||
category: string;
|
||||
file: string;
|
||||
line: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface SkillHubScan {
|
||||
name: string;
|
||||
identifier: string;
|
||||
source: string;
|
||||
trust_level: string;
|
||||
/** "safe" | "caution" | "dangerous". */
|
||||
verdict: string;
|
||||
summary: string;
|
||||
/** Install-policy decision for this trust+verdict combo. */
|
||||
policy: "allow" | "ask" | "block";
|
||||
policy_reason: string;
|
||||
findings: SkillHubScanFinding[];
|
||||
severity_counts: Record<string, number>;
|
||||
}
|
||||
|
||||
// ── Admin types ───────────────────────────────────────────────────────
|
||||
|
||||
export interface McpServer {
|
||||
|
||||
Reference in New Issue
Block a user