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:
Teknium
2026-06-06 02:44:50 -07:00
committed by GitHub
parent 5af899c7ca
commit 56236b16e3
4 changed files with 1282 additions and 73 deletions
+82 -1
View File
@@ -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 {