diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index c90448e87..b65cb113d 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -4900,5 +4900,27 @@ "status": "ready" } } + }, + "claude-code-skills": { + "title": "Claude Code skills", + "category": "guides", + "hero": { + "file": "assets/media/claude-code-skills/hero.png", + "alt": "Title card on white. Claude Code skills in large ink type, a kicker reading What they are, where they live, how to write one, a Checked 14 Oct 2026 pill and a yellow folder with a SKILL.md sheet sticking out.", + "prompt": "Still of the hero scene in assets/media/claude-code-skills/motion.js.", + "status": "ready" + }, + "inline": {} + }, + "claude-code-router": { + "title": "Claude Code Router", + "category": "concepts", + "hero": { + "file": "assets/media/claude-code-router/hero.png", + "alt": "Title card on white. Claude Code Router in large ink type, with a kicker and a Checked 14 Oct 2026 chip, beside a paper plane trailing a dotted loop.", + "prompt": "Still of the hero scene in assets/media/claude-code-router/motion.js.", + "status": "ready" + }, + "inline": {} } } diff --git a/blog/src/assets/media/claude-code-router/boat.png b/blog/src/assets/media/claude-code-router/boat.png new file mode 100644 index 000000000..f27454eb3 Binary files /dev/null and b/blog/src/assets/media/claude-code-router/boat.png differ diff --git a/blog/src/assets/media/claude-code-router/chime.png b/blog/src/assets/media/claude-code-router/chime.png new file mode 100644 index 000000000..46afd53ab Binary files /dev/null and b/blog/src/assets/media/claude-code-router/chime.png differ diff --git a/blog/src/assets/media/claude-code-router/hero.png b/blog/src/assets/media/claude-code-router/hero.png new file mode 100644 index 000000000..8999aba3c Binary files /dev/null and b/blog/src/assets/media/claude-code-router/hero.png differ diff --git a/blog/src/assets/media/claude-code-router/motion.css b/blog/src/assets/media/claude-code-router/motion.css new file mode 100644 index 000000000..195502044 --- /dev/null +++ b/blog/src/assets/media/claude-code-router/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Claude Code Router". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/claude-code-router/motion.js b/blog/src/assets/media/claude-code-router/motion.js new file mode 100644 index 000000000..77236b862 --- /dev/null +++ b/blog/src/assets/media/claude-code-router/motion.js @@ -0,0 +1,313 @@ +/* Bright coded scenes for "Claude Code Router". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 31, "", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + }; + + const lerp = (a, b, k) => a + (b - a) * k; + /* a rounded label sized to its words, centred on 0,0 of its group */ + const pill = (g, s, fill, fs = 24) => { + const r = el("rect", { y: -fs, height: fs * 2, rx: fs, fill, stroke: INK, "stroke-width": 3 }, g), t = txt(g, 0, fs * 0.36, s, { "text-anchor": "middle", "font-size": fs, "font-weight": 700 }); + const w = t.getComputedTextLength() + fs * 1.4; r.setAttribute("x", -w / 2); r.setAttribute("width", w); + return w; + }; + /* a wavy line from x0 to x1 at height y, n waves of height a */ + const wave = (x0, x1, y, a = 8, n = 2) => { const st = (x1 - x0) / (n * 2); let d = `M${x0} ${y}`; for (let i = 0; i < n * 2; i++) d += ` q${st / 2} ${i % 2 ? 2 * a : -2 * a} ${st} 0`; return d; }; + /* keyframes [[t, x, y], ...]; moves between different stops lift in a small arc */ + const kf = (t, K, lift = 0) => { + if (t <= K[0][0]) return [K[0][1], K[0][2]]; + for (let i = 1; i < K.length; i++) if (t < K[i][0]) { const a = K[i - 1], b = K[i], k = io(seg(t, a[0], b[0])), mv = a[1] !== b[1] || a[2] !== b[2]; return [lerp(a[1], b[1], k), lerp(a[2], b[2], k) - (mv ? lift * Math.sin(Math.PI * k) : 0)]; } + const L = K[K.length - 1]; return [L[1], L[2]]; + }; + + const SC = {}; + + /* ---- 1. a switchboard: one Claude Code jack, a patch cord, four provider jacks ---- */ + SC.switchboard = { + dur: 12, still: 9.2, + build(svg, api) { + txt(svg, 44, 62, "What is Claude Code Router?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a provider", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + el("rect", { x: 40, y: 90, width: 720, height: 394, rx: 24, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 400, 133, "Claude Code Router :3456", { "text-anchor": "middle", "font-family": MONO, "font-size": 24, "font-weight": 700 }); + [[64, 122], [736, 122]].forEach(([x, y]) => { el("circle", { cx: x, cy: y, r: 7, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); el("path", { d: `M${x - 4} ${y} H${x + 4}`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, svg); }); + el("rect", { x: 62, y: 156, width: 676, height: 306, rx: 16, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const AX = 168, AY = 318, JX = 516, JY = [200, 270, 340, 410], REST = [330, 432]; + const NAMES = ["OpenRouter", "DeepSeek", "Moonshot", "Mistral"]; + const jack = (g, x, y) => { el("circle", { cx: x, cy: y, r: 18, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); el("circle", { cx: x, cy: y, r: 7, fill: INK }, g); }; + txt(svg, AX, AY - 54, "Claude Code", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + el("circle", { cx: AX, cy: AY, r: 29, fill: "none", stroke: MINT, "stroke-width": 6 }, svg); + jack(svg, AX, AY); + let user = null, cur = REST.slice(); + const rings = JY.map((y, i) => { + const g = el("g", {}, svg); + el("rect", { x: JX - 36, y: y - 32, width: 238, height: 64, rx: 18, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + const r = el("circle", { cx: JX, cy: y, r: 29, fill: "none", stroke: MINT, "stroke-width": 6 }, g); + jack(g, JX, y); + txt(g, JX + 38, y + 9, NAMES[i], { "font-size": 26, "font-weight": 700 }); + button(g, `${NAMES[i]}: one of the hosted providers the README names`, () => { user = { from: cur.slice(), i, t0: api.now() }; api.poke(); }); + return r; + }); + const cordO = el("path", { fill: "none", stroke: INK, "stroke-width": 12, "stroke-linecap": "round", "pointer-events": "none" }, svg); + const cordI = el("path", { fill: "none", stroke: BLUE, "stroke-width": 6, "stroke-linecap": "round", "pointer-events": "none" }, svg); + const dots = [0, 1, 2].map(() => el("circle", { r: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5, "pointer-events": "none" }, svg)); + const plug = (x, y) => { const g = el("g", { "pointer-events": "none" }, svg); el("circle", { r: 15, fill: BLUE, stroke: INK, "stroke-width": 3 }, g); el("circle", { r: 5, fill: "#fff", stroke: INK, "stroke-width": 2 }, g); tf(g, x, y); return g; }; + plug(AX, AY); + const pb = plug(...REST); + const K = [[0, ...REST], [1, ...REST], [2, JX, JY[1]], [5.6, JX, JY[1]], [6.6, JX, JY[0]], [10.6, JX, JY[0]], [11.4, ...REST]]; + const cap = strip(svg, 508, 740); + return (t, now) => { + let p, on = -1; + if (user) { + const k = io(cl((now - user.t0) / 0.6)); + p = [lerp(user.from[0], JX, k), lerp(user.from[1], JY[user.i], k) - 50 * Math.sin(Math.PI * k)]; + if (k >= 1) on = user.i; + } else { + p = kf(t, K, 60); + if (t >= 2 && t < 5.6) on = 1; else if (t >= 6.6 && t < 10.6) on = 0; + } + cur = p; + tf(pb, p[0], p[1]); + const s = Math.max(28, Math.min(150, (452 - Math.max(AY, p[1])) / 0.75)); + const P = [[AX, AY], [AX + 60, AY + s], [p[0] - 60, p[1] + s], p]; + const d = `M${AX} ${AY} C${P[1][0]} ${P[1][1]} ${P[2][0]} ${P[2][1]} ${p[0]} ${p[1]}`; + cordO.setAttribute("d", d); cordI.setAttribute("d", d); + const bz = (u, j) => { const v = 1 - u; return v * v * v * P[0][j] + 3 * v * v * u * P[1][j] + 3 * v * u * u * P[2][j] + u * u * u * P[3][j]; }; + dots.forEach((c, k) => { const u = (((user ? now : t) * 0.42 + k / 3) % 1) * 0.86 + 0.07; c.setAttribute("cx", bz(u, 0)); c.setAttribute("cy", bz(u, 1)); c.setAttribute("opacity", on >= 0 ? 1 : 0); }); + rings.forEach((r, i) => r.setAttribute("opacity", i === on ? 1 : 0)); + cap.textContent = user ? (on >= 0 ? `${NAMES[on]}: one of the hosted providers the README names` : "CCR forwards each one to the provider you set up") : on === 1 ? "Claude Code sends its requests to CCR on your machine" : on === 0 ? "CCR forwards each one to the provider you set up" : "The README: “one stable local endpoint”"; + }; + }, + }; + + /* ---- 2. a pinwheel keeps turning when one breeze stops and another starts from the other side ---- */ + SC.pinwheel = { + dur: 12, still: 9.2, + build(svg) { + txt(svg, 44, 62, "How does Claude Code Router work?", { "font-size": 32, "font-weight": 700 }); + const CX = 400, CY = 262, R = 140, G = 480; + el("path", { d: `M30 ${G} H770`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("path", { d: `M${CX - 70} ${G} Q${CX} ${G - 34} ${CX + 70} ${G} Z`, fill: MINT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("rect", { x: CX - 8, y: CY, width: 16, height: G - CY - 14, rx: 8, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + const YS = [178, 232, 286, 340, 394]; + const breeze = (side, label, fill) => { + const g = el("g", {}, svg), x0 = side < 0 ? 40 : 760, x1 = side < 0 ? 244 : 556; + const lines = YS.map((y, k) => { const sh = k % 2 ? 40 : 0, a = side < 0 ? x0 + sh : x0 - sh; return el("path", { d: wave(a, x1, y, 7, 2), fill: "none", stroke: side < 0 ? BLUE : LIL, "stroke-width": 5, "stroke-linecap": "round", "stroke-dasharray": "44 22" }, g); }); + const leaves = [0, 1, 2].map((k) => { const n = el("path", { d: "M-10 0 Q0 -9 10 0 Q0 9 -10 0 Z", fill: k === 1 ? YEL : MINT, stroke: INK, "stroke-width": 2.5 }, g); return { n, k }; }); + const lab = el("g", { transform: `translate(${side < 0 ? 142 : 658} 128)` }, svg); + pill(lab, label, fill, 26); + return { g, lines, leaves, x0, x1, side, box: lab.firstChild, word: lab.lastChild, fill }; + }; + const B = [breeze(-1, "retry", SKY), breeze(1, "fall back", LIL)]; + const wheel = el("g", {}, svg), COL = [YEL, BLUE, MINT, LIL]; + for (let i = 0; i < 4; i++) { + const b = el("g", { transform: `rotate(${i * 90})` }, wheel); + el("path", { d: `M0 0 L0 ${-R} C${R * 0.48} ${-R * 0.94} ${R * 0.74} ${-R * 0.52} ${R * 0.64} ${-R * 0.1} Z`, fill: COL[i], stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, b); + el("path", { d: `M0 0 L0 ${-R} L${R * 0.3} ${-R * 0.4} Z`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, b); + } + el("circle", { r: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, wheel); + el("circle", { r: 4, fill: INK }, wheel); + const b1 = (t) => seg(t, 0.2, 1) * (1 - seg(t, 4.4, 5.2)), b2 = (t) => seg(t, 5.8, 6.6) * (1 - seg(t, 11.2, 11.9)); + const w = (t) => 50 + 330 * Math.max(b1(t), b2(t)); + const AN = [0]; for (let i = 1; i <= 1200; i++) AN.push(AN[i - 1] + w((i - 0.5) / 100) / 100); + const fit = (Math.round(AN[1200] / 90) * 90) / AN[1200]; + const cap = strip(svg, 508, 740); + return (t) => { + const i = Math.floor(cl(t, 0, 11.99) * 100), ang = (AN[i] + (AN[i + 1] - AN[i]) * (t * 100 - i)) * fit; + tf(wheel, CX, CY, 1, ang); + [b1(t), b2(t)].forEach((o, j) => { + const b = B[j]; + const live = o > 0.5; + b.box.setAttribute("fill", live ? b.fill : SOFT); b.box.setAttribute("stroke", live ? INK : LIL); b.word.setAttribute("fill", live ? INK : MUTE); + b.lines.forEach((l, k) => { l.setAttribute("stroke-dashoffset", -((t * 120 * o + k * 17) % 66)); l.setAttribute("stroke", o > 0.05 ? (b.side < 0 ? BLUE : LIL) : LIL); l.setAttribute("opacity", o > 0.05 ? 0.4 + 0.6 * o : 0.4); }); + b.leaves.forEach((f) => { const u = (t * 0.55 + f.k / 3) % 1, x = lerp(b.x0, b.x1, u), y = YS[f.k * 2] - 26 + Math.sin(u * 9 + f.k) * 10; tf(f.n, x, y, 1, u * 400 * b.side); f.n.setAttribute("opacity", Math.sin(u * Math.PI) * o); }); + }); + cap.textContent = t >= 4.8 && t < 11.4 ? "Credential pools and key rotation keep requests running" : "Retry, then fall back to models in a set order"; + }; + }, + }; + + /* ---- 3. a wind chime hangs from one bar; a breeze touches each provider tube in turn ---- */ + SC.chime = { + dur: 12, still: 9.6, + build(svg, api) { + txt(svg, 44, 62, "Which models and providers can it use?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 44, 102, "tap a tube", { "font-size": 20, fill: MUTE, "font-weight": 500 }); + const X = [122, 233, 344, 456, 567, 678], L = [222, 158, 208, 144, 194, 130], BAR = 180, TOP = 214; + const NAMES = ["OpenRouter", "DeepSeek", "SiliconFlow", "Moonshot", "Mistral", "Z.AI"], COL = [SKY, LIL, MINT, YEL, BLUE, SKY]; + const YB = [286, 336, 386]; + const bz = YB.map((y, k) => el("path", { d: wave(0, 220 - k * 30, y, 7, 2), fill: "none", stroke: LIL, "stroke-width": 5, "stroke-linecap": "round" }, svg)); + el("path", { d: `M96 142 L400 104 L704 142`, stroke: INK, "stroke-width": 2.5, fill: "none", "stroke-linejoin": "round" }, svg); + el("circle", { cx: 400, cy: 96, r: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const strk = el("g", {}, svg); + el("path", { d: "M0 0 V112", stroke: INK, "stroke-width": 2.5 }, strk); + el("circle", { cy: 134, r: 22, fill: SOFT, stroke: INK, "stroke-width": 3 }, strk); + let user = null; + const tubes = X.map((x, i) => { + const g = el("g", {}, svg), h = TOP - BAR; + el("rect", { x: -64, y: h - 8, width: 128, height: L[i] + 64, rx: 18, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + el("path", { d: `M0 0 V${h}`, stroke: INK, "stroke-width": 2.5 }, g); + el("rect", { x: -21, y: h, width: 42, height: L[i], rx: 10, fill: COL[i], stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M-8 ${h + 16} V${h + L[i] - 16}`, stroke: "#fff", "stroke-width": 5, "stroke-linecap": "round" }, g); + const arcs = el("g", { transform: `translate(30 ${h + L[i] * 0.42})` }, g); + [14, 28].forEach((r) => el("path", { d: `M${r * 0.5} ${-r * 0.87} A${r} ${r} 0 0 1 ${r * 0.5} ${r * 0.87}`, fill: "none", stroke: BLUE, "stroke-width": 4, "stroke-linecap": "round" }, arcs)); + txt(g, 0, h + L[i] + 36, NAMES[i], { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, `${NAMES[i]}: a hosted provider the README names`, () => { user = { i, t0: api.now() }; api.poke(); }); + return { g, arcs, x, deg: 57.3 / (h + L[i] + 36), a: 0.6 + ((x + 300) / 1400) * 5, b: 6.6 + ((800 - x + 300) / 1400) * 4.4 }; + }); + el("rect", { x: 70, y: 142, width: 660, height: 38, rx: 12, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 400, 171, "CCR", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + const cap = strip(svg, 508, 740); + const sw = (s, A) => (s < 0 ? 0 : A * Math.exp(-1.2 * s) * Math.sin(6.2 * s)); + return (t, now) => { + const hx = t < 6.4 ? lerp(-300, 1100, seg(t, 0.6, 5.6)) : lerp(1100, -300, seg(t, 6.6, 11)); + const dir = t < 6.4 ? 1 : -1; + bz.forEach((p, k) => { const len = 220 - k * 30, x = dir > 0 ? hx - len : hx; p.setAttribute("transform", `translate(${x} ${Math.sin(t * 3 + k) * 4})`); p.setAttribute("opacity", t < 6.4 ? 1 - seg(t, 5.4, 5.6) : 1 - seg(t, 10.8, 11)); }); + let last = -1; + tubes.forEach((u, i) => { + const sA = t - u.a, sB = t - u.b, sU = user && user.i === i ? now - user.t0 : -1; + const ang = (sw(sA, 24) + sw(sB, -18) + sw(sU, 26)) * u.deg; + tf(u.g, u.x, BAR, 1, ang); + const ring = Math.max(sA >= 0 && sA < 1 ? Math.sin((sA / 1) * Math.PI) : 0, sB >= 0 && sB < 1 ? Math.sin(sB * Math.PI) : 0, sU >= 0 && sU < 1 ? Math.sin(sU * Math.PI) : 0); + u.arcs.setAttribute("opacity", ring); + if (sA >= 0) last = i; + }); + strk.setAttribute("transform", `translate(400 ${BAR}) rotate(${Math.sin(t * 2.4) * 3})`); + cap.textContent = user ? `${NAMES[user.i]}: a hosted provider the README names` : t >= 6.4 && t < 11.4 ? "Or your own endpoint: custom compatible providers" : "Hosted providers, our selection from the README"; + }; + }, + }; + + /* ---- 4. a paper boat passes a lock gate that opens part way; two tall flags stay on the bank ---- */ + SC.boat = { + dur: 12.6, still: 10, + build(svg) { + txt(svg, 44, 62, "What are the trade offs?", { "font-size": 32, "font-weight": 700 }); + const WY = 400, GL = 316, GR = 496, LIFT = 112, PT = 196; + const cloud = (x, y, s) => el("path", { d: "M-60 20 H60 A26 26 0 0 0 44 -22 A34 34 0 0 0 -16 -30 A30 30 0 0 0 -60 20 Z", fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round", transform: `translate(${x} ${y}) scale(${s})` }, svg); + const clouds = [cloud(640, 150, 1.1), cloud(150, 196, 0.85)]; + el("rect", { x: 30, y: 368, width: 740, height: 40, rx: 12, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 30, y: WY, width: 740, height: 86, rx: 14, fill: SKY, stroke: INK, "stroke-width": 3 }, svg); + const left = el("g", {}, svg); + const boat = el("g", {}, svg); + const FL = [[-80, 60, YEL, false], [-56, 120, BLUE, true], [56, 120, LIL, true], [80, 60, MINT, false]]; + const flag = (p, col) => { const g = el("g", {}, p); el("path", { d: "M0 0 V-62", stroke: INK, "stroke-width": 3.5, "stroke-linecap": "round" }, g); el("path", { d: "M0 -62 L34 -51 L0 -40 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); return g; }; + const flags = FL.map(([x, h, col, tall]) => { + const g = el("g", {}, boat); + el("path", { d: `M0 0 V${-(h - 62)}`, stroke: INK, "stroke-width": 3.5, "stroke-linecap": "round" }, g); + flag(el("g", { transform: `translate(0 ${-(h - 62)})` }, g), col); + g.setAttribute("transform", `translate(${x} -40)`); + return { g, x, h, col, tall }; + }); + el("path", { d: "M-46 -40 L0 -100 L46 -40 Z", fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, boat); + el("path", { d: "M-104 -42 H104 L72 0 H-72 Z", fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, boat); + txt(boat, 0, -13, "Claude Code", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + [[110, 432], [250, 456], [660, 440], [520, 462]].forEach(([x, y]) => el("path", { d: wave(x, x + 70, y, 4, 2), fill: "none", stroke: "#fff", "stroke-width": 4, "stroke-linecap": "round" }, svg)); + const panel = el("g", {}, svg); + el("rect", { x: GL, y: PT, width: GR - GL, height: WY - PT + 8, fill: SOFT, stroke: INK, "stroke-width": 3 }, panel); + [1, 2, 3, 4].forEach((k) => el("path", { d: `M${GL + k * 36} ${PT + 14} V${WY - 6}`, stroke: LIL, "stroke-width": 3, "stroke-linecap": "round" }, panel)); + [GL - 16, GR].forEach((x) => el("rect", { x, y: PT - 4, width: 16, height: 490 - PT, rx: 4, fill: YEL, stroke: INK, "stroke-width": 3 }, svg)); + el("rect", { x: GL - 24, y: 84, width: GR - GL + 48, height: PT - 84, rx: 14, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, (GL + GR) / 2, 152, "gateway", { "text-anchor": "middle", "font-size": 32, "font-weight": 700 }); + const bx = (t) => (t < 3.8 ? lerp(104, 168, oc(seg(t, 0.4, 2.6))) : lerp(168, 640, io(seg(t, 3.8, 8.8)))); + const lift = (t) => io(seg(t, 2.6, 3.6)) - io(seg(t, 11.4, 12.3)); + const BANK = [176, 232]; + const drops = flags.filter((f) => f.tall).map((f, k) => { + let tc = 99; for (let s = 3.8; s < 9; s += 0.005) if (bx(s) + f.x + 4 >= GL) { tc = s; break; } + const g = el("g", {}, left), inner = el("g", {}, g); + el("path", { d: `M0 0 V${-(f.h - 62)}`, stroke: INK, "stroke-width": 3.5, "stroke-linecap": "round" }, inner); + flag(el("g", { transform: `translate(0 ${-(f.h - 62)})` }, inner), f.col); + return { f, tc, g, to: BANK[f.x > 0 ? 1 : 0] }; + }); + const cap = strip(svg, 508, 740); + return (t) => { + const out = seg(t, 11.6, 12.3), x = bx(t), y = WY + 3 * Math.sin(t * 2.2); + tf(boat, x, y, 1, Math.sin(t * 1.7) * 1.5); + boat.setAttribute("opacity", seg(t, 0, 0.4) * (1 - out)); + panel.setAttribute("transform", `translate(0 ${-LIFT * lift(t)})`); + drops.forEach((d) => { + const on = t >= d.tc; + d.f.g.setAttribute("opacity", on ? 0 : 1); + d.g.setAttribute("opacity", on ? 1 - out : 0); + if (!on) return; + const k = seg(t, d.tc, d.tc + 0.8), sx = lerp(GL - 6, d.to, oc(k)), sy = lerp(WY - 40, 386, k * k) - 50 * Math.sin(Math.PI * k); + tf(d.g, sx, sy, 1, lerp(-40, d.f.x > 0 ? 6 : -6, oc(k)) + (k >= 1 ? Math.sin((t - d.tc) * 2) * 2 : 0)); + }); + clouds.forEach((c, i) => c.setAttribute("transform", `translate(${(i ? 150 : 640) + Math.sin(t * 0.5 + i * 2) * 14} ${i ? 196 : 150}) scale(${i ? 0.85 : 1.1})`)); + cap.textContent = t < 3.8 || t >= 12 ? "Claude Code gains capabilities with each release" : "“a gateway that doesn't forward them breaks the corresponding features”"; + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 104, "What it is, how it works and how to install it", { "font-size": 25, "font-weight": 500, fill: MUTE }); + txt(svg, 62, 206, "Claude Code", { "font-size": 82, "font-weight": 700 }); + txt(svg, 62, 298, "Router", { "font-size": 82, "font-weight": 700 }); + el("path", { d: "M68 326 H330", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 62, y: 372, width: 312, height: 56, rx: 28, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 218, 409, "Checked 14 Oct 2026", { "text-anchor": "middle", "font-size": 25, "font-weight": 700 }); + el("path", { d: "M560 470 C640 480 720 460 700 400 C684 350 616 372 636 418 C656 462 760 420 790 300", stroke: LIL, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-dasharray": "1 16" }, svg); + el("circle", { cx: 560, cy: 470, r: 11, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + const p = el("g", { transform: "translate(860 180) rotate(-24)" }, svg); + el("path", { d: "M0 0 L-190 -56 L-118 18 Z", fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, p); + el("path", { d: "M0 0 L-118 18 L-150 70 Z", fill: SKY, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, p); + el("path", { d: "M-118 18 L-130 44 L-150 70", fill: "none", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, p); + el("path", { d: "M0 0 L-190 -56 L-150 70", fill: "none", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round", "stroke-opacity": 0 }, p); + [[620, 150, 15, YEL], [890, 430, 18, MINT], [500, 380, 12, BLUE]].forEach(([x, y, r, col]) => el("path", { d: `M0 ${-r} Q0 0 ${r} 0 Q0 0 0 ${r} Q0 0 ${-r} 0 Q0 0 0 ${-r} Z`, fill: col, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", transform: `translate(${x} ${y})` }, svg)); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/claude-code-router/pinwheel.png b/blog/src/assets/media/claude-code-router/pinwheel.png new file mode 100644 index 000000000..8da7c8558 Binary files /dev/null and b/blog/src/assets/media/claude-code-router/pinwheel.png differ diff --git a/blog/src/assets/media/claude-code-router/switchboard.png b/blog/src/assets/media/claude-code-router/switchboard.png new file mode 100644 index 000000000..1e4409101 Binary files /dev/null and b/blog/src/assets/media/claude-code-router/switchboard.png differ diff --git a/blog/src/assets/media/claude-code-skills/bookmark.png b/blog/src/assets/media/claude-code-skills/bookmark.png new file mode 100644 index 000000000..3b7cd4c0d Binary files /dev/null and b/blog/src/assets/media/claude-code-skills/bookmark.png differ diff --git a/blog/src/assets/media/claude-code-skills/drawers.png b/blog/src/assets/media/claude-code-skills/drawers.png new file mode 100644 index 000000000..bb08f721f Binary files /dev/null and b/blog/src/assets/media/claude-code-skills/drawers.png differ diff --git a/blog/src/assets/media/claude-code-skills/hero.png b/blog/src/assets/media/claude-code-skills/hero.png new file mode 100644 index 000000000..e7f461e9f Binary files /dev/null and b/blog/src/assets/media/claude-code-skills/hero.png differ diff --git a/blog/src/assets/media/claude-code-skills/motion.css b/blog/src/assets/media/claude-code-skills/motion.css new file mode 100644 index 000000000..379b736ad --- /dev/null +++ b/blog/src/assets/media/claude-code-skills/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Claude Code skills". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/claude-code-skills/motion.js b/blog/src/assets/media/claude-code-skills/motion.js new file mode 100644 index 000000000..6c248430b --- /dev/null +++ b/blog/src/assets/media/claude-code-skills/motion.js @@ -0,0 +1,323 @@ +/* Bright coded scenes for "Claude Code skills". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 31, "", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + }; + + const lerp = (a, b, k) => a + (b - a) * k; + /* keyframes [[t, x, y], ...] eased between stops */ + const kf = (t, K) => { + if (t <= K[0][0]) return [K[0][1], K[0][2]]; + for (let i = 1; i < K.length; i++) if (t < K[i][0]) { const k = io(seg(t, K[i - 1][0], K[i][0])); return [lerp(K[i - 1][1], K[i][1], k), lerp(K[i - 1][2], K[i][2], k)]; } + const L = K[K.length - 1]; return [L[1], L[2]]; + }; + const tick = (g) => { el("circle", { r: 17, fill: MINT, stroke: INK, "stroke-width": 3 }, g); el("path", { d: "M-8 0 L-2 6 L8 -6", stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); }; + + const SC = {}; + + /* ---- 1. a rubber stamp inks once and prints the same checklist on three cards ---- */ + SC.stamp = { + dur: 11, still: 9, + build(svg) { + txt(svg, 44, 62, "What is a Claude Code skill?", { "font-size": 32, "font-weight": 700 }); + const F = 470, CX = [320, 500, 680], CW = 160, CT = 112, PX = 127, HOV = 300, PRESS = 436, PAD = 420; + el("path", { d: `M24 ${F} H776`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("rect", { x: 32, y: 432, width: 190, height: 38, rx: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 44, y: PAD, width: 166, height: 16, rx: 6, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const BARS = [62, 46, 56, 40]; + const cards = CX.map((cx) => { + el("rect", { x: cx - CW / 2 + 8, y: CT + 8, width: CW, height: F - CT - 8, rx: 14, fill: LIL }, svg); + el("rect", { x: cx - CW / 2, y: CT, width: CW, height: F - CT - 8, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const p = el("g", {}, svg); + el("rect", { x: cx - 64, y: 136, width: 128, height: 300, rx: 12, fill: "none", stroke: BLUE, "stroke-width": 3 }, p); + txt(p, cx, 180, "SKILL.md", { "text-anchor": "middle", "font-family": MONO, "font-size": 22, "font-weight": 700, fill: BLUE }); + el("path", { d: `M${cx - 48} 198 H${cx + 48}`, stroke: BLUE, "stroke-width": 3, "stroke-linecap": "round" }, p); + [240, 294, 348, 402].forEach((y, k) => { + el("rect", { x: cx - 48, y: y - 13, width: 26, height: 26, rx: 6, fill: "none", stroke: BLUE, "stroke-width": 3 }, p); + el("path", { d: `M${cx - 42} ${y} l5 6 l9 -12`, stroke: BLUE, "stroke-width": 3, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, p); + el("rect", { x: cx - 12, y: y - 6, width: BARS[k], height: 12, rx: 6, fill: BLUE }, p); + }); + const k = el("g", {}, svg); tick(k); + return { p, k, cx }; + }); + const st = el("g", { "pointer-events": "none" }, svg), body = el("g", {}, st); + el("rect", { x: -14, y: -108, width: 28, height: 58, fill: YEL, stroke: INK, "stroke-width": 3 }, body); + el("ellipse", { cx: 0, cy: -124, rx: 36, ry: 26, fill: LIL, stroke: INK, "stroke-width": 3 }, body); + el("rect", { x: -76, y: -56, width: 152, height: 44, rx: 8, fill: YEL, stroke: INK, "stroke-width": 3 }, body); + el("rect", { x: -54, y: -48, width: 108, height: 28, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, body); + txt(body, 0, -28, "SKILL.md", { "text-anchor": "middle", "font-family": MONO, "font-size": 17, "font-weight": 700 }); + el("rect", { x: -70, y: -14, width: 140, height: 14, rx: 4, fill: BLUE, stroke: INK, "stroke-width": 3 }, body); + const S0 = [1.9, 3.55, 5.2]; + const K = [[0, PX, PAD], [0.6, PX, PAD], [1.1, PX, HOV]]; + S0.forEach((s, i) => { K.push([s, CX[i], HOV], [s + 0.35, CX[i], PRESS], [s + 0.6, CX[i], PRESS], [s + 0.95, CX[i], HOV]); }); + K.push([7.1, PX, HOV], [7.45, PX, PAD]); + const cap = strip(svg, 508, 740); + return (t) => { + const out = seg(t, 10.3, 10.8); + const [x, y] = kf(t, K); + let q = Math.sin(seg(t, 7.4, 7.7) * Math.PI) * 0.6; + S0.forEach((s) => { q = Math.max(q, Math.sin(seg(t, s + 0.3, s + 0.65) * Math.PI)); }); + st.setAttribute("transform", `translate(${x} ${y})`); + body.setAttribute("transform", `scale(${1.2 * (1 + 0.05 * q)} ${1.2 * (1 - 0.08 * q)})`); + cards.forEach((c, i) => { + c.p.setAttribute("opacity", (t >= S0[i] + 0.5 ? 1 : 0) * (1 - out)); + const p = ob(seg(t, 6.6 + i * 0.25, 7 + i * 0.25)); + tf(c.k, c.cx + 66, CT + 4, cl(p, 0, 1.3)); c.k.setAttribute("opacity", (p <= 0.01 ? 0 : 1) * (1 - out)); + }); + cap.textContent = t < S0[0] || t >= 10.3 ? "“the same instructions, checklist, or multi-step procedure”" : t < 6.6 ? "Write the instructions once and reuse them" : "A skill's body “loads only when it's used”"; + }; + }, + }; + + /* ---- 2. a card catalogue: four drawers open in turn and each lifts one index card ---- */ + SC.drawers = { + dur: 12, still: 9.6, + build(svg, api) { + txt(svg, 44, 62, "Where do Claude Code skills live?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 100, "tap a drawer", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const D = [ + ["Personal", ["~/.claude/", "skills/", "/", "SKILL.md"], SKY, "Personal: all your projects on this machine"], + ["Project", [".claude/", "skills/", "/", "SKILL.md"], MINT, "Project: sessions in this repository"], + ["Plugin", ["/", "skills/", "/", "SKILL.md"], LIL, "Plugin: wherever the plugin is enabled"], + ["Enterprise", ["The managed", "settings", "directory"], YEL, "Enterprise: all users where your organization deploys it"], + ]; + const ST = 330, SH = 116, CY = ST + SH / 2, CLIP = 412, TOP = 126; + const defs = el("defs", {}, svg), cp = el("clipPath", { id: "mg-drawers-clip" }, defs); + el("rect", { x: 0, y: 0, width: 800, height: CLIP }, cp); + el("rect", { x: 60, y: 462, width: 22, height: 26, rx: 5, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 718, y: 462, width: 22, height: 26, rx: 5, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 40, y: 312, width: 720, height: 152, rx: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 30, y: 296, width: 740, height: 20, rx: 8, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + let pinned = -1; + const dr = D.map(([name, lines, col, line], i) => { + const sx = 56 + i * 176, cx = sx + 80; + el("rect", { x: sx, y: ST, width: 160, height: SH, rx: 8, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + const inside = el("path", { fill: SOFT, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, svg); + const wrap = el("g", { "clip-path": "url(#mg-drawers-clip)" }, svg), card = el("g", {}, wrap); + el("rect", { x: -78, y: 0, width: 156, height: 292, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, card); + el("path", { d: "M-76 18 H76", stroke: INK, "stroke-width": 2.5 }, card); + el("rect", { x: -76.5, y: 1.5, width: 153, height: 16, rx: 8, fill: col }, card); + el("path", { d: "M-78 18 H78", stroke: INK, "stroke-width": 2.5 }, card); + const mono = i < 3; + lines.forEach((s, k) => txt(card, -66, 52 + k * 33, s, mono ? { "font-family": MONO, "font-size": 16, "font-weight": 700 } : { "font-size": 19, "font-weight": 600 })); + el("circle", { cx: 0, cy: 196, r: 7, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, card); + const front = el("g", {}, svg); + el("rect", { x: sx - 6, y: ST - 6, width: 172, height: SH + 12, rx: 12, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, front); + el("rect", { x: sx, y: ST, width: 160, height: SH, rx: 8, fill: YEL, stroke: INK, "stroke-width": 3 }, front); + el("rect", { x: cx - 64, y: ST + 16, width: 128, height: 38, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, front); + txt(front, cx, ST + 42, name, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + el("rect", { x: cx - 28, y: ST + 76, width: 56, height: 18, rx: 9, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, front); + button(front, `${name}: ${lines.join("")}. ${line}`, () => { pinned = i; api.poke(); }); + return { inside, card, front, cx, sx, a: 0.7 + i * 1.9 }; + }); + const cap = strip(svg, 508, 740); + return (t) => { + let last = -1; + dr.forEach((d, i) => { + const pin = pinned === i; + let o = io(seg(t, d.a, d.a + 0.45)) * (1 - io(seg(t, 11.2, 11.7))); + let r = ob(seg(t, d.a + 0.4, d.a + 0.95)) * (1 - io(seg(t, 10.7, 11.2))); + if (pin) { o = 1; r = 1; } + if (t >= d.a && t < 10.7) last = i; + const s = 1 + 0.05 * o, dy = 30 * o, top = CY + dy - (SH / 2) * s, hw = 80 * s; + d.front.setAttribute("transform", `translate(${d.cx} ${CY + dy}) scale(${s}) translate(${-d.cx} ${-CY})`); + d.inside.setAttribute("d", `M${d.sx} ${ST} H${d.sx + 160} L${d.cx + hw} ${top + 1} H${d.cx - hw} Z`); + d.inside.setAttribute("opacity", o > 0.02 ? 1 : 0); + d.card.setAttribute("transform", `translate(${d.cx} ${CLIP - (CLIP - TOP) * r - (pin ? 10 : 0)})`); + }); + cap.textContent = pinned >= 0 ? D[pinned][3] : t >= 8 && t < 10.7 ? "Enterprise wins over personal, and personal over project" : last < 0 ? "Where you save a skill decides which sessions load it" : D[last][3]; + }; + }, + }; + + /* ---- 3. a book with bookmark tabs: a request lifts one tab and the book falls open there ---- */ + SC.bookmark = { + dur: 12, still: 9.4, + build(svg) { + txt(svg, 44, 62, "How does Claude decide to use a skill?", { "font-size": 32, "font-weight": 700 }); + const W = 360, H = 246, BY = 214, OX = [12, 126, 240], TW = 108; + const TABS = [["deploy"], ["summarize-", "changes"], ["last30days"]]; + const tabs = TABS.map((ls, i) => { + const g = el("g", {}, svg), r = el("rect", { x: 0, y: -62, width: TW, height: 82, rx: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + ls.forEach((s, k) => txt(g, TW / 2, ls.length > 1 ? -40 + k * 21 : -26, s, { "text-anchor": "middle", "font-size": 16, "font-weight": 700 })); + return { g, r, ox: OX[i] }; + }); + const page = el("g", {}, svg); + el("rect", { x: 8, y: 8, width: W, height: H, rx: 8, fill: SOFT, stroke: INK, "stroke-width": 3 }, page); + el("rect", { x: 0, y: 0, width: W, height: H, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, page); + txt(page, 28, 52, "## Current changes", { "font-family": MONO, "font-size": 17, "font-weight": 700 }); + el("rect", { x: 22, y: 70, width: 190, height: 34, rx: 8, fill: SOFT }, page); + txt(page, 32, 93, "!`git diff HEAD`", { "font-family": MONO, "font-size": 17, "font-weight": 700 }); + txt(page, 28, 142, "## Instructions", { "font-family": MONO, "font-size": 17, "font-weight": 700 }); + txt(page, 28, 178, "Summarize the changes", { "font-family": MONO, "font-size": 17, "font-weight": 500 }); + el("rect", { x: 28, y: 196, width: 270, height: 9, rx: 4.5, fill: LIL }, page); + el("rect", { x: 28, y: 214, width: 196, height: 9, rx: 4.5, fill: LIL }, page); + const inner = el("g", {}, svg); + el("rect", { x: -W - 8, y: 8, width: W, height: H, rx: 8, fill: SOFT, stroke: INK, "stroke-width": 3 }, inner); + el("rect", { x: -W, y: 0, width: W, height: H, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, inner); + txt(inner, -W + 28, 46, "description", { "font-family": MONO, "font-size": 17, "font-weight": 700, fill: MUTE }); + const hl = el("rect", { y: 186, height: 32, rx: 8, fill: YEL }, inner); + ["Summarizes uncommitted", "changes and flags", "anything risky.", "Use when the user"].forEach((s, k) => txt(inner, -W + 28, 84 + k * 30, s, { "font-size": 20, "font-weight": 600 })); + const a5 = txt(inner, -W + 28, 208, "asks", { "font-size": 20, "font-weight": 600 }); + const ax = -W + 28 + a5.getComputedTextLength() + 6; + const w5 = txt(inner, ax, 208, "what changed", { "font-size": 20, "font-weight": 600 }); + hl.setAttribute("x", ax - 5); hl.setAttribute("width", w5.getComputedTextLength() + 10); + const cover = el("g", {}, svg); + el("rect", { x: 0, y: 0, width: W, height: H, rx: 8, fill: BLUE, stroke: INK, "stroke-width": 3 }, cover); + el("path", { d: `M22 3 V${H - 3}`, stroke: INK, "stroke-width": 2.5 }, cover); + el("rect", { x: W / 2 - 118, y: 74, width: 236, height: 52, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, cover); + txt(cover, W / 2, 107, "~/.claude/skills/", { "text-anchor": "middle", "font-family": MONO, "font-size": 18, "font-weight": 700 }); + el("path", { d: `M70 170 H${W - 50} M70 194 H${W - 110}`, stroke: "#fff", "stroke-width": 5, "stroke-linecap": "round" }, cover); + const req = el("g", {}, svg); + el("rect", { x: 6, y: 6, width: 270, height: 56, rx: 14, fill: LIL }, req); + el("rect", { x: 0, y: 0, width: 270, height: 56, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, req); + txt(req, 135, 36, "What did I change?", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + const link = el("path", { fill: "none", stroke: LIL, "stroke-width": 4, "stroke-linecap": "round", "stroke-dasharray": "1 11" }, svg); + const cap = strip(svg, 508, 740); + return (t) => { + const c = io(seg(t, 10.5, 11.5)), o = io(seg(t, 3.5, 4.6)) - c, k = 1 - 2 * o, S = lerp(400 - W / 2, 400, o); + const lift = 20 * cl(ob(seg(t, 2.5, 2.9)), 0, 1.2) * (1 - c); + tabs.forEach((b, i) => { const up = i === 1 ? lift : 0; b.g.setAttribute("transform", `translate(${S + b.ox} ${BY - up})`); b.r.setAttribute("fill", i === 1 && t >= 2.5 && t < 11 ? SKY : YEL); }); + page.setAttribute("transform", `translate(${S} ${BY})`); + cover.setAttribute("transform", `translate(${S} ${BY}) scale(${Math.max(k, 0.001)} 1)`); cover.setAttribute("opacity", k > 0 ? 1 : 0); + inner.setAttribute("transform", `translate(${S} ${BY}) scale(${Math.max(-k, 0.001)} 1)`); inner.setAttribute("opacity", k < 0 ? 1 : 0); + hl.setAttribute("opacity", seg(t, 4.7, 5) * (1 - c)); + const rx = lerp(-300, 44, io(seg(t, 0.9, 1.5))) - 360 * io(seg(t, 10.3, 11)); + req.setAttribute("transform", `translate(${rx} 84)`); + const tx = S + OX[1] + TW / 2, ty = BY - 62 - lift - 4; + link.setAttribute("d", `M${rx + 276} 112 Q${tx - 10} 106 ${tx} ${ty}`); + link.setAttribute("opacity", seg(t, 2, 2.3) * (1 - c)); + cap.textContent = t < 0.9 || t >= 11 ? "Descriptions sit in context during a regular session" : t < 3.5 ? "Claude reads each skill's description" : "“full skill content only loads when invoked”"; + }; + }, + }; + + /* ---- 4. one jar on a central spice rack, copied onto four small racks ---- */ + SC.rack = { + dur: 11.5, still: 9.2, + build(svg, api) { + txt(svg, 44, 62, "How do you share skills across tools?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 100, "tap the jar", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const jar = (g) => { + el("rect", { x: -38, y: -122, width: 76, height: 22, rx: 6, fill: LIL, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: -48, y: -104, width: 96, height: 104, rx: 16, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: -40, y: -56, width: 80, height: 49, rx: 10, fill: MINT }, g); + const lab = el("rect", { x: -44, y: -94, width: 88, height: 34, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + txt(g, 0, -71, "SKILL.md", { "text-anchor": "middle", "font-family": MONO, "font-size": 16, "font-weight": 700 }); + return lab; + }; + const rack = (cx, sy, w, h, name) => { + el("rect", { x: cx - w / 2 + 10, y: sy - h, width: w - 20, height: h, rx: 12, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: cx - w / 2, y: sy, width: w, height: 16, rx: 6, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + const t0 = txt(svg, cx, sy + 49, name, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }), pw = t0.getComputedTextLength() + 30; + el("rect", { x: cx - pw / 2, y: sy + 24, width: pw, height: 36, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg).after(t0); + }; + const rail = (cx, sy, w) => { + el("rect", { x: cx - w / 2 + 6, y: sy - 34, width: w - 12, height: 9, rx: 4.5, fill: YEL, stroke: INK, "stroke-width": 2.5 }, svg); + [cx - w / 2 + 6, cx + w / 2 - 16].forEach((x) => el("rect", { x, y: sy - 40, width: 10, height: 42, rx: 4, fill: YEL, stroke: INK, "stroke-width": 2.5 }, svg)); + }; + const CS = 250, SS = 436, SX = [115, 305, 495, 685], SW = 164, CSC = 1.15; + const TOOLS = ["Claude Code", "Codex", "Cursor", "OpenCode"]; + rack(400, CS, 280, 120, "Skills Hub"); + const main = el("g", {}, svg); + el("rect", { x: 340, y: CS - 150, width: 120, height: 156, rx: 16, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, main); + const mj = el("g", { transform: `translate(400 ${CS}) scale(${CSC})` }, main), mlab = jar(mj); + rail(400, CS, 280); + const ghosts = []; + SX.forEach((x, i) => { + rack(x, SS, SW, 108, TOOLS[i]); + const gh = el("g", { transform: `translate(${x} ${SS})` }, svg); ghosts.push(gh); + el("path", { d: "M-38 -122 H38 V-104 H32 Q48 -104 48 -88 V-16 Q48 0 32 0 H-32 Q-48 0 -48 -16 V-88 Q-48 -104 -32 -104 H-38 Z", fill: "none", stroke: LIL, "stroke-width": 3, "stroke-dasharray": "3 8", "stroke-linecap": "round", "stroke-linejoin": "round" }, gh); + }); + const copies = SX.map((x, i) => { const g = el("g", { "pointer-events": "none" }, svg), lab = jar(g); return { g, lab, x, a: 2 + i * 0.85 }; }); + SX.forEach((x) => rail(x, SS, SW)); + button(main, "Skills Hub: installs a skill once and syncs it to Claude Code, Codex, Cursor and OpenCode", () => api.seek(1.8)); + const cap = strip(svg, 508, 740); + return (t) => { + const out = seg(t, 10.8, 11.3), sync = t >= 6 && t < 10.8; + copies.forEach((c, i) => { + ghosts[i].setAttribute("opacity", t >= c.a + 0.85 && t < 10.8 ? 0 : 1); + const p = io(seg(t, c.a, c.a + 0.9)), q = Math.sin(seg(t, c.a + 0.9, c.a + 1.15) * Math.PI); + const x = lerp(400, c.x, p), y = lerp(CS, SS, p) - 90 * Math.sin(Math.PI * p), s = lerp(CSC, 1, p); + c.g.setAttribute("transform", `translate(${x} ${y}) scale(${s * (1 + 0.06 * q)} ${s * (1 - 0.08 * q)})`); + c.g.setAttribute("opacity", (t < c.a ? 0 : 1) * (1 - out)); + c.lab.setAttribute("fill", sync ? YEL : "#fff"); + }); + mlab.setAttribute("fill", sync ? YEL : "#fff"); + cap.textContent = t < 2 || t >= 10.8 ? "Skills Hub: a desktop app for macOS, Windows and Linux" : t < 6 ? "Installs a skill once and syncs it to the tools you choose" : "Skills Hub's README lists 48 supported tools"; + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 112, "What they are, where they live, how to write one", { "font-size": 25, "font-weight": 500, fill: MUTE }); + txt(svg, 62, 206, "Claude Code", { "font-size": 80, "font-weight": 700 }); + txt(svg, 62, 292, "skills", { "font-size": 80, "font-weight": 700 }); + el("path", { d: "M68 320 H312", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 62, y: 362, width: 312, height: 56, rx: 28, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 218, 399, "Checked 14 Oct 2026", { "text-anchor": "middle", "font-size": 25, "font-weight": 700 }); + const f = el("g", { transform: "translate(764 310)" }, svg); + el("path", { d: "M-140 -100 Q-140 -112 -128 -112 H-62 Q-52 -112 -47 -103 L-40 -90 H128 Q140 -90 140 -78 V110 Q140 122 128 122 H-128 Q-140 122 -140 110 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, f); + const sh = el("g", { transform: "translate(6 -14) rotate(-7)" }, f); + el("rect", { x: -100, y: -146, width: 200, height: 220, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, sh); + txt(sh, -78, -106, "SKILL.md", { "font-family": MONO, "font-size": 26, "font-weight": 700 }); + [[-78, -84, 150, LIL], [-78, -60, 116, SKY], [-78, -36, 136, LIL]].forEach(([x, y, w, c]) => el("rect", { x, y, width: w, height: 11, rx: 5.5, fill: c }, sh)); + el("path", { d: "M-150 6 H150 L141 110 Q140 122 128 122 H-128 Q-140 122 -141 110 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, f); + [[606, 168, 15, SKY], [902, 470, 18, MINT], [560, 470, 12, LIL], [916, 150, 11, YEL]].forEach(([x, y, r, col]) => el("path", { d: `M0 ${-r} Q0 0 ${r} 0 Q0 0 0 ${r} Q0 0 ${-r} 0 Q0 0 0 ${-r} Z`, fill: col, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", transform: `translate(${x} ${y})` }, svg)); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/claude-code-skills/rack.png b/blog/src/assets/media/claude-code-skills/rack.png new file mode 100644 index 000000000..1ef06026f Binary files /dev/null and b/blog/src/assets/media/claude-code-skills/rack.png differ diff --git a/blog/src/assets/media/claude-code-skills/stamp.png b/blog/src/assets/media/claude-code-skills/stamp.png new file mode 100644 index 000000000..fb34e4d83 Binary files /dev/null and b/blog/src/assets/media/claude-code-skills/stamp.png differ diff --git a/blog/src/posts/aider-vs-claude-code.md b/blog/src/posts/aider-vs-claude-code.md index 43666baef..b405d892e 100644 --- a/blog/src/posts/aider-vs-claude-code.md +++ b/blog/src/posts/aider-vs-claude-code.md @@ -81,7 +81,7 @@ Four sensible moves, depending on what you liked about Aider: 1. **Claude Code**, if you want more autonomy and Claude models suit you. You trade a commit per edit for checkpoints, and you decide when to commit. 2. **[Munder Difflin](https://harnessmd.com/download)**, if you are picking Claude Code and will want more than one session going. It is a free and open source desktop app that runs Claude Code agents side by side, each in a real terminal and optionally its own git worktree, with long term memory and a shared task board (todo, doing, blocked, done). Aider is not a built in engine as of 0.5.2. You can type `aider` into the Custom engine, but that agent gets no hooks, so the app cannot tell when it is idle and messages to it go back to the orchestrator. 3. **[OpenCode](/blog/what-is-opencode/)**, if model choice was the point. It is MIT licensed, and its docs say it supports 75+ LLM providers and local models, checked 15 Sep 2026. -4. **cecli**, if you want to stay on Aider's codebase. This GitHub fork of Aider (Apache 2.0, formerly aider-ce) published v1.5.1 on 12 Sep 2026, and its README links docs for MCP configuration and subagents. We have not tested it. +4. **cecli**, if you want to stay on Aider's codebase. This GitHub fork of Aider (Apache 2.0, formerly aider-ce) published v1.5.1 on 12 Sep 2026, and its README links docs for MCP configuration and subagents. ## Should you choose Aider or Claude Code today? diff --git a/blog/src/posts/claude-code-hooks-explained.md b/blog/src/posts/claude-code-hooks-explained.md index 8552de214..81b2cde2e 100644 --- a/blog/src/posts/claude-code-hooks-explained.md +++ b/blog/src/posts/claude-code-hooks-explained.md @@ -35,7 +35,7 @@ Everything below was checked on 29 Sep 2026 against the official [hooks referenc A hook is a rule that says "when this event fires, run this handler". Most handlers are shell commands (`type: "command"`), but the reference now lists five types: `command`, `http`, `mcp_tool`, `prompt` and `agent`. As the [hooks guide](https://code.claude.com/docs/en/hooks-guide) says, hooks give you deterministic control: a guard runs every time, not when the model remembers. -That is also the answer to "hooks vs skills". A skill is instructions Claude may choose to load. A hook runs whether Claude likes it or not. +That is also the answer to "hooks vs skills". A skill is instructions Claude may choose to load. A hook runs whether Claude likes it or not. Our [Claude Code skills guide](/blog/claude-code-skills/) covers writing one. ## Which hook events does Claude Code have? diff --git a/blog/src/posts/claude-code-projects.md b/blog/src/posts/claude-code-projects.md index f6289c018..38421b8f6 100644 --- a/blog/src/posts/claude-code-projects.md +++ b/blog/src/posts/claude-code-projects.md @@ -23,7 +23,7 @@ faq: Projects in Claude Code are one ongoing conversation where Claude runs your tasks as parallel threads, usually in the cloud. They are in public beta on Pro and Max plans and rolling out gradually. Our view: join the waitlist if your code is on github.com, and expect to use your plan faster. Checked 11 Oct 2026. -We read Anthropic's [Projects docs](https://code.claude.com/docs/en/claude-projects), [launch post](https://claude.com/blog/projects-redesigned) and [changelog page](https://code.claude.com/docs/en/changelog) on 11 Oct 2026. We have not tried Projects. The [concepts hub](/blog/topics/concepts/) explains the terms. +We read Anthropic's [Projects docs](https://code.claude.com/docs/en/claude-projects), [launch post](https://claude.com/blog/projects-redesigned) and [changelog page](https://code.claude.com/docs/en/changelog) on 11 Oct 2026. The [concepts hub](/blog/topics/concepts/) explains the terms. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It is not part of Projects. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers it. diff --git a/blog/src/posts/claude-code-router.md b/blog/src/posts/claude-code-router.md new file mode 100644 index 000000000..d672eb05a --- /dev/null +++ b/blog/src/posts/claude-code-router.md @@ -0,0 +1,96 @@ +--- +title: "Claude Code Router: What It Is, How It Works and How to Install It" +seoTitle: "Claude Code Router (CCR): Point Claude Code at Other Models" +description: "Claude Code Router, checked 14 Oct 2026: what the local gateway does, how to install it, which providers it lists, and what Anthropic says about it." +date: 2026-10-14 +category: concepts +categoryLabel: Concepts +type: Technical +primaryKeyword: "claude code router" +secondaryKeywords: ["claude-code-router", "ccr", "use claude code with other models", "claude code openrouter", "claude code deepseek"] +tags: ["Concepts", "Claude Code", "Model Routing", "Open Source", "Cost"] +faq: + - q: "Is Claude Code Router free?" + a: "The software is open source: GitHub listed it under the MIT licence on 14 Oct 2026. The models behind it are a separate matter: you add each provider with your own API key, and Anthropic's gateway docs say that while a gateway credential or apiKeyHelper is active, gateway traffic is billed per token to whoever owns the credential the gateway forwards." + - q: "Does Anthropic support Claude Code Router?" + a: "No. Anthropic's LLM gateway docs said on 14 Oct 2026 that Anthropic doesn't endorse, maintain, or audit third-party gateway products, and doesn't support routing Claude Code to non-Claude models through any gateway." + - q: "Can I use Claude Code with DeepSeek or OpenRouter?" + a: "Claude Code Router's README names both among the providers it supports. You add the provider and key in the app, start its server and apply a Claude Code profile. Anthropic's gateway docs say it doesn't support routing Claude Code to non-Claude models." + - q: "What port does Claude Code Router use?" + a: "Its README says the local model gateway listens on http://127.0.0.1:3456 by default. If you install the npm CLI and run ccr ui, the browser management screen is at http://127.0.0.1:3458, and the gateway stays on 3456." + - q: "Do my Claude Pro or Max limits apply when I use a gateway?" + a: "Not while a gateway credential is active. Anthropic's LLM gateway docs say those requests carry the gateway credential in place of your claude.ai login, so the subscription's usage limits don't apply to them. Setting only ANTHROPIC_BASE_URL, with no gateway credential, keeps your saved login and its limits." +--- + +Claude Code Router (CCR) is an MIT licensed local gateway that points Claude Code, and other coding agents, at the providers and models you choose, such as OpenRouter or DeepSeek. Anthropic's [gateway docs](https://code.claude.com/docs/en/llm-gateway) say Anthropic doesn't support routing Claude Code to non-Claude models through any gateway. Checked 14 Oct 2026. + +Want several agents side by side rather than one agent on many models? [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. More explainers sit in our [Concepts hub](/blog/topics/concepts/). + +## What is Claude Code Router? + +It is a local model gateway and control plane for coding agents, in the words of its [README](https://github.com/musistudio/claude-code-router). The README says it gives agents "one stable local endpoint" while you manage providers, models, accounts, routing rules and tools in one place. In effect, it is a switchboard operator for API calls. + +GitHub shows the repo was created in February 2025, and on 14 Oct 2026 its description read "One local control plane for every AI agent". The README lists Claude Code, Claude Design, Codex, Grok CLI, Kimi CLI, Kilo Code, OpenCode, Pi, ZCode and WorkBuddy as supported agents. GitHub showed v3.1.3, released 9 Oct 2026, under the MIT licence, with more than 37,000 stars. + +
Animation. An old telephone switchboard has one jack marked Claude Code on the left and jacks marked OpenRouter, DeepSeek, Moonshot and Mistral on the right. A patch cord plugs Claude Code into DeepSeek, then moves across to OpenRouter.
One local endpoint for the agent, many providers behind it. CCR's README, read 14 Oct 2026.
+ +## How does Claude Code Router work? + +Claude Code sends its requests to CCR on your machine, and CCR forwards each one to the provider, model and account you set up. The README's diagram shows the agents on top, "Claude Code Router :3456" in the middle with profiles, routing, credentials, tools and logs, and the selected provider, model and account below. + +Routing rules can match on request headers and bodies, use prefixes, rewrite requests, retry and fall back to models in a set order, per the README. The README also lists credential pools and key rotation to keep requests running, and logs that show the resolved provider, model, latency, tokens and estimated cost. + +
Animation. A pinwheel spins in a breeze drawn as lines labelled retry. The lines fade out, a second breeze labelled fall back starts from the other side, and the pinwheel keeps turning.
Retries, key rotation and ordered fallback models, as listed in CCR's README on 14 Oct 2026.
+ +## How do you install Claude Code Router? + +Download the desktop app for macOS, Windows or Linux, or install the npm CLI, which needs `Node.js` 22 or newer. The README's desktop steps: + +1. Open Providers, then Add Provider. Pick a preset or a custom endpoint, enter the API key, choose the protocol and models, and save. +2. Open Server and click Start. The gateway listens on `http://127.0.0.1:3456` by default. +3. Open Agent Config, choose Claude Code, select a model and apply the profile. +4. Use Claude Code as normal, and check Logs for the resolved provider and model. + +The CLI starts the same gateway with a browser management screen instead of the Electron app: + +```sh +npm install -g @musistudio/claude-code-router +ccr ui +``` + +Then open `http://127.0.0.1:3458` and follow the same steps; the gateway stays on port 3456. There is a Docker route too, and the README says to read its Docker guide before you expose CCR remotely. + +## Which models and providers can it use? + +Custom compatible providers, in the README's words, plus a list of named presets. These rows are our selection from the README, read 14 Oct 2026: + +| Type | What the README names | +| --- | --- | +| API formats | OpenAI Chat and Responses, Anthropic Messages, Gemini Generate Content and Interactions | +| Hosted providers | OpenRouter, DeepSeek, SiliconFlow, Moonshot, Kimi Code, Mistral, Z.AI, Bailian | +| Your own endpoint | Custom compatible providers | +| Extra capabilities | Fusion vision, web search, MCP tools, ToolHub | + +
Animation. A wind chime hangs from one bar labelled CCR. Its tubes are labelled OpenRouter, DeepSeek, SiliconFlow, Moonshot, Mistral and Z.AI, and a breeze touches each tube in turn so it swings.
Some of the hosted providers CCR's README named on 14 Oct 2026.
+ +## What are the trade offs? + +You leave Anthropic's support behind, and some Claude Code features may not make the trip. Anthropic's [LLM gateway docs](https://code.claude.com/docs/en/llm-gateway) say Anthropic "doesn't endorse, maintain, or audit third-party gateway products, and doesn't support routing Claude Code to non-Claude models through any gateway." + +Anthropic's gateway docs add that Claude Code gains capabilities with each release, and "a gateway that doesn't forward them breaks the corresponding features", so the gateway has to keep up. On billing, the gateway docs say that while a gateway credential is active, requests carry it in place of your claude.ai login, the subscription's usage limits don't apply, and the traffic is billed per token to whoever owns the credential. + +
Animation. A paper boat marked Claude Code carries four small flags toward a lock gate marked gateway. The gate lifts part way, the boat passes under it, and its two tall flags stay behind on the bank.
A gateway that doesn't forward new capabilities breaks those features. Anthropic's gateway docs, 14 Oct 2026.
+ +We think the bigger risk is the model, not the router: try a small task on any model Claude Code was not built for before you trust it with a refactor. Our [Ollama guide](/blog/how-to-connect-ollama-to-claude-code/) lists what breaks when Claude Code runs on a local model. If price is the reason you are looking, compare first with [what Claude Code costs](/blog/how-much-does-claude-code-cost/) on a plan. + +## What are the other options? + +Another gateway, Anthropic's own gateway, a local model, or a different agent. [OmniRoute](https://github.com/diegosouzapw/OmniRoute) is another MIT licensed gateway, and its GitHub description said on 14 Oct 2026 that it works with Claude Code, Codex, Cursor, OpenCode, Cline and Copilot. + +If you want a gateway for Claude models with budgets and audit logs, Anthropic's docs point to Claude apps gateway, its self-hosted gateway with SSO sign-in. For local models, our Ollama guide covers `ollama launch claude`. For an agent built around many models from the start, see [Claude Code alternatives](/blog/claude-code-alternatives/). + +## Can you use it with Munder Difflin? + +Munder Difflin runs Claude Code as you have it set up on your machine. Setup is in [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). + + diff --git a/blog/src/posts/claude-code-routines.md b/blog/src/posts/claude-code-routines.md index 5b6af5eea..1b79d3e18 100644 --- a/blog/src/posts/claude-code-routines.md +++ b/blog/src/posts/claude-code-routines.md @@ -24,7 +24,7 @@ faq: Our view: Claude routines are worth one small, low risk job if you want Claude Code to work while your laptop is closed, but they are a research preview with hourly limits. A routine is a saved prompt that runs in the cloud on a schedule, an API call or a GitHub event. Checked 12 Oct 2026. -We have not tried routines. This page reports what Anthropic's documentation says. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It sits at the local end of this choice, and routines sit at the cloud end. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. +This page reports what Anthropic's documentation says. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It sits at the local end of this choice, and routines sit at the cloud end. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What are Claude routines? diff --git a/blog/src/posts/claude-code-skills.md b/blog/src/posts/claude-code-skills.md new file mode 100644 index 000000000..8fdd2e851 --- /dev/null +++ b/blog/src/posts/claude-code-skills.md @@ -0,0 +1,113 @@ +--- +title: "Claude Code Skills: What They Are, Where They Live and How to Write One" +seoTitle: "Claude Code Skills: Write, Find and Share Skills (Oct 2026)" +description: "Claude Code skills, checked 14 Oct 2026: what a skill is, where skill folders live, a first skill file, how Claude picks one and how to share them." +date: 2026-10-14 +category: guides +categoryLabel: Guides +type: Technical +primaryKeyword: "claude code skills" +secondaryKeywords: ["claude skills", "how to create a claude code skill", "SKILL.md", "claude code skills directory", "agent skills", "skills hub"] +tags: ["Guides", "Claude Code", "Skills", "Security", "Open Source"] +faq: + - q: "What is the difference between a skill and CLAUDE.md?" + a: "Anthropic's skills docs say that, unlike `CLAUDE.md` content, a skill's body loads only when it is used, so long reference material costs almost nothing until you need it. The docs suggest a skill when a section of `CLAUDE.md` has grown into a procedure rather than a fact." + - q: "Are custom slash commands the same as skills now?" + a: "Yes. Anthropic's skills docs say custom commands have been merged into skills: `.claude/commands/deploy.md` and `.claude/skills/deploy/SKILL.md` both create `/deploy` and work the same way. Old command files keep working, but a skill folder can also hold supporting files." + - q: "Why is my Claude Code skill not triggering?" + a: "Anthropic's troubleshooting list starts with the description: it should include keywords users would naturally say. Check that the skill appears when you ask Claude what skills are available, or invoke it with its slash name. If the frontmatter YAML is malformed, Claude can't match your description; on v2.1.233 or later, `claude plugin validate .claude/skills` finds the broken file." + - q: "How do I stop Claude from running a skill by itself?" + a: "Add `disable-model-invocation: true` to the skill's frontmatter. Anthropic's skills docs say Claude then can't invoke it on its own, while you can still run it by typing its slash name. The docs suggest this for workflows with side effects, such as a deploy or a commit." + - q: "Do Claude Code skills work in Codex or Cursor?" + a: "The format can travel. Claude Code skills follow the Agent Skills open standard, and on 14 Oct 2026 the Skills Hub README listed Codex, Cursor and OpenCode among 48 supported tools. Claude Code adds its own features on top of the standard, such as dynamic context injection, so we think shared skills should stick to the standard fields." +--- + +A Claude Code skill is a folder holding a `SKILL.md` file of instructions, which Claude loads when your request matches its description or when you type `/skill-name`. Anthropic's [skills docs](https://code.claude.com/docs/en/skills) put personal skills in `~/.claude/skills/` and project skills in `.claude/skills/`. Checked 14 Oct 2026. + +Everything below applies to a plain Claude Code session. If you run several agents side by side, [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. More walkthroughs sit in our [Guides hub](/blog/topics/guides/). + +## What is a Claude Code skill? + +A skill is a set of instructions that Claude adds to its toolkit and uses when relevant. Anthropic's skills docs suggest writing one "when you keep pasting the same instructions, checklist, or multi-step procedure into chat". The difference from `CLAUDE.md` is cost: the docs say a skill's body "loads only when it's used, so long reference material costs almost nothing until you need it." + +
Animation. A rubber stamp lifts off an ink pad and presses the same short checklist onto three blank cards in a row, and every print comes out identical.
Write the instructions once and reuse them. Anthropic's skills docs, read 14 Oct 2026.
+ +Custom commands now count as skills. Per the skills docs, `.claude/commands/deploy.md` and `.claude/skills/deploy/SKILL.md` both create `/deploy`, and old command files keep working. Skills follow the [Agent Skills](https://agentskills.io) open standard, which the docs say "works across multiple AI tools". Our guide to [adding an MCP server](/blog/how-to-add-an-mcp-server-to-claude-code/) covers the other common way to extend Claude Code. + +## Where do Claude Code skills live? + +Where you save a skill decides which sessions load it. These rows are our selection from the table in Anthropic's skills docs, read 14 Oct 2026: + +| Location | Path | Loads in | +| --- | --- | --- | +| Personal | `~/.claude/skills//SKILL.md` | All your projects on this machine, but not Cowork or cloud sessions | +| Project | `.claude/skills//SKILL.md` | Sessions in this repository; commit it so your team gets it | +| Plugin | `/skills//SKILL.md` | Wherever the plugin is enabled, as `/plugin-name:skill-name` | +| Enterprise | `.claude/skills//SKILL.md` in the managed settings directory | All users on machines where your organization deploys it | + +When two skills share a name, the docs say enterprise wins over personal, and personal over project. If you sign in with a claude.ai account in the terminal, Claude Code v2.1.273 or later syncs the skills enabled there into `~/.claude/skills/synced/`. + +
Animation. A wooden card catalogue has four drawers labelled Personal, Project, Plugin and Enterprise. Each drawer slides open in turn and one index card rises out of it.
Where a skill is saved decides which sessions load it. Anthropic's skills docs, 14 Oct 2026.
+ +## How do you write your first Claude Code skill? + +Make a folder, add a `SKILL.md` with a `description`, and write the steps under it. This is the example in Anthropic's skills docs, a skill that summarizes your uncommitted changes: + +```bash +mkdir -p ~/.claude/skills/summarize-changes +``` + +Save this as `~/.claude/skills/summarize-changes/SKILL.md`: + +```yaml +--- +description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff. +--- + +## Current changes + +!`git diff HEAD` + +## Instructions + +Summarize the changes above in two or three bullet points, then list any risks you notice such as missing error handling, hardcoded values, or tests that need updating. If the diff is empty, say there are no uncommitted changes. +``` + +The line that starts with `!` runs `git diff HEAD` first and swaps in its output, so Claude reads your real diff. Test it by asking "What did I change?" or by typing `/summarize-changes`. The folder name becomes the command unless you set `name`. + +The docs say every frontmatter field is optional and only `description` is recommended. Four you will meet early, our pick: + +- `disable-model-invocation: true` stops Claude loading the skill on its own. +- `allowed-tools` lets Claude use the listed tools without asking, for that turn. +- `paths` limits automatic loading to files that match its patterns. +- `context: fork` runs the skill in a forked subagent. + +## How does Claude decide to use a skill? + +Claude reads each skill's description and loads the full skill when a request matches it. Anthropic's skills docs say descriptions sit in context during a regular session, but "full skill content only loads when invoked". On 14 Oct 2026 the docs set two defaults, which settings can change: 1,536 characters for the description plus `when_to_use`, and 1% of the context window for the listing. With many skills, Claude Code drops some descriptions to fit, starting with the ones you invoke least. + +
Animation. A closed book has a row of bookmark tabs along its top edge, each with a short label. A request card slides in, one tab lifts and the book falls open at that page.
Claude sees each description first, and the full skill loads when invoked. Anthropic's skills docs, 14 Oct 2026.
+ +The docs' first fix for a skill that never triggers is a description with the keywords users would naturally say. Typing `/deploy staging` at the start of a message runs the skill directly; the same name after plain text gives Claude permission but doesn't run it. If Claude skips a rule that must hold every time, the docs say to move it into a hook, and our [hooks guide](/blog/claude-code-hooks-explained/) shows how. + +## How do you share skills across tools? + +Commit them to the repo, ship them in a plugin, or sync one copy to several agents. Anthropic's skills docs list three scopes: commit `.claude/skills/`, add a `skills/` directory to a plugin, or deploy through managed settings. The [last30days skill](https://github.com/mvanhorn/last30days-skill), a research skill, installs in Claude Code as a plugin, per its README on 14 Oct 2026: `/plugin marketplace add mvanhorn/last30days-skill`, then `/plugin install last30days`. + +For several tools at once, [Skills Hub](https://github.com/qufei1993/skills-hub) is a desktop app for macOS, Windows and Linux that installs a skill once and syncs it to the tools you choose. Its README said on 14 Oct 2026 that it supports 48 AI coding tools, including Claude Code, Codex, Cursor and OpenCode. GitHub showed v1.0.0, released 11 Oct 2026, under the MIT licence, with more than 1,800 stars. + +
Animation. One labelled jar sits on a central spice rack. Copies of it slide onto four smaller racks marked Claude Code, Codex, Cursor and OpenCode, and every label matches.
Skills Hub's README lists 48 supported tools, these four among them. Read 14 Oct 2026.
+ +Portability has a catch. Anthropic's skills docs say that outside Claude Code, for claude.ai uploads and the Skills API, you can use only the six fields in the Agent Skills spec: `name`, `description`, `license`, `compatibility`, `metadata` and `allowed-tools`. Any other field makes packaging or upload fail with a hard error. Our view: keep a skill you plan to share to those six. + +## Are Claude Code skills safe to install? + +Treat a skill like a script you are about to run, because it can run commands. Anthropic's skills docs warn that workspace trust doesn't gate a project skill's `allowed-tools`, even in a `-p` run in a folder you've never trusted, and tell you to review that field in repository skills before you run Claude Code there. A skill from a stranger is a shell script with good manners. + +NVIDIA's [SkillSpector](https://github.com/NVIDIA/SkillSpector) README said on 14 Oct 2026 that 26.1% of skills in a 31,132 skill research subset contained vulnerabilities, and its `skillspector scan` command checks a skill before you install it. + +## Do skills work inside Munder Difflin? + +Yes. Munder Difflin runs Claude Code in your project folder, so the skills Claude Code reads there apply. To set it up, follow [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). + + diff --git a/blog/src/posts/claude-haiku-5-5.md b/blog/src/posts/claude-haiku-5-5.md index 560779a77..7a21d9803 100644 --- a/blog/src/posts/claude-haiku-5-5.md +++ b/blog/src/posts/claude-haiku-5-5.md @@ -12,7 +12,7 @@ faq: - q: "How much does Claude Haiku 5.5 cost?" a: "Anthropic's announcement of 7 October 2026 lists $0.10 input and $0.50 output per million tokens for prompts up to 100,000 tokens, and $0.50 input and $2.50 output for prompts over 100,000 tokens. Cache reads are $0.01 and $0.05. Checked 8 Oct 2026." - q: "Is Claude Haiku 5.5 better than Haiku 4.5?" - a: "On Anthropic's own figures, yes on every row where both have a score. For example, the announcement lists 72.4% for Haiku 5.5 and 15.7% for Haiku 4.5 on the offline subset of OSWorld 2.1. We did not run these tests." + a: "On Anthropic's own figures, yes on every row where both have a score. For example, the announcement lists 72.4% for Haiku 5.5 and 15.7% for Haiku 4.5 on the offline subset of OSWorld 2.1." - q: "Is Claude Haiku 5.5 better than Sonnet 5.5?" a: "No. Sonnet 5.5 is ahead on every row of Anthropic's own benchmark table, and the announcement says Sonnet 5.5 and Opus 5.5 remain better choices for complex agentic coding tasks. Haiku 5.5 is the cheaper and faster option." - q: "What is the model ID for Claude Haiku 5.5?" @@ -21,7 +21,7 @@ faq: a: "Claude Code's model docs say to run /model claude-haiku-5-5 in a session, or start with claude --model claude-haiku-5-5. The same page says to use Claude Code v2.1.293 or later with Haiku 5.5. Read on 8 Oct 2026." --- -Claude Haiku 5.5 is Anthropic's new small model: much cheaper than Haiku 4.5 on prompts up to 100,000 tokens, and behind Sonnet 5.5 on every row of Anthropic's own benchmark table. Anthropic released it on 7 October 2026. We have not tried it. Checked 8 Oct 2026. +Claude Haiku 5.5 is Anthropic's new small model: much cheaper than Haiku 4.5 on prompts up to 100,000 tokens, and behind Sonnet 5.5 on every row of Anthropic's own benchmark table. Anthropic released it on 7 October 2026. Checked 8 Oct 2026. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. A cheaper small model matters when several agents run at once, which is why we cover it. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup and the [Concepts hub](/blog/topics/concepts/) explains the terms. @@ -56,7 +56,7 @@ The model page also lists a 50% Batch API discount on input and output. For the ## How does Claude Haiku 5.5 score on benchmarks? -On Anthropic's own figures it is far ahead of Haiku 4.5 and behind Sonnet 5.5. The five rows below are our selection from the eight rows in the [announcement](https://www.anthropic.com/claude-haiku-5-5) table. We did not run them. NR means not reported. +On Anthropic's own figures it is far ahead of Haiku 4.5 and behind Sonnet 5.5. The five rows below are our selection from the eight rows in the [announcement](https://www.anthropic.com/claude-haiku-5-5) table. NR means not reported. | Benchmark (as named by Anthropic) | Haiku 5.5 | Haiku 4.5 | GPT-6 Luna | Sonnet 5.5 | | --- | --- | --- | --- | --- | @@ -70,7 +70,7 @@ Sonnet 5.5 leads on all eight rows of the full table. Anthropic marks its Fronti The coding gap is the one to notice. On Terminal-Bench 4.0, Haiku 5.5 is at 39.2% against 70.6% for Sonnet 5.5. Anthropic says so itself: "Sonnet 5.5 and Opus 5.5 remain better choices for complex agentic coding tasks like those measured by Terminal-Bench 4.0." -
Animation. Four paper planes for Haiku 5.5, Haiku 4.5, GPT-6 Luna and Sonnet 5.5 glide along a ruled strip and land at distances set by their scores on one benchmark at a time. Arrow buttons step through the five benchmarks.
Anthropic's own figures, five rows we selected from its table of 7 October 2026. We did not run them.
+
Animation. Four paper planes for Haiku 5.5, Haiku 4.5, GPT-6 Luna and Sonnet 5.5 glide along a ruled strip and land at distances set by their scores on one benchmark at a time. Arrow buttons step through the five benchmarks.
Anthropic's own figures, five rows we selected from its table of 7 October 2026.
## Where can you use Claude Haiku 5.5? @@ -98,7 +98,7 @@ We think it makes the split between a planner and its helpers cheaper to try. An Two customer quotes from the announcement, the first on subagents and the second on speed. Rogo: "The short and high-volume work is where Claude Haiku 5.5 fits for us, like quick lookups, subagents, and summaries." Asana: "Compared with the model we use today, we saw over a 30% reduction in latency for task completions and up to 2.5x faster inference per agent turn." -Our view: put a bigger model on planning and hard debugging, and a small fast one on searching, summarising and running tests. The small one fetches, the big one decides. This is an idea, not a test. We have not tried Haiku 5.5. Our piece on [model routing](/blog/do-more-with-less-model-routing/) explains the reasoning. +Our view: put a bigger model on planning and hard debugging, and a small fast one on searching, summarising and running tests. The small one fetches, the big one decides. Our piece on [model routing](/blog/do-more-with-less-model-routing/) explains the reasoning. ## What should you do now? diff --git a/blog/src/posts/claude-managed-agents-dynamic-workflows.md b/blog/src/posts/claude-managed-agents-dynamic-workflows.md index a4e1bf629..63b9ac4c9 100644 --- a/blog/src/posts/claude-managed-agents-dynamic-workflows.md +++ b/blog/src/posts/claude-managed-agents-dynamic-workflows.md @@ -24,7 +24,7 @@ faq: Dynamic workflows let a Claude Managed Agents agent write a program that runs many agents in the background and combines their results. They have been in beta since 9 October 2026. Our view: a good fit for work with many pieces, and a reason to set a budget first. Checked 11 Oct 2026. -We have not tried dynamic workflows. This page reports what Anthropic's documentation says. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It sits at the local end of this choice, and Managed Agents sits at the hosted end. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. +This page reports what Anthropic's documentation says. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It sits at the local end of this choice, and Managed Agents sits at the hosted end. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What is Claude Managed Agents? diff --git a/blog/src/posts/gemini-cli-vs-claude-code.md b/blog/src/posts/gemini-cli-vs-claude-code.md index 84c8ef074..122f9e764 100644 --- a/blog/src/posts/gemini-cli-vs-claude-code.md +++ b/blog/src/posts/gemini-cli-vs-claude-code.md @@ -85,7 +85,7 @@ The differences we could source: - **Where it runs.** Claude Code's docs list the terminal, IDE extensions, a desktop app and the web. Gemini CLI's README describes a terminal tool and a GitHub Action. - **Built in extras.** Gemini CLI's README lists Google Search grounding, conversation checkpointing and sandboxing. Claude Code's overview lists skills, auto memory and scheduled tasks. -We have not run a benchmark between them, so we are not ranking quality or speed. +This page compares features and setup; it does not rank quality or speed. ## Who should pick which? diff --git a/blog/src/posts/gpt-6-intelligent-ui.md b/blog/src/posts/gpt-6-intelligent-ui.md index baaa3f0a2..6ed035ee5 100644 --- a/blog/src/posts/gpt-6-intelligent-ui.md +++ b/blog/src/posts/gpt-6-intelligent-ui.md @@ -23,7 +23,7 @@ faq: GPT-6 in ChatGPT is a wider rollout of a model family paid customers already had, now with Intelligent UI, which lets answers include charts, forms and buttons. OpenAI says Plus, Pro, Business and Enterprise started getting it on 7 Oct 2026, with Free and Go from 8 Oct. Codex models are not changing in this release. Checked 8 Oct 2026. -[Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. Codex is one of those agents, so we read the announcement for what it says about Codex. We have not tested GPT-6 or Intelligent UI, so nothing here is a test result. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup and the [Concepts hub](/blog/topics/concepts/) explains the terms. +[Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. Codex is one of those agents, so we read the announcement for what it says about Codex. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What did OpenAI announce? diff --git a/blog/src/posts/hermes-agent-vs-openclaw.md b/blog/src/posts/hermes-agent-vs-openclaw.md index 03ae3476f..6a75a8364 100644 --- a/blog/src/posts/hermes-agent-vs-openclaw.md +++ b/blog/src/posts/hermes-agent-vs-openclaw.md @@ -97,7 +97,7 @@ The [Hermes memory docs](https://hermes-agent.nousresearch.com/docs/user-guide/f ## What do their docs say about security? -Both docs describe a pairing code for unknown senders, and each lists its own controls. We did not test either tool, so this is each project's own wording. +Both docs describe a pairing code for unknown senders, and each lists its own controls in its own words. The [Hermes security page](https://hermes-agent.nousresearch.com/docs/user-guide/security) lists eight layers, including dangerous command approval and container isolation. The default approval mode is `smart`, which uses a second model to judge risk. Hub skills go through a security scanner. diff --git a/blog/src/posts/mistral-large-4.md b/blog/src/posts/mistral-large-4.md index dd1703b96..7179aa7e7 100644 --- a/blog/src/posts/mistral-large-4.md +++ b/blog/src/posts/mistral-large-4.md @@ -23,7 +23,7 @@ faq: Our view: Mistral Large 4, nicknamed Le Chonk, is a serious open weight coding model that you can only rent for now. Mistral launched it as a public preview on 6 October 2026, with about 1 trillion parameters, a 1M token context window, an API you can call today and weights promised this month. Checked 6 Oct 2026. -Open models can drive coding agents, which is why we care. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. We have not tried Mistral Large 4 with it. Our guide to [running Munder Difflin on open models](/blog/run-munder-difflin-on-open-models/) covers the models you can use now, the [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. +Open models can drive coding agents, which is why we care. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. Our guide to [running Munder Difflin on open models](/blog/run-munder-difflin-on-open-models/) covers the models you can use now, the [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What is Mistral Large 4? @@ -65,7 +65,7 @@ Source: [Mistral's model page](https://docs.mistral.ai/models/mistral-large-4-0) ## How does Mistral Large 4 score on benchmarks? -On the vendor's own figures, it is second among the five models in both charts below, and leads neither. The two rows below are our selection from the charts in Mistral's [launch post](https://mistral.ai/news/mistral-large-4/). We did not run them. The charts round to whole numbers. +On the vendor's own figures, it is second among the five models in both charts below, and leads neither. The two rows below are our selection from the charts in Mistral's [launch post](https://mistral.ai/news/mistral-large-4/). The charts round to whole numbers. | Benchmark (Mistral's charts) | Mistral Large 4 | Kimi K3 | GLM-5.3 | DeepSeek V4 Pro 0813 | Qwen3.8 Max | | --- | --- | --- | --- | --- | --- | @@ -76,7 +76,7 @@ In its text, Mistral gives 61.7% on DeepSWE v1.1, 28.3% on Terminal-Bench 4 and VentureBeat noted that the model did not yet appear in Artificial Analysis' public evaluations or on the DeepSWE leaderboard, so the ranking claim "remains provisional". Mistral's charts say the scores were evaluated privately by Artificial Analysis. For another launch this week, see our [Reflection Beam explainer](/blog/reflection-beam/). -
Animation. Five flags for Mistral Large 4, Kimi K3, GLM-5.3, DeepSeek V4 Pro 0813 and Qwen3.8 Max slide up their poles to heights set by their scores, first for DeepSWE 1.1 and then for Terminal-Bench 4, and the order changes between the two.
Mistral's own figures from its launch post of 6 October 2026, two rows we selected. We did not run them.
+
Animation. Five flags for Mistral Large 4, Kimi K3, GLM-5.3, DeepSeek V4 Pro 0813 and Qwen3.8 Max slide up their poles to heights set by their scores, first for DeepSWE 1.1 and then for Terminal-Bench 4, and the order changes between the two.
Mistral's own figures from its launch post of 6 October 2026, two rows we selected.
## What hardware do you need to run it? @@ -90,7 +90,7 @@ Through the API, probably, but no source we opened names a coding CLI that ships One detail is telling. Mistral's DeepSWE chart labels rivals by harness, such as Qwen3.8 Max with Claude Code and GLM-5.3 with Opencode. Its own bar names none, and the footnote mentions a harness that has not launched publicly. -We think any agent that accepts a custom API endpoint could be pointed at it, but we have not tested that. For what works now, see our [best AI coding agents](/blog/best-ai-coding-agents/) roundup. +We think any agent that accepts a custom API endpoint could be pointed at it. For what works now, see our [best AI coding agents](/blog/best-ai-coding-agents/) roundup. ## What should you do now? diff --git a/blog/src/posts/muse-wonder.md b/blog/src/posts/muse-wonder.md index 044bc7ccc..f9a54b669 100644 --- a/blog/src/posts/muse-wonder.md +++ b/blog/src/posts/muse-wonder.md @@ -23,7 +23,7 @@ faq: Our view: Muse Wonder is a real contender for coding agents, not a clear winner. Meta announced Muse Wonder and Muse Wonder Full on 12 October 2026. In Meta's own coding table, Muse Wonder Full tops two of four rows and Claude Opus 5.5 tops the other two. Checked 13 Oct 2026. -New coding models drive coding agents. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. We have not tried Muse Wonder. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. +New coding models drive coding agents. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What is Muse Wonder? @@ -56,7 +56,7 @@ Muse Code is Meta's coding agent that runs in your terminal. Meta's [Muse Code p The Muse Code page prints its install command with and without flags, the fuller being `curl -fsSL https://dev.meta.ai/install.sh | bash`, and says "Available for MacOS and Windows". The Muse Code page does not mention Linux. -Meta's [developer home page](https://dev.meta.ai/) says "Multi-agent coding in your terminal". On 13 Oct 2026 the Muse Code page still said "powered by Muse Spark", and its model table had no Muse Wonder row. +Meta's [developer home page](https://dev.meta.ai/) says "Multi-agent coding in your terminal". On 14 Oct 2026 the Muse Code page said "powered by Muse models", and its plans listed Muse Wonder.
Animation. A toolbox labelled Muse Code opens. A hammer, a saw and a screwdriver lift out and work side by side on one plank, while a spirit level slides along behind them checking the work.
Meta's Muse Code page, read 13 Oct 2026, says "Workers in parallel, reviewers in the background". The drawing shows tools.
@@ -85,7 +85,7 @@ Meta's launch post names no setting that turns either one on. Our view: wait for ## How good is Muse Wonder at coding? -On Meta's own figures, Muse Wonder Full leads two of the four coding rows and Claude Opus 5.5 leads the other two. The table holds the four coding rows from Meta's model card, our selection from its 14 rows. Each number is Meta's own figure, and Meta's model page labels every column "(max)". We did not run them. +On Meta's own figures, Muse Wonder Full leads two of the four coding rows and Claude Opus 5.5 leads the other two. The table holds the four coding rows from Meta's model card, our selection from its 14 rows. Each number is Meta's own figure, and Meta's model page labels every column "(max)". | Benchmark | Muse Wonder Full | Muse Wonder | Claude Fable 5.1 | GPT-6 Astra | Claude Opus 5.5 | | --- | --- | --- | --- | --- | --- | diff --git a/blog/src/posts/openai-decisions-api.md b/blog/src/posts/openai-decisions-api.md index 3ed4db59b..b87ecca14 100644 --- a/blog/src/posts/openai-decisions-api.md +++ b/blog/src/posts/openai-decisions-api.md @@ -23,7 +23,7 @@ faq: The OpenAI Decisions API is a beta endpoint that answers fixed questions with a probability, a pick or a score instead of written text. OpenAI released it on 6 Oct 2026 with one model, gpt-6-luna, at $0.10 per 1M input tokens. Our view: worth a test for classifying and routing, not a coding tool. Checked 7 Oct 2026. -Agents need sorting and routing too, which is why we looked. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. We have not tried the Decisions API, so nothing here is a test result. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup and the [Concepts hub](/blog/topics/concepts/) explains the terms. +Agents need sorting and routing too, which is why we looked. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What is the OpenAI Decisions API? diff --git a/blog/src/posts/reflection-beam.md b/blog/src/posts/reflection-beam.md index 4bcdd5d37..e1f16a9fd 100644 --- a/blog/src/posts/reflection-beam.md +++ b/blog/src/posts/reflection-beam.md @@ -23,7 +23,7 @@ faq: Reflection AI Beam is a 501 billion parameter open weight AI model for coding, reasoning and agent work, announced on 5 October 2026. You cannot download it yet. Our view: promising on efficiency, unproven until the weights ship, and behind Qwen 3.8 Max on every row in Reflection's own tables where both have a score. Checked 6 Oct 2026. -Open models can drive coding agents, which is why we care. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. We have not tried Beam with it, because there are no weights to try. Our guide to [running Munder Difflin on open models](/blog/run-munder-difflin-on-open-models/) covers the models you can use now, the [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. +Open models can drive coding agents, which is why we care. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. Our guide to [running Munder Difflin on open models](/blog/run-munder-difflin-on-open-models/) covers the models you can use now, the [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What is Reflection AI Beam? @@ -60,7 +60,7 @@ The post gives these training figures: ## How does Beam score on benchmarks? -On Reflection's own figures, Beam is close to GLM 5.2 and behind Qwen 3.8 Max and Kimi K3. The six rows below are our selection from the 21 rows in the four tables in Reflection's [launch post](https://reflection.ai/blog/introducing-beam). We did not run them. NR means not reported. +On Reflection's own figures, Beam is close to GLM 5.2 and behind Qwen 3.8 Max and Kimi K3. The six rows below are our selection from the 21 rows in the four tables in Reflection's [launch post](https://reflection.ai/blog/introducing-beam). NR means not reported. | Benchmark (as named by Reflection) | Beam | GLM 5.2 | Qwen 3.8 Max | Kimi K3 | | --- | --- | --- | --- | --- | @@ -75,7 +75,7 @@ On these six rows Beam is ahead of GLM 5.2 on three and behind on three. Across Reflection says as much. The post calls Beam "competitive with larger open models like GLM 5.2 and approaching Qwen 3.8-Max", and adds that frontier open models like Kimi K3 "remain ahead on raw capability". TechCrunch noted on 5 October 2026 that the performance claims haven't been independently verified. -
Animation. Four balloons for Beam, GLM 5.2, Qwen 3.8 Max and Kimi K3 float at heights set by their scores on one benchmark at a time, with a tag on the one that is ahead. Arrow buttons step through the six benchmarks.
Reflection's own figures, six rows we selected from its tables. We did not run them. Use the arrows to change benchmark.
+
Animation. Four balloons for Beam, GLM 5.2, Qwen 3.8 Max and Kimi K3 float at heights set by their scores on one benchmark at a time, with a tag on the one that is ahead. Arrow buttons step through the six benchmarks.
Reflection's own figures, six rows we selected from its tables. Use the arrows to change benchmark.
## What makes Beam efficient? diff --git a/blog/src/posts/run-munder-difflin-on-open-models.md b/blog/src/posts/run-munder-difflin-on-open-models.md index 820dd66c5..19ad35ac2 100644 --- a/blog/src/posts/run-munder-difflin-on-open-models.md +++ b/blog/src/posts/run-munder-difflin-on-open-models.md @@ -77,7 +77,7 @@ expose it, often `:8000/v1`. Pull a model, tell the engine where it lives, and pick it for an agent. -Want a local model behind Claude Code itself? That route is covered in [how to connect Ollama to Claude Code](/blog/how-to-connect-ollama-to-claude-code/). A newer open weight model to watch is [Reflection AI's Beam](/blog/reflection-beam/); its weights were not out when we checked on 6 Oct 2026. +Want a local model behind Claude Code itself? That route is covered in [how to connect Ollama to Claude Code](/blog/how-to-connect-ollama-to-claude-code/). Hosted models instead? See [Claude Code Router](/blog/claude-code-router/). A newer open weight model to watch is [Reflection AI's Beam](/blog/reflection-beam/); its weights were not out when we checked on 6 Oct 2026. **1. Pull a model.** With [Ollama](https://ollama.com) installed, grab one sized to your memory: diff --git a/blog/src/posts/what-is-claude-agent-sdk.md b/blog/src/posts/what-is-claude-agent-sdk.md index 12a89292d..aaec7242a 100644 --- a/blog/src/posts/what-is-claude-agent-sdk.md +++ b/blog/src/posts/what-is-claude-agent-sdk.md @@ -23,7 +23,7 @@ faq: The Claude Agent SDK is Claude Code packaged as a Python and TypeScript library, so your own program can run the same agent loop and tools. Our view: pick it when you are building a product, not when you want a coding assistant today. Billing depends on how you sign in. Checked 8 Oct 2026. -[Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. We did not run the SDK for this page. Everything below comes from Anthropic's docs and package pages. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup and the [Concepts hub](/blog/topics/concepts/) explains the terms. +[Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. Everything below comes from Anthropic's docs and package pages. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup and the [Concepts hub](/blog/topics/concepts/) explains the terms. ## What is the Claude Agent SDK? diff --git a/blog/src/posts/what-is-opencode.md b/blog/src/posts/what-is-opencode.md index 85a27ef01..bd69befd6 100644 --- a/blog/src/posts/what-is-opencode.md +++ b/blog/src/posts/what-is-opencode.md @@ -1,6 +1,6 @@ --- -title: "What Is OpenCode? Install, Pricing and How It Compares" -description: "OpenCode is Anomaly's MIT licensed coding agent for the terminal. What it is, how to install it, what it costs, and how it compares with Claude Code and Codex." +title: "What Is OpenCode? Free, MIT Licensed, and What You Pay For" +description: "OpenCode is a free, MIT licensed coding agent from Anomaly. The tool costs nothing; you pay for the model you connect, unless it is local or free." date: 2026-09-29 category: concepts categoryLabel: Concepts diff --git a/blog/src/posts/what-is-zcode.md b/blog/src/posts/what-is-zcode.md index 2c8b1f81d..d55763594 100644 --- a/blog/src/posts/what-is-zcode.md +++ b/blog/src/posts/what-is-zcode.md @@ -21,7 +21,7 @@ faq: a: "No. Other products use the name. This page covers only the ZCode made by Z.ai, at zcode.z.ai and zai-org/ZCode." --- -ZCode is Z.ai's open source coding workspace: a desktop app, a browser interface and a terminal agent in one Apache-2.0 repo, tuned for Z.ai's GLM models but shipped with templates for other providers. We have not tested it. This page reports what its own pages say. Checked 8 Oct 2026. +ZCode is Z.ai's open source coding workspace: a desktop app, a browser interface and a terminal agent in one Apache-2.0 repo, tuned for Z.ai's GLM models but shipped with templates for other providers. This page reports what its own pages say. Checked 8 Oct 2026. Other products are also called zcode. This page is about Z.ai's ZCode only. It is one vendor's workspace. If you want several agent CLIs working side by side, that is the layer [Munder Difflin](https://harnessmd.com/download) works at: free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup, and more explainers sit in the [concepts hub](/blog/topics/concepts/). @@ -111,6 +111,6 @@ Versions differ too. GitHub's one release is v3.14.3, with a macOS arm64 and a W ## Who does ZCode suit? -Our view: ZCode suits people who already pay for a GLM Coding Plan and want Z.ai's own desktop workspace with the source open to read. The repo is under three weeks old, so for a longer public record we think OpenCode or Codex CLI is the safer first pick. We have not run ZCode: this is a reading of its documents, not a review. Our [best AI coding agents](/blog/best-ai-coding-agents/) page compares more tools. +Our view: ZCode suits people who already pay for a GLM Coding Plan and want Z.ai's own desktop workspace with the source open to read. The repo is under three weeks old, so for a longer public record we think OpenCode or Codex CLI is the safer first pick. Our [best AI coding agents](/blog/best-ai-coding-agents/) page compares more tools. diff --git a/docs/blog/agent-security-and-sandboxing/index.html b/docs/blog/agent-security-and-sandboxing/index.html index bb6b766f7..4a166b17b 100644 --- a/docs/blog/agent-security-and-sandboxing/index.html +++ b/docs/blog/agent-security-and-sandboxing/index.html @@ -343,34 +343,34 @@

AI Agent Use Cases From a Rea
- +
- + - 4 min + 5 min
-

Gemini 4 Argon Pricing: Half Price Until 30 November, Then the Real Bill Arrives

-

Gemini 4 Argon pricing, checked 13 Oct 2026: $2 input and $10 output per 1M tokens through 30 Nov, then $4 and $20. No free tier. Worked examples inside.

+

Claude Code Skills: What They Are, Where They Live and How to Write One

+

Claude Code skills, checked 14 Oct 2026: what a skill is, where skill folders live, a first skill file, how Claude picks one and how to share them.

Guides - Read → + Read →
- +
4 min
-

How to Use Gemini 4 Argon: Five Ways In, and Free Is Not One of Them

-

How to use Gemini 4 Argon on 13 Oct 2026: the Gemini app, AI Mode, AI Studio, the API with model ID gemini-4-argon, and Antigravity. What each route needs.

+

Gemini 4 Argon Pricing: Half Price Until 30 November, Then the Real Bill Arrives

+

Gemini 4 Argon pricing, checked 13 Oct 2026: $2 input and $10 output per 1M tokens through 30 Nov, then $4 and $20. No free tier. Worked examples inside.

Guides - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-13/index.html b/docs/blog/agent-tools-today-2026-10-13/index.html index 7e618bfcf..1b230bb94 100644 --- a/docs/blog/agent-tools-today-2026-10-13/index.html +++ b/docs/blog/agent-tools-today-2026-10-13/index.html @@ -237,7 +237,7 @@

How this list is built