mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(claude-code): correct skill install command and marketplace setup UX
- formatInstallCommand now produces /plugin install {name}@litellm instead of /plugin marketplace add {source}
- extraKnownMarketplaces snippet fixed: source must be a nested object not a flat string; the flat string caused Claude Code to reject the settings file
- marketplace key renamed from my-org to litellm to match the name the proxy returns in marketplace.json
- setup tab now shows /plugin marketplace add command as primary option with settings.json as secondary
- usage tab now shows a hint to run /plugin marketplace update litellm when a plugin is not found
This commit is contained in:
parent
b0fac57fe4
commit
05c91aa5f2
3 changed files with 102 additions and 39 deletions
|
|
@ -25,7 +25,7 @@ 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": {
|
||||
litellm: {
|
||||
source: {
|
||||
source: "url",
|
||||
url: "https://proxy.example.com/claude-code/marketplace.json",
|
||||
|
|
@ -37,28 +37,12 @@ describe("buildMarketplaceSettingsSnippet", () => {
|
|||
});
|
||||
|
||||
describe("formatInstallCommand", () => {
|
||||
it("formats github source with repo", () => {
|
||||
const source: PluginSource = { source: "github", repo: "org/repo" };
|
||||
expect(formatInstallCommand({ name: "my-plugin", source })).toBe("/plugin marketplace add org/repo");
|
||||
it("produces a /plugin install command scoped to the litellm marketplace", () => {
|
||||
expect(formatInstallCommand({ name: "my-plugin" })).toBe("/plugin install my-plugin@litellm");
|
||||
});
|
||||
|
||||
it("formats url source", () => {
|
||||
const source: PluginSource = { source: "url", url: "https://example.com/plugin" };
|
||||
expect(formatInstallCommand({ name: "my-plugin", source })).toBe(
|
||||
"/plugin marketplace add https://example.com/plugin",
|
||||
);
|
||||
});
|
||||
|
||||
it("formats git-subdir source using its url", () => {
|
||||
const source: PluginSource = { source: "git-subdir", url: "https://github.com/org/repo", path: "plugins/x" };
|
||||
expect(formatInstallCommand({ name: "my-plugin", source })).toBe(
|
||||
"/plugin marketplace add https://github.com/org/repo",
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to plugin name when no repo or url", () => {
|
||||
const source: PluginSource = { source: "github" };
|
||||
expect(formatInstallCommand({ name: "my-plugin", source })).toBe("/plugin marketplace add my-plugin");
|
||||
it("uses the plugin name as the identifier", () => {
|
||||
expect(formatInstallCommand({ name: "code-review" })).toBe("/plugin install code-review@litellm");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -179,13 +179,14 @@ export const parseSkillSource = (rawUrl: string, subPath?: string): SkillSourceP
|
|||
/**
|
||||
* Build the `~/.claude/settings.json` snippet that registers the proxy as a marketplace.
|
||||
* Claude Code expects `extraKnownMarketplaces.<name>.source` to be a source object, not a
|
||||
* bare `"url"` string, so the url/source pair is nested one level deeper.
|
||||
* bare `"url"` string, so the url/source pair is nested one level deeper. The key must be
|
||||
* "litellm" to match the name the proxy returns in marketplace.json.
|
||||
*/
|
||||
export const buildMarketplaceSettingsSnippet = (proxyOrigin: string): string =>
|
||||
JSON.stringify(
|
||||
{
|
||||
extraKnownMarketplaces: {
|
||||
"my-org": {
|
||||
litellm: {
|
||||
source: {
|
||||
source: "url",
|
||||
url: `${proxyOrigin}/claude-code/marketplace.json`,
|
||||
|
|
@ -198,20 +199,11 @@ export const buildMarketplaceSettingsSnippet = (proxyOrigin: string): string =>
|
|||
);
|
||||
|
||||
/**
|
||||
* Generate install command for Claude Code CLI
|
||||
* Format: /plugin marketplace add org/repo OR /plugin marketplace add url
|
||||
* Generate install command for Claude Code CLI.
|
||||
* Installs the named plugin from the "litellm" marketplace registered in settings.json.
|
||||
*/
|
||||
export const formatInstallCommand = (plugin: { name: string; source: PluginSource }): string => {
|
||||
const { source } = plugin;
|
||||
if (source.source === "github" && source.repo) {
|
||||
return `/plugin marketplace add ${source.repo}`;
|
||||
}
|
||||
if ((source.source === "url" || source.source === "git-subdir") && source.url) {
|
||||
return `/plugin marketplace add ${source.url}`;
|
||||
}
|
||||
// Fallback to plugin name
|
||||
return `/plugin marketplace add ${plugin.name}`;
|
||||
};
|
||||
export const formatInstallCommand = (plugin: { name: string }): string =>
|
||||
`/plugin install ${plugin.name}@litellm`;
|
||||
|
||||
/**
|
||||
* Extract unique categories from plugins list
|
||||
|
|
|
|||
|
|
@ -261,6 +261,32 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Shown when the marketplace catalog is stale and the plugin isn't found yet */}
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #fce8b2",
|
||||
borderRadius: 8,
|
||||
padding: "12px 16px",
|
||||
backgroundColor: "#fefce8",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<p style={{ fontSize: 13, color: "#5f6368", lineHeight: 1.6, margin: "0 0 8px 0" }}>
|
||||
If you see "Plugin {skill.name} not found in marketplace", update the catalog first:
|
||||
</p>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: 13,
|
||||
fontFamily: "monospace",
|
||||
color: "#202124",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
>
|
||||
/plugin marketplace update litellm
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<p style={{ fontSize: 13, color: "#5f6368", lineHeight: 1.6, margin: 0 }}>
|
||||
Don't have the marketplace configured yet?{" "}
|
||||
<span onClick={() => setActiveTab("setup")} style={{ color: "#1a73e8", cursor: "pointer" }}>
|
||||
|
|
@ -276,12 +302,73 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 8px 0" }}>
|
||||
One-time marketplace setup
|
||||
</h2>
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 24px 0", lineHeight: 1.6 }}>
|
||||
Add this to{" "}
|
||||
|
||||
{/* Option 1: single command — fastest path for most users */}
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 12px 0", lineHeight: 1.6 }}>
|
||||
Run this command in Claude Code to register the marketplace:
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #dadce0",
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "10px 16px",
|
||||
backgroundColor: "#f8f9fa",
|
||||
borderBottom: "1px solid #dadce0",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 13, color: "#3c4043", fontWeight: 500 }}>Run in Claude Code</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
||||
copyToClipboard(`/plugin marketplace add ${origin}/claude-code/marketplace.json`, "marketplace-cmd");
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
fontSize: 12,
|
||||
color: copiedKey === "marketplace-cmd" ? "#137333" : "#1a73e8",
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{copiedKey === "marketplace-cmd" ? <CheckOutlined /> : <CopyOutlined />}
|
||||
{copiedKey === "marketplace-cmd" ? "Copied" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: "14px 16px",
|
||||
fontSize: 13,
|
||||
fontFamily: "monospace",
|
||||
color: "#202124",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
{`/plugin marketplace add ${typeof window !== "undefined" ? window.location.origin : "<proxy-url>"}/claude-code/marketplace.json`}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Option 2: settings.json — for persistent config or managed deployments.
|
||||
extraKnownMarketplaces requires source to be a nested object, not a flat string. */}
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 12px 0", lineHeight: 1.6 }}>
|
||||
Or add this to{" "}
|
||||
<code style={{ fontSize: 13, backgroundColor: "#f1f3f4", padding: "1px 6px", borderRadius: 4 }}>
|
||||
~/.claude/settings.json
|
||||
</code>{" "}
|
||||
to point Claude Code at your proxy:
|
||||
for a persistent configuration:
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue