diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts index 4c84db2a97d..c16eba24f7b 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts @@ -17,9 +17,25 @@ import { formatKeywords, parseSkillSource, isValidSubPath, + buildMarketplaceSettingsSnippet, } from "./helpers"; import { MarketplacePluginEntry, PluginSource } from "./types"; +describe("buildMarketplaceSettingsSnippet", () => { + it("nests the url under a source object so Claude Code accepts the marketplace", () => { + expect(JSON.parse(buildMarketplaceSettingsSnippet("https://proxy.example.com"))).toEqual({ + extraKnownMarketplaces: { + "my-org": { + source: { + source: "url", + url: "https://proxy.example.com/claude-code/marketplace.json", + }, + }, + }, + }); + }); +}); + describe("formatInstallCommand", () => { it("formats github source with repo", () => { const source: PluginSource = { source: "github", repo: "org/repo" }; diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts index cab3c5cba3c..a4e70f78af1 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts @@ -176,6 +176,27 @@ export const parseSkillSource = (rawUrl: string, subPath?: string): SkillSourceP return parseRawGitSource(url, subPath); }; +/** + * Build the `~/.claude/settings.json` snippet that registers the proxy as a marketplace. + * Claude Code expects `extraKnownMarketplaces..source` to be a source object, not a + * bare `"url"` string, so the url/source pair is nested one level deeper. + */ +export const buildMarketplaceSettingsSnippet = (proxyOrigin: string): string => + JSON.stringify( + { + extraKnownMarketplaces: { + "my-org": { + source: { + source: "url", + url: `${proxyOrigin}/claude-code/marketplace.json`, + }, + }, + }, + }, + null, + 2, + ); + /** * Generate install command for Claude Code CLI * Format: /plugin marketplace add org/repo OR /plugin marketplace add url diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/skill_detail.tsx b/ui/litellm-dashboard/src/components/claude_code_plugins/skill_detail.tsx index 8b7537c1988..fe001641135 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/skill_detail.tsx +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/skill_detail.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { ArrowLeftOutlined, CopyOutlined, CheckOutlined, LinkOutlined } from "@ant-design/icons"; -import { formatInstallCommand } from "./helpers"; +import { buildMarketplaceSettingsSnippet, formatInstallCommand } from "./helpers"; import { Plugin } from "./types"; interface SkillDetailProps { @@ -31,6 +31,10 @@ const SkillDetail: React.FC = ({ skill, onBack }) => { const installCommand = formatInstallCommand(skill); + const settingsSnippet = buildMarketplaceSettingsSnippet( + typeof window !== "undefined" ? window.location.origin : "", + ); + const detailRows = [ ...(skill.category ? [{ property: "Category", value: skill.category }] : []), ...(skill.domain ? [{ property: "Domain", value: skill.domain }] : []), @@ -298,21 +302,7 @@ const SkillDetail: React.FC = ({ skill, onBack }) => { > ~/.claude/settings.json