diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/skill_detail.test.tsx b/ui/litellm-dashboard/src/components/claude_code_plugins/skill_detail.test.tsx
new file mode 100644
index 00000000000..98ae4a44ae6
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/claude_code_plugins/skill_detail.test.tsx
@@ -0,0 +1,42 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { Plugin } from "./types";
+
+import SkillDetail from "./skill_detail";
+
+const buildSkill = (source: Plugin["source"]): Plugin => ({
+ id: "plugin-id",
+ name: "my-skill",
+ source,
+ enabled: true,
+});
+
+describe("SkillDetail source", () => {
+ it("links a github source to the repository", () => {
+ render();
+ expect(screen.getByRole("link", { name: /github.com\/org\/repo/ })).toHaveAttribute(
+ "href",
+ "https://github.com/org/repo",
+ );
+ });
+
+ it("renders an ssh clone url as plain text instead of an unusable link", () => {
+ render(
+ ,
+ );
+ expect(screen.getByText("git@ghe.example.com:org/repo.git")).toBeInTheDocument();
+ expect(screen.queryByRole("link", { name: /ghe.example.com/ })).not.toBeInTheDocument();
+ });
+
+ it("renders an ssh git-subdir source as plain text without a tree path", () => {
+ render(
+ ,
+ );
+ expect(screen.getByText("git@ghe.example.com:org/repo.git")).toBeInTheDocument();
+ expect(screen.queryByRole("link", { name: /ghe.example.com/ })).not.toBeInTheDocument();
+ });
+});
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 fe001641135..0f25b8ad515 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 { buildMarketplaceSettingsSnippet, formatInstallCommand } from "./helpers";
+import { buildMarketplaceSettingsSnippet, formatInstallCommand, getSourceLink } from "./helpers";
import { Plugin } from "./types";
interface SkillDetailProps {
@@ -21,13 +21,15 @@ const SkillDetail: React.FC = ({ skill, onBack }) => {
setTimeout(() => setCopiedKey(null), 2000);
};
+ const sourceLink = getSourceLink(skill.source);
const sourceUrl = (() => {
const src = skill.source;
- if (src.source === "github" && src.repo) return `https://github.com/${src.repo}`;
- if (src.source === "git-subdir" && src.url) return src.path ? `${src.url}/tree/main/${src.path}` : src.url;
- if (src.source === "url" && src.url) return src.url;
- return null;
+ if (sourceLink && src.source === "git-subdir" && src.path) {
+ return `${sourceLink}/tree/main/${src.path}`;
+ }
+ return sourceLink;
})();
+ const sourceText = skill.source.url ?? sourceUrl;
const installCommand = formatInstallCommand(skill);
@@ -146,7 +148,7 @@ const SkillDetail: React.FC = ({ skill, onBack }) => {
- {sourceUrl && (
+ {sourceUrl ? (
+ ) : (
+ sourceText && (
+
+
Source
+
{sourceText}
+
+ )
)}
{skill.keywords && skill.keywords.length > 0 && (