feat(skills): add repository field for source transparency
This commit is contained in:
parent
109ac1c549
commit
4f0f02f9f0
@ -40,6 +40,8 @@ export type SkillStatusEntry = {
|
|||||||
primaryEnv?: string;
|
primaryEnv?: string;
|
||||||
emoji?: string;
|
emoji?: string;
|
||||||
homepage?: string;
|
homepage?: string;
|
||||||
|
/** URL to the source repository (e.g., GitHub) for transparency and auditability. */
|
||||||
|
repository?: string;
|
||||||
always: boolean;
|
always: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
blockedByAllowlist: boolean;
|
blockedByAllowlist: boolean;
|
||||||
@ -164,6 +166,11 @@ function buildSkillStatus(
|
|||||||
entry.frontmatter.website ??
|
entry.frontmatter.website ??
|
||||||
entry.frontmatter.url;
|
entry.frontmatter.url;
|
||||||
const homepage = homepageRaw?.trim() ? homepageRaw.trim() : undefined;
|
const homepage = homepageRaw?.trim() ? homepageRaw.trim() : undefined;
|
||||||
|
const repositoryRaw =
|
||||||
|
entry.metadata?.repository ??
|
||||||
|
entry.frontmatter.repository ??
|
||||||
|
entry.frontmatter.repo;
|
||||||
|
const repository = repositoryRaw?.trim() ? repositoryRaw.trim() : undefined;
|
||||||
|
|
||||||
const requiredBins = entry.metadata?.requires?.bins ?? [];
|
const requiredBins = entry.metadata?.requires?.bins ?? [];
|
||||||
const requiredAnyBins = entry.metadata?.requires?.anyBins ?? [];
|
const requiredAnyBins = entry.metadata?.requires?.anyBins ?? [];
|
||||||
@ -237,6 +244,7 @@ function buildSkillStatus(
|
|||||||
primaryEnv: entry.metadata?.primaryEnv,
|
primaryEnv: entry.metadata?.primaryEnv,
|
||||||
emoji,
|
emoji,
|
||||||
homepage,
|
homepage,
|
||||||
|
repository,
|
||||||
always,
|
always,
|
||||||
disabled,
|
disabled,
|
||||||
blockedByAllowlist,
|
blockedByAllowlist,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { resolveSkillInvocationPolicy } from "./frontmatter.js";
|
import { resolveMoltbotMetadata, resolveSkillInvocationPolicy } from "./frontmatter.js";
|
||||||
|
|
||||||
describe("resolveSkillInvocationPolicy", () => {
|
describe("resolveSkillInvocationPolicy", () => {
|
||||||
it("defaults to enabled behaviors", () => {
|
it("defaults to enabled behaviors", () => {
|
||||||
@ -18,3 +18,54 @@ describe("resolveSkillInvocationPolicy", () => {
|
|||||||
expect(policy.disableModelInvocation).toBe(true);
|
expect(policy.disableModelInvocation).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("resolveMoltbotMetadata", () => {
|
||||||
|
it("extracts repository from metadata", () => {
|
||||||
|
const frontmatter = {
|
||||||
|
metadata: JSON.stringify({
|
||||||
|
moltbot: {
|
||||||
|
repository: "https://github.com/example/skill-repo",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = resolveMoltbotMetadata(frontmatter);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result?.repository).toBe("https://github.com/example/skill-repo");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("extracts homepage and repository together", () => {
|
||||||
|
const frontmatter = {
|
||||||
|
metadata: JSON.stringify({
|
||||||
|
moltbot: {
|
||||||
|
homepage: "https://example.com",
|
||||||
|
repository: "https://github.com/example/skill-repo",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = resolveMoltbotMetadata(frontmatter);
|
||||||
|
expect(result?.homepage).toBe("https://example.com");
|
||||||
|
expect(result?.repository).toBe("https://github.com/example/skill-repo");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined repository when not present", () => {
|
||||||
|
const frontmatter = {
|
||||||
|
metadata: JSON.stringify({
|
||||||
|
moltbot: {
|
||||||
|
emoji: "🔧",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = resolveMoltbotMetadata(frontmatter);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result?.repository).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for missing metadata", () => {
|
||||||
|
const frontmatter = { name: "test-skill" };
|
||||||
|
const result = resolveMoltbotMetadata(frontmatter);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -98,6 +98,7 @@ export function resolveMoltbotMetadata(
|
|||||||
always: typeof metadataObj.always === "boolean" ? metadataObj.always : undefined,
|
always: typeof metadataObj.always === "boolean" ? metadataObj.always : undefined,
|
||||||
emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : undefined,
|
emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : undefined,
|
||||||
homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : undefined,
|
homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : undefined,
|
||||||
|
repository: typeof metadataObj.repository === "string" ? metadataObj.repository : undefined,
|
||||||
skillKey: typeof metadataObj.skillKey === "string" ? metadataObj.skillKey : undefined,
|
skillKey: typeof metadataObj.skillKey === "string" ? metadataObj.skillKey : undefined,
|
||||||
primaryEnv: typeof metadataObj.primaryEnv === "string" ? metadataObj.primaryEnv : undefined,
|
primaryEnv: typeof metadataObj.primaryEnv === "string" ? metadataObj.primaryEnv : undefined,
|
||||||
os: osRaw.length > 0 ? osRaw : undefined,
|
os: osRaw.length > 0 ? osRaw : undefined,
|
||||||
|
|||||||
@ -22,6 +22,8 @@ export type MoltbotSkillMetadata = {
|
|||||||
primaryEnv?: string;
|
primaryEnv?: string;
|
||||||
emoji?: string;
|
emoji?: string;
|
||||||
homepage?: string;
|
homepage?: string;
|
||||||
|
/** URL to the source repository (e.g., GitHub) for transparency and auditability. */
|
||||||
|
repository?: string;
|
||||||
os?: string[];
|
os?: string[];
|
||||||
requires?: {
|
requires?: {
|
||||||
bins?: string[];
|
bins?: string[];
|
||||||
|
|||||||
@ -484,6 +484,8 @@ export type SkillStatusEntry = {
|
|||||||
primaryEnv?: string;
|
primaryEnv?: string;
|
||||||
emoji?: string;
|
emoji?: string;
|
||||||
homepage?: string;
|
homepage?: string;
|
||||||
|
/** URL to the source repository (e.g., GitHub) for transparency and auditability. */
|
||||||
|
repository?: string;
|
||||||
always: boolean;
|
always: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
blockedByAllowlist: boolean;
|
blockedByAllowlist: boolean;
|
||||||
|
|||||||
@ -101,6 +101,25 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
|||||||
</span>
|
</span>
|
||||||
${skill.disabled ? html`<span class="chip chip-warn">disabled</span>` : nothing}
|
${skill.disabled ? html`<span class="chip chip-warn">disabled</span>` : nothing}
|
||||||
</div>
|
</div>
|
||||||
|
${skill.repository
|
||||||
|
? html`
|
||||||
|
<div style="margin-top: 6px;">
|
||||||
|
<a
|
||||||
|
href="${skill.repository}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="repo-link"
|
||||||
|
style="display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--link-color, #0969da); text-decoration: none;"
|
||||||
|
title="View source repository"
|
||||||
|
>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||||
|
</svg>
|
||||||
|
Source
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
${missing.length > 0
|
${missing.length > 0
|
||||||
? html`
|
? html`
|
||||||
<div class="muted" style="margin-top: 6px;">
|
<div class="muted" style="margin-top: 6px;">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user