fix(ui): nest source object in Claude Code marketplace settings snippet (#35322)

Co-authored-by: milan <milan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-07-31 14:34:42 -07:00 committed by GitHub
parent fa56283806
commit 3083c55ffc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 28 deletions

View file

@ -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" };

View file

@ -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.<name>.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

View file

@ -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<SkillDetailProps> = ({ skill, onBack }) => {
const installCommand = formatInstallCommand(skill);
const settingsSnippet = buildMarketplaceSettingsSnippet(
typeof window !== "undefined" ? window.location.origin : "<proxy-url>",
);
const detailRows = [
...(skill.category ? [{ property: "Category", value: skill.category }] : []),
...(skill.domain ? [{ property: "Domain", value: skill.domain }] : []),
@ -298,21 +302,7 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
>
<span style={{ fontSize: 13, color: "#3c4043", fontWeight: 500 }}>~/.claude/settings.json</span>
<button
onClick={() => {
const snippet = JSON.stringify(
{
extraKnownMarketplaces: {
"my-org": {
source: "url",
url: `${typeof window !== "undefined" ? window.location.origin : ""}/claude-code/marketplace.json`,
},
},
},
null,
2,
);
copyToClipboard(snippet, "settings");
}}
onClick={() => copyToClipboard(settingsSnippet, "settings")}
style={{
display: "flex",
alignItems: "center",
@ -339,18 +329,7 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
backgroundColor: "#fff",
}}
>
{JSON.stringify(
{
extraKnownMarketplaces: {
"my-org": {
source: "url",
url: `${typeof window !== "undefined" ? window.location.origin : "<proxy-url>"}/claude-code/marketplace.json`,
},
},
},
null,
2,
)}
{settingsSnippet}
</pre>
</div>
</div>