Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,16 @@ scripts/generate-*.js # Build-time generation scripts
scripts/validate-skills.js # Structural validation (CI)
src/ # Astro site source
data/skills.ts # Build-time skill loader
data/constants.ts # Static data (API endpoints, frameworks)
data/constants.ts # Static data (frameworks list)
data/site.ts # Site URL and base path config
layouts/BaseLayout.astro # HTML shell, meta tags, JSON-LD
layouts/SiteLayout.astro # Shared header/nav/footer for main pages
components/BrowseTab.tsx # Preact island: search + skill grid
components/ApiTab.tsx # Preact island: API reference
components/ApiTab.tsx # Preact island: access/endpoints reference
components/SkillHeader.tsx # Preact island: skill detail header
pages/index.astro # Browse page
pages/how-it-works/ # How it works page (fully static)
pages/api/ # API reference page
pages/access/ # Access reference page (real endpoints)
pages/skills/[slug]/ # Dynamic skill pages (pre-rendered)
pages/skills/[slug].md.ts # Raw .md endpoint per skill
pages/llms.txt.ts # Skills index for agents
Expand Down
162 changes: 96 additions & 66 deletions src/components/ApiTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
import { useState } from "preact/hooks";
import { API_ENDPOINTS, SANS_FONT } from "../data/constants";
import { SANS_FONT } from "../data/constants";
import { BASE_PATH, SITE_URL } from "../data/site";
import CopyButton from "./CopyButton";

const RAW_BASE = "https://raw.githubusercontent.com/dfinity/icskills/main/skills";

const REAL_ENDPOINTS = [
{
label: "Single skill (markdown)",
url: `${SITE_URL}/skills/ckbtc.md`,
curl: `curl -sL ${SITE_URL}/skills/ckbtc.md`,
desc: "Raw SKILL.md for one skill. Drop it straight into agent context.",
contentType: "text/markdown",
},
{
label: "Single skill (GitHub raw)",
url: `${RAW_BASE}/ckbtc/SKILL.md`,
curl: `curl -sL ${RAW_BASE}/ckbtc/SKILL.md`,
desc: "Same content via GitHub raw URLs. Works without the site.",
contentType: "text/plain",
},
{
label: "Skill index",
url: `${SITE_URL}/llms.txt`,
curl: `curl -sL ${SITE_URL}/llms.txt`,
desc: "Short index with links to every skill. Follows the llms.txt convention.",
contentType: "text/plain",
},
{
label: "All skills (full)",
url: `${SITE_URL}/llms-full.txt`,
curl: `curl -sL ${SITE_URL}/llms-full.txt`,
desc: "Every skill concatenated into one file. For full context injection.",
contentType: "text/plain",
},
{
label: "Agent discovery",
url: `${SITE_URL}/.well-known/agent.json`,
curl: `curl -sL ${SITE_URL}/.well-known/agent.json`,
desc: "Machine-readable manifest. Lists all skills and endpoint URLs.",
contentType: "application/json",
},
];

function TerminalHeader({ title }: { title: string }) {
return (
Expand All @@ -17,84 +58,73 @@ function TerminalHeader({ title }: { title: string }) {
);
}

export default function ApiTab() {
const [expandedEndpoint, setExpandedEndpoint] = useState<number | null>(null);

export default function AccessTab() {
return (
<div style={{ maxWidth: "860px" }}>
<div style={{ marginBottom: "48px" }}>
<p style={{
fontSize: "clamp(18px, 3vw, 26px)", fontWeight: 700, color: "var(--text-primary)",
letterSpacing: "-0.5px", lineHeight: 1.4, margin: "0 0 12px 0",
}}>
REST API. No auth. No keys.
Get skills into your agent.
</p>
<p style={{
fontSize: "14px", color: "var(--text-faint)", margin: 0,
fontFamily: SANS_FONT,
}}>
Base URL: <code style={{
color: "var(--accent-text)",
background: `rgba(var(--accent-rgb),0.1)`,
padding: "2px 8px", borderRadius: "3px",
}}>https://dfinity.github.io/icskills/api/v1</code>
No auth. No keys. Every skill is a static file you can fetch directly.
</p>
</div>

{/* Collapsible endpoints */}
{/* Real endpoints */}
<div style={{ display: "flex", flexDirection: "column", gap: "12px", marginBottom: "48px" }}>
{API_ENDPOINTS.map((endpoint, i) => (
<div key={i} className="api-endpoint-card" style={{
{REAL_ENDPOINTS.map((ep) => (
<div key={ep.label} style={{
border: "1px solid var(--border-default)",
borderRadius: "10px",
overflow: "hidden",
}}>
<div
role="button"
tabIndex={0}
aria-expanded={expandedEndpoint === i}
onClick={() => setExpandedEndpoint(expandedEndpoint === i ? null : i)}
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setExpandedEndpoint(expandedEndpoint === i ? null : i); } }}
style={{
padding: "14px 20px",
background: expandedEndpoint === i ? "var(--bg-input)" : "var(--bg-card-subtle)",
borderBottom: expandedEndpoint === i ? "1px solid var(--border-subtle)" : "none",
display: "flex", alignItems: "center", gap: "12px",
cursor: "pointer", transition: "background 0.15s",
}}
>
<div style={{
padding: "14px 20px",
background: "var(--bg-card-subtle)",
display: "flex", alignItems: "center", gap: "12px",
flexWrap: "wrap",
}}>
<span style={{
fontSize: "10px", fontWeight: 800, padding: "3px 10px",
background: endpoint.method === "POST" ? `rgba(var(--blue-rgb),0.15)` : `rgba(var(--green-rgb),0.15)`,
color: endpoint.method === "POST" ? "var(--accent-blue)" : "var(--green)",
background: `rgba(var(--green-rgb),0.15)`,
color: "var(--green)",
borderRadius: "4px", letterSpacing: "1px",
}}>{endpoint.method}</span>
<code style={{ fontSize: "14px", color: "var(--text-sub)", fontWeight: 600 }}>{endpoint.path}</code>
<span className="endpoint-desc" style={{
fontSize: "12px", color: "var(--text-faint)", marginLeft: "auto",
fontFamily: SANS_FONT, marginRight: "8px",
}}>{endpoint.desc}</span>
}}>GET</span>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: "13px", fontWeight: 700, color: "var(--text-primary)", marginBottom: "2px" }}>
{ep.label}
</div>
<div style={{ fontSize: "11px", color: "var(--text-faint)", fontFamily: SANS_FONT }}>
{ep.desc}
</div>
</div>
<span style={{
fontSize: "16px", color: "var(--text-ghost)",
transform: expandedEndpoint === i ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.2s", lineHeight: 1,
}}>{"\u25BE"}</span>
fontSize: "9px", padding: "2px 8px",
background: `rgba(var(--accent-rgb),0.08)`,
border: `1px solid rgba(var(--accent-rgb),0.15)`,
borderRadius: "3px", color: "var(--accent-text)",
textTransform: "uppercase", letterSpacing: "1px",
whiteSpace: "nowrap",
}}>{ep.contentType}</span>
</div>
<div style={{
padding: "10px 20px",
background: "var(--bg-code)",
display: "flex", alignItems: "center", gap: "8px",
}}>
<code style={{
flex: 1, fontSize: "11px", color: "var(--accent)",
whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
minWidth: 0,
}}>{ep.curl}</code>
<CopyButton text={ep.curl} />
</div>
{expandedEndpoint === i && (
<div style={{
padding: "16px 20px",
background: "var(--bg-response)",
}}>
<div style={{
fontSize: "10px", color: "var(--text-ghost)", textTransform: "uppercase",
letterSpacing: "1px", marginBottom: "8px",
}}>Response</div>
<pre style={{
fontSize: "11px", color: "var(--text-dim)", margin: 0,
whiteSpace: "pre-wrap", lineHeight: 1.6,
}}>{endpoint.response}</pre>
</div>
)}
</div>
))}
</div>
Expand All @@ -118,14 +148,14 @@ export default function ApiTab() {
color: "var(--text-tertiary)", margin: 0,
whiteSpace: "pre-wrap",
}}>
<span style={{color:"var(--text-faint)"}}># Get a skill as JSON</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{" dfinity.github.io/icskills/api/v1/skills/ckbtc\n\n"}
<span style={{color:"var(--text-faint)"}}># Get raw markdown for agent context</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{" dfinity.github.io/icskills/api/v1/skills/ckbtc/raw\n\n"}
<span style={{color:"var(--text-faint)"}}># Search for a skill</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{" dfinity.github.io/icskills/api/v1/skills/search?q=token\n\n"}
<span style={{color:"var(--text-faint)"}}># Get multiple at once</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{' -X POST dfinity.github.io/icskills/api/v1/skills/batch \\\n -d \'{"ids":["ckbtc","icrc-ledger","wallet"]}\''}</pre>
<span style={{color:"var(--text-faint)"}}>{"# Fetch a skill and paste into your agent"}</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{` -sL ${RAW_BASE}/ckbtc/SKILL.md\n\n`}
<span style={{color:"var(--text-faint)"}}>{"# Get the skill as a served .md file"}</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{` -sL ${SITE_URL}/skills/ckbtc.md\n\n`}
<span style={{color:"var(--text-faint)"}}>{"# Get the full skill index"}</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{` -sL ${SITE_URL}/llms.txt\n\n`}
<span style={{color:"var(--text-faint)"}}>{"# All skills in one file (for full context injection)"}</span>{"\n"}
<span style={{color:"var(--accent-text)"}}>curl</span>{` -sL ${SITE_URL}/llms-full.txt`}</pre>
</div>
</div>

Expand All @@ -134,9 +164,9 @@ export default function ApiTab() {
display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "8px",
}}>
{[
{ title: "No auth needed", desc: "Open API. No keys, no signup, no rate limits for normal use." },
{ title: "JSON + Markdown", desc: "Structured JSON for programmatic use. Raw markdown for context injection." },
{ title: "Always current", desc: "Skills update when icp-cli or canister IDs change. Versioned." },
{ title: "No auth needed", desc: "Open access. No keys, no signup. Every URL returns content directly." },
{ title: "Plain markdown", desc: "Skills are markdown files. Paste into any agent context, rules file, or system prompt." },
{ title: "Always current", desc: "Skills update when canister IDs or APIs change. Versioned in frontmatter." },
].map((note) => (
<div key={note.title} style={{
padding: "20px",
Expand Down
4 changes: 2 additions & 2 deletions src/components/BrowseTab.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useMemo } from "preact/hooks";
import type { Skill } from "../data/skills";
import { API_ENDPOINTS, SANS_FONT } from "../data/constants";
import { SANS_FONT } from "../data/constants";
import { BASE_PATH } from "../data/site";
import { CategoryIcon } from "./Icons";
import CopyButton from "./CopyButton";
Expand Down Expand Up @@ -61,8 +61,8 @@ export default function BrowseTab({ skills }: Props) {
<div className="hero-stats" style={{ display: "flex", gap: "32px", marginTop: "32px" }}>
{[
{ val: skills.length, label: "Skills" },
{ val: API_ENDPOINTS.length, label: "API Routes" },
{ val: TOTAL_ENDPOINTS, label: "Operations" },
{ val: "5", label: "Endpoints" },
{ val: "0", label: "Hallucinations" },
].map(({ val, label }) => (
<div key={label}>
Expand Down
109 changes: 1 addition & 108 deletions src/data/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,113 +2,6 @@

export const SANS_FONT = "'Inter', system-ui, sans-serif";

export const API_ENDPOINTS = [
{
method: "GET",
path: "/skills",
desc: "All skills, with metadata.",
response: `{
"skills": [
{
"id": "ckbtc",
"name": "ckBTC Integration",
"version": "2.1.0",
"category": "defi",
"dependencies": ["icrc-ledger", "wallet"],
"updated": "2026-02-24"
},
...
]
}`,
},
{
method: "GET",
path: "/skills/{id}",
desc: "Full skill. This is the main one.",
response: `{
"id": "ckbtc",
"version": "2.1.0",
"what": "ckBTC is chain-key Bitcoin on the IC...",
"prerequisites": {
"icp-cli": ">=0.1.0",
"language": ["motoko", "rust"],
"skills": ["icrc-ledger", "wallet"]
},
"pitfalls": [
"DO NOT use the old minter canister ID",
"Fee is 10 satoshis, not 0",
"Must use subaccounts for user deposits"
],
"steps": [...],
"verification": [
{
"run": "icp canister call ckbtc_ledger icrc1_balance_of ...",
"expect": "(nat)"
}
]
}`,
},
{
method: "GET",
path: "/skills/{id}/raw",
desc: "Raw SKILL.md. Drop it straight into agent context.",
response: `# ckBTC Integration
## SKILL.md v2.1.0

## What this is
ckBTC is chain-key Bitcoin...

## Prerequisites
- icp-cli >= 0.1.0
...

## \u26A0 Agent Mistakes
- DO NOT use the old minter canister ID
...`,
},
{
method: "GET",
path: "/skills/{id}/deps",
desc: "Dependency tree. What else the agent needs to read first.",
response: `{
"skill": "ckbtc",
"requires": ["icrc-ledger", "wallet"],
"tree": {
"icrc-ledger": { "requires": [] },
"wallet": { "requires": [] }
}
}`,
},
{
method: "GET",
path: "/skills/search?q={query}",
desc: "Search. For when the agent knows the task but not the skill name.",
response: `{
"query": "bitcoin",
"results": [
{ "id": "ckbtc", "name": "ckBTC Integration", "relevance": 0.97 },
{ "id": "evm-rpc", "name": "EVM RPC Integration", "relevance": 0.31 }
]
}`,
},
{
method: "POST",
path: "/skills/batch",
desc: "Multiple skills in one call. Send an array of IDs.",
response: `// Request body:
{ "ids": ["ckbtc", "icrc-ledger", "wallet"] }

// Response:
{
"skills": [
{ "id": "ckbtc", "version": "2.1.0", ... },
{ "id": "icrc-ledger", "version": "2.3.0", ... },
{ "id": "wallet", "version": "1.4.0", ... }
]
}`,
},
];

export const FRAMEWORKS = [
{ name: "Claude", note: "Skills as context", color: "#D97757" },
{ name: "ChatGPT", note: "Function calling", color: "#10a37f" },
Expand All @@ -119,7 +12,7 @@ export const FRAMEWORKS = [
{ name: "Claude Code", note: "SKILL.md files", color: "#D97757" },
{ name: "OpenCode", note: "Remote instructions", color: "#00DC82" },
{ name: "OpenClaw", note: "Skills marketplace", color: "#EF4444" },
{ name: "Your Agent", note: "REST API", color: "#fbbf24" },
{ name: "Your Agent", note: "curl + paste", color: "#fbbf24" },
];

export const FW_LIGHT_COLORS: Record<string, string> = {
Expand Down
4 changes: 2 additions & 2 deletions src/layouts/SiteLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {
canonicalUrl?: string;
ogTitle?: string;
ogDescription?: string;
activeTab: "browse" | "how-it-works" | "api";
activeTab: "browse" | "how-it-works" | "access";
maxWidth?: string;
}

Expand All @@ -27,7 +27,7 @@ const {
const tabs = [
{ id: "browse", label: "browse", href: `${BASE_PATH}/` },
{ id: "how-it-works", label: "how it works", href: `${BASE_PATH}/how-it-works/` },
{ id: "api", label: "api", href: `${BASE_PATH}/api/` },
{ id: "access", label: "access", href: `${BASE_PATH}/access/` },
] as const;
---

Expand Down
Loading