diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index b65cb113d..41251f004 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -4922,5 +4922,39 @@ "status": "ready" }, "inline": {} + }, + "agent-tools-today-2026-10-15": { + "title": "Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season", + "category": "news", + "hero": { + "file": "assets/media/agent-tools-today/hero.png", + "alt": "Hand drawn sketch: a front page with five numbered lines, a clock, and a yellow character reading it out", + "prompt": "Series hero for the Agent Tools Today daily brief, drawn in code (media-src/topic-tiles.html?t=news). Reused by every brief.", + "status": "ready" + }, + "inline": {}, + "youtube": [] + }, + "muse-code-vs-claude-code": { + "title": "Muse Code vs Claude Code", + "category": "comparisons", + "hero": { + "file": "assets/media/muse-code-vs-claude-code/hero.png", + "alt": "Title card on white. The words Muse Code vs Claude Code in large ink type above a Checked 14 Oct 2026 pill, with two spinning tops facing each other.", + "prompt": "Still of the hero scene in assets/media/muse-code-vs-claude-code/motion.js.", + "status": "ready" + }, + "inline": {} + }, + "opencode-2-0": { + "title": "OpenCode 2.0: what changed and how to upgrade", + "category": "concepts", + "hero": { + "file": "assets/media/opencode-2-0/hero.png", + "alt": "Title card on white. OpenCode 2.0 in large ink type under the kicker What changed, a Checked 15 Oct 2026 pill, and a weathervane turning in the wind.", + "prompt": "Still of the hero scene in assets/media/opencode-2-0/motion.js.", + "status": "ready" + }, + "inline": {} } } diff --git a/blog/src/assets/media/agent-tools-today-2026-10-15/motion.css b/blog/src/assets/media/agent-tools-today-2026-10-15/motion.css new file mode 100644 index 000000000..fc991db4c --- /dev/null +++ b/blog/src/assets/media/agent-tools-today-2026-10-15/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Agent tools today, 15 Oct 2026". 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/agent-tools-today-2026-10-15/motion.js b/blog/src/assets/media/agent-tools-today-2026-10-15/motion.js new file mode 100644 index 000000000..1a45a5220 --- /dev/null +++ b/blog/src/assets/media/agent-tools-today-2026-10-15/motion.js @@ -0,0 +1,143 @@ +/* Bright coded scenes for "Agent tools today, 15 Oct 2026". 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 SC = {}; + + /* ---- a jigsaw puzzle: four pieces of OpenCode 2.0 slide in and lock, then the 2.0 piece drops into the middle ---- */ + SC.today = { + dur: 12, still: 9.5, + build(svg, api) { + const CX = 400, CY = 300, RH = 80; + const rad = (d) => (d * Math.PI) / 180; + const rev = (a) => a.slice().reverse(); + const path = (pts) => "M" + pts.map((p) => p[0].toFixed(1) + " " + p[1].toFixed(1)).join("L") + "Z"; + /* a round jigsaw knob on an edge at (mx, my): u runs along the edge, v points out; neck half width n, head radius r, head centre h out */ + const knob = (mx, my, ux, uy, vx, vy, n, r, h) => { + const top = h - Math.sqrt(r * r - n * n), pts = [[mx - ux * n + vx * top, my - uy * n + vy * top]]; + const a0 = Math.atan2(top - h, -n), a1 = Math.atan2(top - h, n) - 2 * Math.PI, hx = mx + vx * h, hy = my + vy * h; + for (let i = 1; i < 32; i++) { const a = a0 + ((a1 - a0) * i) / 32, c = Math.cos(a) * r, s = Math.sin(a) * r; pts.push([hx + ux * c + vx * s, hy + uy * c + vy * s]); } + pts.push([mx + ux * n + vx * top, my + uy * n + vy * top]); + return pts; + }; + /* a straight seam with one knob in the middle, bulging along (vx, vy) */ + const seam = (A, B, vx, vy) => { + const len = Math.hypot(B[0] - A[0], B[1] - A[1]), ux = (B[0] - A[0]) / len, uy = (B[1] - A[1]) / len, mx = (A[0] + B[0]) / 2, my = (A[1] + B[1]) / 2; + return [A, [mx - ux * 15, my - uy * 15], ...knob(mx, my, ux, uy, vx, vy, 15, 25, 34), [mx + ux * 15, my + uy * 15], B]; + }; + /* a quarter of the round hole in the middle, with a knob of the 2.0 piece pointing out at its middle */ + const quarter = (a0, a1) => { + const m = (a0 + a1) / 2, d = (Math.asin(12 / RH) * 180) / Math.PI, pts = []; + const on = (a) => [CX + RH * Math.cos(rad(a)), CY + RH * Math.sin(rad(a))]; + for (let i = 0; i <= 12; i++) pts.push(on(a0 + ((m - d - a0) * i) / 12)); + const [mx, my] = on(m); + pts.push(...knob(mx, my, -Math.sin(rad(m)), Math.cos(rad(m)), Math.cos(rad(m)), Math.sin(rad(m)), 12, 20, 30)); + for (let i = 0; i <= 12; i++) pts.push(on(m + d + ((a1 - m - d) * i) / 12)); + return pts; + }; + const sTop = seam([400, 70], [400, 220], 1, 0), sRight = seam([480, 300], [730, 300], 0, 1); + const sBot = seam([400, 380], [400, 530], -1, 0), sLeft = seam([70, 300], [320, 300], 0, -1); + const qBR = quarter(0, 90), qBL = quarter(90, 180), qTL = quarter(180, 270), qTR = quarter(270, 360); + const P = [ + { pts: [[70, 70], [400, 70], ...sTop, ...rev(qTL), ...rev(sLeft)], fill: BLUE, label: "Service", lx: 226, ly: 182, px: 235, py: 185, fx: -330, fy: -240, fr: -22, sx: -30, sy: -22, sr: -8, tab: [434, 145] }, + { pts: [[400, 70], [730, 70], [730, 300], ...rev(sRight), ...rev(qTR), ...rev(sTop)], fill: MINT, label: "Code Mode", lx: 590, ly: 182, px: 565, py: 185, fx: 330, fy: -240, fr: 20, sx: 30, sy: -22, sr: 7, tab: [605, 334] }, + { pts: [...sLeft, ...rev(qBL), ...sBot, [70, 530]], fill: LIL, label: "Background", lx: 206, ly: 452, px: 235, py: 415, fx: -330, fy: 250, fr: 18, sx: -30, sy: 22, sr: 6, tab: [195, 266] }, + { pts: [[730, 300], [730, 530], [400, 530], ...rev(sBot), ...rev(qBR), ...sRight], fill: SKY, label: "Plugins", lx: 584, ly: 452, px: 565, py: 415, fx: 330, fy: 250, fr: -20, sx: 30, sy: 22, sr: -7, tab: [366, 455] }, + ]; + const hole = [...qBR, ...qBL, ...qTL, ...qTR]; + /* the board and the empty slots */ + el("rect", { x: 34, y: 34, width: 732, height: 532, rx: 28, fill: SOFT }, svg); + [...P.map((p) => p.pts), hole].forEach((pts) => el("path", { d: path(pts), fill: "#fff", stroke: LIL, "stroke-width": 2, "stroke-dasharray": "7 8", "stroke-linejoin": "round" }, svg)); + /* the pieces; the whole puzzle is the tap target */ + const puz = el("g", {}, svg); + el("rect", { x: 52, y: 52, width: 696, height: 496, rx: 20, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, puz); + const pcs = P.map((p) => { + const g = el("g", {}, puz); + el("path", { d: path(p.pts), fill: p.fill, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + txt(g, p.lx, p.ly, p.label, { "text-anchor": "middle", "font-size": 38, "font-weight": 700 }); + return g; + }); + const mid = el("g", {}, puz); + el("path", { d: path(hole.map(([x, y]) => [x - CX, y - CY])), fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, mid); + txt(mid, 0, 16, "2.0", { "text-anchor": "middle", "font-size": 46, "font-weight": 700 }); + let t0 = -99; + button(puz, "Scatter and rebuild the puzzle", () => { t0 = api.now(); api.poke(); }); + /* a click ring where each piece locks, and a little burst when the middle piece lands */ + const rings = P.map((p) => el("circle", { cx: p.tab[0], cy: p.tab[1], r: 12, fill: "none", stroke: INK, "stroke-width": 3, opacity: 0 }, svg)); + const rays = [0, 1, 2, 3, 4, 5, 6, 7].map((i) => ({ a: rad(22.5 + i * 45), n: el("path", { stroke: INK, "stroke-width": 3, "stroke-linecap": "round", opacity: 0 }, svg) })); + return (t, now) => { + const dt = now - t0, sp = dt >= 0 && dt < 2.3 ? (dt < 0.6 ? oc(dt / 0.6) : 1 - io(seg(dt, 1.0, 2.1))) : 0; + const out = 1 - seg(t, 11.3, 11.9); + pcs.forEach((g, i) => { + const p = P[i], ti = 0.7 + i, k = oc(seg(t, ti, ti + 0.8)), pulse = 1 + 0.04 * Math.sin(Math.PI * seg(t, ti + 0.8, ti + 1.15)); + const x = p.fx * (1 - k) + p.sx * sp, y = p.fy * (1 - k) + p.sy * sp, r = p.fr * (1 - k) + p.sr * sp; + g.setAttribute("transform", `translate(${x + p.px} ${y + p.py}) rotate(${r}) scale(${pulse}) translate(${-p.px} ${-p.py})`); + g.setAttribute("opacity", seg(t, ti, ti + 0.25) * out); + const kr = seg(t, ti + 0.8, ti + 1.5); + rings[i].setAttribute("r", 12 + 40 * oc(kr)); + rings[i].setAttribute("stroke-width", 3 * (1 - kr)); + rings[i].setAttribute("opacity", kr > 0 && kr < 1 ? 1 : 0); + }); + const km = seg(t, 4.9, 5.7), kp = seg(t, 5.7, 6.05); + const ms = 1 + 0.07 * Math.sin(Math.PI * kp) + 0.12 * sp, mr = -140 * (1 - oc(km)) + 30 * sp; + mid.setAttribute("transform", `translate(${CX} ${CY - 340 * (1 - oc(km))}) rotate(${mr}) scale(${ms})`); + mid.setAttribute("opacity", seg(t, 4.9, 5.1) * out); + const kb = seg(t, 5.75, 6.5), r1 = 98 + 12 * oc(kb), r2 = 104 + 32 * oc(kb); + rays.forEach((y) => { + const c = Math.cos(y.a), s = Math.sin(y.a); + y.n.setAttribute("d", `M${CX + c * r1} ${CY + s * r1} L${CX + c * r2} ${CY + s * r2}`); + y.n.setAttribute("stroke-width", 3 * (1 - seg(kb, 0.5, 1))); + y.n.setAttribute("opacity", kb > 0 && kb < 1 ? 1 : 0); + }); + }; + }, + }; + + /* ---- 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/agent-tools-today-2026-10-15/today.png b/blog/src/assets/media/agent-tools-today-2026-10-15/today.png new file mode 100644 index 000000000..92db3171b Binary files /dev/null and b/blog/src/assets/media/agent-tools-today-2026-10-15/today.png differ diff --git a/blog/src/assets/media/muse-code-vs-claude-code/bells.png b/blog/src/assets/media/muse-code-vs-claude-code/bells.png new file mode 100644 index 000000000..2881a0dc8 Binary files /dev/null and b/blog/src/assets/media/muse-code-vs-claude-code/bells.png differ diff --git a/blog/src/assets/media/muse-code-vs-claude-code/fan.png b/blog/src/assets/media/muse-code-vs-claude-code/fan.png new file mode 100644 index 000000000..69a9c3215 Binary files /dev/null and b/blog/src/assets/media/muse-code-vs-claude-code/fan.png differ diff --git a/blog/src/assets/media/muse-code-vs-claude-code/hero.png b/blog/src/assets/media/muse-code-vs-claude-code/hero.png new file mode 100644 index 000000000..0e401b56d Binary files /dev/null and b/blog/src/assets/media/muse-code-vs-claude-code/hero.png differ diff --git a/blog/src/assets/media/muse-code-vs-claude-code/motion.css b/blog/src/assets/media/muse-code-vs-claude-code/motion.css new file mode 100644 index 000000000..ee71004eb --- /dev/null +++ b/blog/src/assets/media/muse-code-vs-claude-code/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Muse Code vs Claude Code". 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/muse-code-vs-claude-code/motion.js b/blog/src/assets/media/muse-code-vs-claude-code/motion.js new file mode 100644 index 000000000..bbaa20cf7 --- /dev/null +++ b/blog/src/assets/media/muse-code-vs-claude-code/motion.js @@ -0,0 +1,377 @@ +/* Bright coded scenes for "Muse Code vs Claude Code". 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 = 20) => { + 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 damped ring: 0 before it starts, then a fading wobble */ + const ringing = (w, amp, f = 8, d = 1.6) => (w < 0 ? 0 : amp * Math.exp(-d * w) * Math.sin(f * w)); + + const SC = {}; + + /* ---- 1. four bells on a string, one per plan; each rings and its price tag swings out ---- */ + SC.bells = { + dur: 12, still: 7.9, + build(svg, api) { + txt(svg, 44, 62, "Muse Code's four plans", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a bell", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const PL = [ + ["Everyday Usage", "$5.00", MINT, 0.72, "Everyday Usage, $5.00/month: 10 to 50 prompts every 5 hours"], + ["High Usage", "$15.00", SKY, 0.9, "High Usage, $15.00/month: 5x more usage than Everyday Usage"], + ["Power Usage", "$50.00", LIL, 1.08, "Power Usage, $50.00/month: Muse Wonder Full; 20x more usage"], + ["Maximum Usage", "$200.00", YEL, 1.26, "Maximum Usage, $200.00/month: 80x more usage than Everyday"], + ]; + const X = [128, 308, 490, 672], RING = [1, 2.6, 4.2, 5.8], ALL = 7.4, TY = 318; + const sy = (x) => { const s = (x - 40) / 720; return 112 + 76 * s * (1 - s); }; + el("path", { d: "M40 112 Q400 150 760 112", stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round" }, svg); + [40, 760].forEach((x) => el("circle", { cx: x, cy: 112, r: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg)); + const bellD = (k) => { + const f = (v) => (v * k).toFixed(1); + return `M0 ${f(16)} C${f(-26)} ${f(16)} ${f(-34)} ${f(34)} ${f(-36)} ${f(60)} C${f(-38)} ${f(84)} ${f(-46)} ${f(96)} ${f(-62)} ${f(104)} L${f(-62)} ${f(112)} H${f(62)} L${f(62)} ${f(104)} C${f(46)} ${f(96)} ${f(38)} ${f(84)} ${f(36)} ${f(60)} C${f(34)} ${f(34)} ${f(26)} ${f(16)} 0 ${f(16)} Z`; + }; + let pinned = -1; + const rung = [-99, -99, -99, -99]; + const B = PL.map(([name, price, col, k], i) => { + const x = X[i], y = sy(x), g = el("g", {}, svg), M = 112 * k - 16 * k + 7; + el("rect", { x: x - 86, y: y - 4, width: 172, height: 474 - y, rx: 18, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + const arcs = el("g", {}, g), AR = []; + [-1, 1].forEach((d) => [0].forEach((j) => AR.push(el("path", { d: `M${d * (66 * k + 12 + j * 16)} ${70 * k - 16 - j * 8} q${d * (10 + j * 6)} ${16 + j * 8} 0 ${32 + j * 16}`, stroke: BLUE, "stroke-width": 4, fill: "none", "stroke-linecap": "round" }, arcs)))); + const tag = el("g", {}, g), L = TY - (y + M); + el("path", { d: `M0 0 V${L}`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, tag); + el("rect", { x: -72, y: L, width: 144, height: 82, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, tag); + el("circle", { cx: 0, cy: L + 13, r: 5, fill: col, stroke: INK, "stroke-width": 2.5 }, tag); + txt(tag, 0, L + 50, price, { "text-anchor": "middle", "font-size": 30, "font-weight": 700 }); + txt(tag, 0, L + 72, "a month", { "text-anchor": "middle", "font-size": 18, "font-weight": 500, fill: MUTE }); + const bell = el("g", {}, g); + el("circle", { cx: 0, cy: 16 * k - 7, r: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, bell); + const clap = el("g", {}, bell); + el("path", { d: `M0 ${16 * k} V${112 * k + 4}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, clap); + el("circle", { cx: 0, cy: 112 * k + 10, r: 9, fill: INK }, clap); + el("path", { d: bellD(k), fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, bell); + el("path", { d: `M${-50 * k} ${98 * k} Q0 ${92 * k} ${50 * k} ${98 * k}`, stroke: INK, "stroke-width": 2.5, fill: "none", "stroke-linecap": "round" }, bell); + el("path", { d: `M${-20 * k} ${30 * k} Q${-27 * k} ${50 * k} ${-28 * k} ${74 * k}`, stroke: "#fff", "stroke-width": 5, fill: "none", "stroke-linecap": "round" }, bell); + txt(g, x, 452, name, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, PL[i][4], () => { pinned = i; rung[i] = api.now(); api.poke(); }); + return { x, y, k, M, bell, clap, tag, arcs, AR }; + }); + const cap = strip(svg, 504, 740); + return (t, now) => { + const out = seg(t, 11.2, 11.8); + let last = -1; + B.forEach((b, i) => { + const w1 = t - RING[i], w2 = t - ALL, wu = now - rung[i]; + const a = ringing(w1, 12) + ringing(w2, 9) + (wu < 5 ? ringing(wu, 12) : 0); + b.bell.setAttribute("transform", `translate(${b.x} ${b.y}) rotate(${a}) translate(0 ${7 - 16 * b.k})`); + b.clap.setAttribute("transform", `rotate(${-a * 0.9} 0 ${16 * b.k})`); + const r = (a * Math.PI) / 180, mx = b.x - b.M * Math.sin(r), my = b.y + b.M * Math.cos(r); + const sw = ringing(w1 - 0.12, -9, 7, 1.3) + ringing(w2 - 0.12, -7, 7, 1.3) + (wu < 5 ? ringing(wu - 0.12, -9, 7, 1.3) : 0) + Math.sin(now * 1.3 + i) * 1.2; + const rv = rung[i] > -99 ? 1 : ob(seg(t, RING[i] + 0.05, RING[i] + 0.45)) * (1 - out); + b.tag.setAttribute("transform", `translate(${mx} ${my}) rotate(${sw}) scale(${cl(0.6 + 0.4 * rv, 0, 1.1)})`); + b.tag.setAttribute("opacity", rv <= 0.01 ? 0 : cl(rv * 1.5)); + const pulse = (w) => (w >= 0 && w < 1.3 ? 1 - w / 1.3 : 0), p = Math.max(pulse(w1), pulse(w2), wu < 5 ? pulse(wu) : 0); + b.arcs.setAttribute("transform", `translate(${b.x} ${b.y})`); + b.arcs.setAttribute("opacity", p); + if (t >= RING[i] && t < 11.2) last = i; + }); + cap.textContent = pinned >= 0 ? PL[pinned][4] : last < 0 || t >= 11.2 ? "Meta's Muse Code page, read 14 Oct 2026" : t >= ALL ? "From $5.00 to $200.00 a month" : PL[last][4]; + }; + }, + }; + + /* ---- 2. two umbrella stands: Muse models drop into one, Claude models into the other ---- */ + SC.umbrellas = { + dur: 12, still: 9.4, + build(svg, api) { + txt(svg, 44, 62, "Which models do they use?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a stand", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const RY = 384, F = 482, O = 156; + el("path", { d: `M40 ${F} H760`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const umb = (g, col) => { + el("path", { d: "M0 2 V-44 a16 16 0 0 0 -32 0 v8", stroke: INK, "stroke-width": 7, fill: "none", "stroke-linecap": "round" }, g); + el("path", { d: "M-7 0 L-22 34 Q-30 200 -12 284 L0 300 L12 284 Q30 200 22 34 L7 0 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("path", { d: "M0 10 Q-6 160 0 290 M-12 32 Q-17 160 -5 280", stroke: INK, "stroke-width": 2, fill: "none", "stroke-linecap": "round" }, g); + el("path", { d: "M0 300 V312", stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, g); + }; + const ST = [ + { cx: 270, W: 270, col: SKY, label: "Muse Code", line: "Muse Code uses Meta's Muse models", tap: "Power Usage adds “the most powerful Muse models”" }, + { cx: 592, W: 230, col: LIL, label: "Claude Code", line: "Claude Code uses Claude models", tap: "Claude Code runs Anthropic's Claude models" }, + ]; + ST.forEach((s) => el("ellipse", { cx: s.cx, cy: RY, rx: s.W / 2, ry: 16, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg)); + const U = []; + [["Muse Spark", YEL, 190, 40], ["Muse Wonder", LIL, 270, 102], ["Muse Image", MINT, 350, 164]].forEach(([s, col, x, py], i) => { + const g = el("g", { "pointer-events": "none" }, svg); + umb(g, col); + U.push({ g, s, x, py, st: 0, t0: 0.9 + i * 0.75 }); + }); + { + const g = el("g", { "pointer-events": "none" }, svg); + [[-36, BLUE], [36, YEL]].forEach(([dx, col]) => umb(el("g", { transform: `translate(${dx} 0)` }, g), col)); + U.push({ g, s: "Claude models", x: 592, py: 102, st: 1, t0: 3.6, fs: 22 }); + } + U.forEach((u) => { u.pg = el("g", { "pointer-events": "none" }, svg); u.p = el("g", {}, u.pg); pill(u.p, u.s, "#fff", u.fs || 20); }); + const hit = [-99, -99]; + let pinned = -1; + ST.forEach((s, i) => { + const L = s.cx - s.W / 2, R = s.cx + s.W / 2, g = el("g", {}, svg); + el("rect", { x: L - 10, y: 116, width: s.W + 20, height: F + 10 - 116, rx: 20, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + el("path", { d: `M${L} ${RY} L${L + 12} ${F} H${R - 12} L${R} ${RY} A${s.W / 2} 16 0 0 1 ${L} ${RY} Z`, fill: s.col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + const pw = s.label.length * 14 + 30; + el("rect", { x: s.cx - pw / 2, y: RY + 32, width: pw, height: 46, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + txt(g, s.cx, RY + 63, s.label, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + button(g, `${s.label}: ${s.tap}`, () => { pinned = i; hit[i] = api.now(); api.poke(); }); + }); + const cap = strip(svg, 504, 740); + return (t, now) => { + const out = seg(t, 11.2, 11.8); + U.forEach((u, i) => { + const q = seg(t, u.t0, u.t0 + 0.5), bo = Math.sin(seg(t, u.t0 + 0.5, u.t0 + 0.85) * Math.PI) * -12; + const w = now - hit[u.st], jig = w >= 0 && w < 1.2 ? Math.sin(w * 14) * 7 * (1 - w / 1.2) : 0; + const sway = Math.sin(now * 1.1 + i * 1.7) * 1 + jig; + const tr = `translate(${u.x} ${O - 470 * (1 - q * q) + bo}) rotate(${sway} 0 ${RY - O})`, op = hit[u.st] > -99 ? 1 : seg(t, u.t0, u.t0 + 0.1) * (1 - out); + u.g.setAttribute("transform", tr); u.pg.setAttribute("transform", tr); + u.g.setAttribute("opacity", op); u.pg.setAttribute("opacity", op); + const pk = hit[u.st] > -99 ? 1 : ob(seg(t, u.t0 + 0.55, u.t0 + 0.95)); + u.p.setAttribute("transform", `translate(0 ${u.py}) scale(${cl(pk, 0, 1.2)})`); + u.p.setAttribute("opacity", pk <= 0.01 ? 0 : 1); + }); + cap.textContent = pinned >= 0 ? ST[pinned].tap : t < 0.9 || t >= 11.2 ? "Each comes from one lab and runs that lab's models" : t < 3.6 ? ST[0].line : t < 6.4 ? ST[1].line : "Every Muse Code plan lists Muse Spark, Muse Wonder and Muse Image"; + }; + }, + }; + + /* ---- 3. a brass telescope swings across two signs and brings each platform into focus ---- */ + SC.telescope = { + dur: 12, still: 9.6, + build(svg, api) { + txt(svg, 44, 62, "Where do they run?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a platform", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const MUSE = "Meta's page: “Available for MacOS and Windows.”"; + const IT = [ + ["MacOS", YEL, 0, 0, MUSE], ["Windows", MINT, 0, 1, MUSE], + ["terminal", SKY, 1, 0, "Claude Code's overview names the terminal"], ["IDE", LIL, 1, 0, "IDE extensions for VS Code and JetBrains"], + ["desktop app", MINT, 1, 1, "Claude Code's overview names a desktop app"], ["web", YEL, 1, 1, "And the web at claude.ai/code"], + ]; + const BD = [{ x0: 34, x1: 304, name: "Muse Code", sh: LIL }, { x0: 440, x1: 766, name: "Claude Code", sh: SKY }]; + const BT = 86, BB = 318, RW = [194, 264], PH = 56, PX = 372, PY = 420; + BD.forEach((b) => { + el("rect", { x: b.x0 + 10, y: BT + 10, width: b.x1 - b.x0, height: BB - BT, rx: 22, fill: b.sh }, svg); + el("rect", { x: b.x0, y: BT, width: b.x1 - b.x0, height: BB - BT, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + txt(svg, (b.x0 + b.x1) / 2, BT + 44, b.name, { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + el("path", { d: `M${b.x0 + 22} ${BT + 64} H${b.x1 - 22}`, stroke: SOFT, "stroke-width": 3, "stroke-linecap": "round" }, svg); + }); + const beam = el("path", { fill: LIL, "fill-opacity": 0.22 }, svg); + const defs = el("defs", {}, svg); + let user = null, curA = -90; + const S = IT.map(([s, col, b, row, line], i) => { + const g = el("g", {}, svg), f = el("filter", { id: `mg-tel-${i}`, x: "-40%", y: "-80%", width: "180%", height: "260%" }, defs); + const blur = el("feGaussianBlur", { stdDeviation: 6 }, f); + const ring = el("rect", { y: RW[row] - PH / 2 - 7, height: PH + 14, rx: PH / 2 + 7, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + const v = el("g", { filter: `url(#mg-tel-${i})` }, g); + const r = el("rect", { y: RW[row] - PH / 2, height: PH, rx: PH / 2, fill: col, stroke: INK, "stroke-width": 3 }, v); + const tx = txt(v, 0, RW[row] + 8, s, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + const w = Math.max(88, tx.getComputedTextLength() + 36); + button(g, `${s}: ${line}`, () => { user = { from: curA, i, t0: api.now() }; api.poke(); }); + return { s, b, row, line, w, r, ring, tx, v, blur }; + }); + [0, 1].forEach((b) => [0, 1].forEach((row) => { + const R = S.filter((o) => o.b === b && o.row === row), tot = R.reduce((a, o) => a + o.w, 0) + 18 * (R.length - 1); + let x = (BD[b].x0 + BD[b].x1) / 2 - tot / 2; + R.forEach((o) => { o.cx = x + o.w / 2; o.r.setAttribute("x", x); o.r.setAttribute("width", o.w); o.ring.setAttribute("x", x - 7); o.ring.setAttribute("width", o.w + 14); o.tx.setAttribute("x", o.cx); x += o.w + 18; }); + })); + S.forEach((o) => { o.ang = (Math.atan2(RW[o.row] - PY, o.cx - PX) * 180) / Math.PI; }); + el("path", { d: "M40 482 H760", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("path", { d: `M${PX} ${PY} L${PX - 62} 482 M${PX} ${PY} L${PX + 62} 482 M${PX} ${PY} V482`, stroke: INK, "stroke-width": 5, fill: "none", "stroke-linecap": "round" }, svg); + const scope = el("g", { "pointer-events": "none" }, svg); + el("rect", { x: -76, y: -12, width: 28, height: 24, rx: 6, fill: SKY, stroke: INK, "stroke-width": 3 }, scope); + el("path", { d: "M-52 -15 L88 -24 V24 L-52 15 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, scope); + el("path", { d: "M4 -19 V19 M46 -22 V22", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, scope); + el("rect", { x: 84, y: -30, width: 28, height: 60, rx: 8, fill: YEL, stroke: INK, "stroke-width": 3 }, scope); + el("ellipse", { cx: 113, cy: 0, rx: 4.5, ry: 22, fill: SKY, stroke: INK, "stroke-width": 2.5 }, scope); + el("circle", { cx: PX, cy: PY, r: 11, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const FT = [1.2, 2.6, 4.2, 5.4, 6.6, 7.8]; + const cap = strip(svg, 504, 740); + return (t, now) => { + const out = seg(t, 11, 11.6); + let a = -90, cur = -1; + S.forEach((o, i) => { a = lerp(a, o.ang, io(seg(t, FT[i] - 0.8, FT[i]))); if (t >= FT[i] - 0.2) cur = i; }); + a = lerp(a, -90, io(seg(t, 11, 11.8))); + let tgt = cur >= 0 && t < 11 ? cur : -1, bo = tgt >= 0 ? seg(t, FT[tgt] - 0.1, FT[tgt] + 0.25) : 0; + if (user) { const k = io(cl((now - user.t0) / 0.6)); a = lerp(user.from, S[user.i].ang, k); tgt = user.i; bo = seg(k, 0.8, 1); } + curA = a; + tf(scope, PX, PY, 1, a); + S.forEach((o, i) => { + const fo = user ? 1 : seg(t, FT[i] - 0.2, FT[i] + 0.3) * (1 - out); + o.blur.setAttribute("stdDeviation", (7 * (1 - fo)).toFixed(2)); + o.v.setAttribute("opacity", (0.3 + 0.7 * fo).toFixed(3)); + const pop = user && user.i === i ? Math.sin(seg(now - user.t0, 0.5, 0.9) * Math.PI) * 0.08 : Math.sin(seg(t, FT[i], FT[i] + 0.35) * Math.PI) * 0.08; + o.v.setAttribute("transform", `translate(${o.cx} ${RW[o.row]}) scale(${1 + pop}) translate(${-o.cx} ${-RW[o.row]})`); + }); + if (tgt >= 0 && bo > 0) { + const r = (a * Math.PI) / 180, ox = PX + 116 * Math.cos(r), oy = PY + 116 * Math.sin(r), o = S[tgt], y = RW[o.row]; + beam.setAttribute("d", `M${ox.toFixed(1)} ${oy.toFixed(1)} L${o.cx - o.w / 2 + 10} ${y} L${o.cx + o.w / 2 - 10} ${y} Z`); + beam.setAttribute("opacity", bo); + } else beam.setAttribute("opacity", 0); + cap.textContent = user ? S[user.i].line : cur < 0 || t >= 11 ? "Meta's Muse Code page and Anthropic's Claude Code overview" : t >= 9 ? "The Muse Code page does not mention Linux." : S[cur].line; + }; + }, + }; + + /* ---- 4. a desk fan turns to each paper sign; the sign flutters and shows its tag ---- */ + SC.fan = { + dur: 12, still: 7, + build(svg, api) { + txt(svg, 44, 62, "Which should you pick?", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a sign", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const F = 482, HX = 400, HY = 222, PT = 132, PH = 150; + const LINES = ["Pick Muse Code if price matters most", "Pick Claude Code if you need Linux, an IDE or the browser"]; + el("path", { d: `M40 ${F} H760`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const SG = [ + { cx: 150, name: "Muse Code", tag: ["$5.00 a month"], col: YEL, dir: -1, t0: 1.9 }, + { cx: 650, name: "Claude Code", tag: ["terminal, IDE,", "desktop and web"], col: SKY, dir: 1, t0: 5.3 }, + ]; + let user = null, curP = 0; + SG.forEach((s, i) => { + const g = el("g", {}, svg); + el("rect", { x: s.cx - 106, y: PT - 22, width: 212, height: F - PT + 30, rx: 20, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + el("path", { d: `M${s.cx} ${PT - 6} V${F - 8}`, stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, g); + el("rect", { x: s.cx - 46, y: F - 14, width: 92, height: 14, rx: 7, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); + const tag = el("g", {}, g), th = s.tag.length * 28 + 34; + el("path", { d: "M0 0 V32", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, tag); + el("rect", { x: -98, y: 30, width: 196, height: th, rx: 14, fill: s.col, stroke: INK, "stroke-width": 3 }, tag); + s.tag.forEach((l, j) => txt(tag, 0, 30 + 42 + j * 28 - (s.tag.length - 1) * 2, l, { "text-anchor": "middle", "font-size": s.tag.length > 1 ? 20 : 23, "font-weight": 700 })); + const pap = el("g", {}, g); + el("rect", { x: s.cx - 92, y: PT, width: 184, height: PH, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, pap); + el("path", { d: `M${s.cx - 64} ${PT + 104} H${s.cx + 64} M${s.cx - 42} ${PT + 124} H${s.cx + 42}`, stroke: SOFT, "stroke-width": 4, "stroke-linecap": "round" }, pap); + txt(pap, s.cx, PT + 78, s.name, { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + el("rect", { x: s.cx - 22, y: PT - 14, width: 44, height: 24, rx: 6, fill: INK }, g); + el("path", { d: `M${s.cx - 14} ${PT - 14} L${s.cx - 8} ${PT - 30} H${s.cx + 8} L${s.cx + 14} ${PT - 14}`, stroke: INK, "stroke-width": 3, fill: "none", "stroke-linejoin": "round" }, g); + s.pap = pap; s.tagG = tag; + button(g, `${s.name}: ${LINES[i]}`, () => { user = { i, from: curP, t0: api.now() }; api.poke(); }); + }); + const winds = SG.map((s) => { + const g = el("g", { "pointer-events": "none" }, svg), x0 = HX + s.dir * 76, x1 = s.cx - s.dir * 100; + return { g, s, P: [176, 222, 268].map((y, j) => el("path", { d: `M${x0} ${y} q${(x1 - x0) / 4} -14 ${(x1 - x0) / 2} 0 t${(x1 - x0) / 2} 0`, stroke: j === 1 ? LIL : SKY, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-dasharray": "22 16" }, g)) }; + }); + el("rect", { x: HX - 72, y: F - 24, width: 144, height: 24, rx: 10, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: HX - 9, y: HY + 60, width: 18, height: F - 24 - HY - 60, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const motor = el("g", { "pointer-events": "none" }, svg); + el("circle", { r: 46, fill: SOFT, stroke: INK, "stroke-width": 3 }, motor); + const head = el("g", { "pointer-events": "none" }, svg); + el("circle", { r: 96, fill: "#fff", stroke: INK, "stroke-width": 3 }, head); + const blades = el("g", {}, head); + [0, 120, 240].forEach((r) => el("path", { d: "M0 0 C18 -14 56 -44 74 -16 C84 2 44 18 0 0 Z", fill: SKY, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", transform: `rotate(${r})` }, blades)); + el("circle", { r: 64, fill: "none", stroke: LIL, "stroke-width": 2 }, head); + for (let k = 0; k < 12; k++) { const a = (k / 12) * 6.2832; el("path", { d: `M${(Math.cos(a) * 20).toFixed(1)} ${(Math.sin(a) * 20).toFixed(1)} L${(Math.cos(a) * 94).toFixed(1)} ${(Math.sin(a) * 94).toFixed(1)}`, stroke: LIL, "stroke-width": 2 }, head); } + el("circle", { r: 96, fill: "none", stroke: INK, "stroke-width": 3 }, head); + el("circle", { r: 16, fill: YEL, stroke: INK, "stroke-width": 3 }, head); + const K = [[0, 0], [0.8, 0], [1.6, -1], [4, -1], [5, 1], [7.6, 1], [8.4, 0], [12, 0]]; + const phi = (t) => { for (let i = 1; i < K.length; i++) if (t < K[i][0]) return lerp(K[i - 1][1], K[i][1], io(seg(t, K[i - 1][0], K[i][0]))); return 0; }; + const cap = strip(svg, 504, 740); + return (t, now) => { + const out = seg(t, 11.2, 11.8); + let p = phi(t); + if (user) p = lerp(user.from, user.i ? 1 : -1, io(cl((now - user.t0) / 0.6))); + curP = p; + const sx = 1 - 0.42 * Math.abs(p); + head.setAttribute("transform", `translate(${HX + p * 24} ${HY}) scale(${sx} 1)`); + motor.setAttribute("transform", `translate(${HX - p * 40} ${HY})`); + blades.setAttribute("transform", `rotate(${(t * 760) % 360})`); + SG.forEach((s, i) => { + const gust = cl(s.dir * p * 1.4 - 0.4), fl = gust * (7 + 3 * Math.sin(now * 11 + i)); + const ang = s.dir * fl, shift = PH * Math.tan((ang * Math.PI) / 180); + s.pap.setAttribute("transform", `translate(${s.cx} ${PT}) skewX(${ang}) scale(1 ${1 - 0.05 * gust * (0.5 + 0.5 * Math.sin(now * 9))}) translate(${-s.cx} ${-PT})`); + const rv = user && user.i === i ? 1 : ob(seg(t, s.t0, s.t0 + 0.7)) * (user ? 1 : 1 - out); + const sw = s.dir * (gust * 3.5 + Math.sin(now * 5 + i) * 1.5 * gust) + Math.sin(now * 1.4 + i) * 1.2 * cl(rv); + s.tagG.setAttribute("transform", `translate(${s.cx + shift} ${PT + PH}) rotate(${sw}) translate(0 ${-150 * (1 - cl(rv, 0, 1.05))})`); + s.tagG.setAttribute("opacity", rv < 0.05 ? 0 : 1); + winds[i].g.setAttribute("opacity", gust); + winds[i].P.forEach((w) => w.setAttribute("stroke-dashoffset", (-s.dir * t * 90).toFixed(1))); + }); + cap.textContent = user ? LINES[user.i] : t < 1.6 || t >= 11.2 ? "Our view: pick by budget and platform" : t < 4.6 ? LINES[0] : t < 8.4 ? LINES[1] : "Try both on a real task from your own repo"; + }; + }, + }; + + /* ---- title card, 16 by 9: two spinning tops face off ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 128, "Plans, prices, models and where they run", { "font-size": 25, "font-weight": 500, fill: MUTE }); + txt(svg, 62, 222, "Muse Code vs", { "font-size": 74, "font-weight": 700 }); + txt(svg, 62, 306, "Claude Code", { "font-size": 74, "font-weight": 700 }); + el("path", { d: "M68 334 H520", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 62, y: 378, width: 312, height: 56, rx: 28, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 218, 415, "Checked 14 Oct 2026", { "text-anchor": "middle", "font-size": 25, "font-weight": 700 }); + const top = (x, y, r, body, band) => { + el("ellipse", { cx: x, cy: y + 4, rx: 46, ry: 8, fill: SOFT }, svg); + const g = el("g", { transform: `translate(${x} ${y}) rotate(${r})` }, svg); + el("rect", { x: -9, y: -186, width: 18, height: 56, rx: 9, fill: band, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: "M0 0 L-64 -70 C-78 -84 -72 -110 -48 -118 C-18 -126 18 -126 48 -118 C72 -110 78 -84 64 -70 Z", fill: body, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("path", { d: "M-72 -92 Q0 -72 72 -92 L68 -78 Q0 -58 -68 -78 Z", fill: band, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, g); + el("ellipse", { cx: 0, cy: -120, rx: 40, ry: 9, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + el("path", { d: "M-76 -30 Q0 2 76 -30", stroke: LIL, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-dasharray": "1 14" }, g); + }; + top(690, 440, -6, LIL, YEL); + top(850, 452, 6, BLUE, SKY); + [[640, 120, 15, SKY], [900, 186, 18, YEL], [588, 476, 12, MINT]].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/muse-code-vs-claude-code/telescope.png b/blog/src/assets/media/muse-code-vs-claude-code/telescope.png new file mode 100644 index 000000000..f60dbb29a Binary files /dev/null and b/blog/src/assets/media/muse-code-vs-claude-code/telescope.png differ diff --git a/blog/src/assets/media/muse-code-vs-claude-code/umbrellas.png b/blog/src/assets/media/muse-code-vs-claude-code/umbrellas.png new file mode 100644 index 000000000..bd1e224d6 Binary files /dev/null and b/blog/src/assets/media/muse-code-vs-claude-code/umbrellas.png differ diff --git a/blog/src/assets/media/opencode-2-0/bricks.png b/blog/src/assets/media/opencode-2-0/bricks.png new file mode 100644 index 000000000..5fee73d25 Binary files /dev/null and b/blog/src/assets/media/opencode-2-0/bricks.png differ diff --git a/blog/src/assets/media/opencode-2-0/dominoes.png b/blog/src/assets/media/opencode-2-0/dominoes.png new file mode 100644 index 000000000..bcfe2fb35 Binary files /dev/null and b/blog/src/assets/media/opencode-2-0/dominoes.png differ diff --git a/blog/src/assets/media/opencode-2-0/ferris.png b/blog/src/assets/media/opencode-2-0/ferris.png new file mode 100644 index 000000000..787e08b10 Binary files /dev/null and b/blog/src/assets/media/opencode-2-0/ferris.png differ diff --git a/blog/src/assets/media/opencode-2-0/hero.png b/blog/src/assets/media/opencode-2-0/hero.png new file mode 100644 index 000000000..6a935c451 Binary files /dev/null and b/blog/src/assets/media/opencode-2-0/hero.png differ diff --git a/blog/src/assets/media/opencode-2-0/lighthouse.png b/blog/src/assets/media/opencode-2-0/lighthouse.png new file mode 100644 index 000000000..037c92ba3 Binary files /dev/null and b/blog/src/assets/media/opencode-2-0/lighthouse.png differ diff --git a/blog/src/assets/media/opencode-2-0/motion.css b/blog/src/assets/media/opencode-2-0/motion.css new file mode 100644 index 000000000..8a16017be --- /dev/null +++ b/blog/src/assets/media/opencode-2-0/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "OpenCode 2.0: what changed and how to upgrade". 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/opencode-2-0/motion.js b/blog/src/assets/media/opencode-2-0/motion.js new file mode 100644 index 000000000..04572534c --- /dev/null +++ b/blog/src/assets/media/opencode-2-0/motion.js @@ -0,0 +1,351 @@ +/* Bright coded scenes for "OpenCode 2.0: what changed and how to upgrade". 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 lerp = (a, b, k) => a + (b - a) * k; + const D2R = Math.PI / 180; + /* a soft rounded strip along the bottom that carries one line of the post */ + const strip = (svg, y, w = 720) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT, stroke: INK, "stroke-width": 2 }, svg); + return txt(svg, 400, y + 32, "", { "text-anchor": "middle", "font-size": 22, "font-weight": 600 }); + }; + + const SC = {}; + + /* ---- 1. a lighthouse: one beam turns across the sea and lights each boat it reaches ---- */ + SC.lighthouse = { + dur: 12, still: 10.4, + build(svg, api) { + txt(svg, 44, 62, "What is the OpenCode service?", { "font-size": 32, "font-weight": 700 }); + const LX = 650, LY = 146, HZ = 236; + /* sea */ + el("rect", { x: 30, y: HZ, width: 740, height: 268, rx: 20, fill: SKY, "fill-opacity": 0.22 }, svg); + el("path", { d: `M44 ${HZ} H756`, stroke: SKY, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const waves = [[70, 280], [250, 268], [420, 300], [90, 420], [240, 470], [560, 260], [380, 410], [190, 330]].map(([x, y]) => ({ x, y, n: el("path", { d: "M0 0 q10 -8 20 0 t20 0", stroke: SKY, "stroke-width": 3, fill: "none", "stroke-linecap": "round" }, svg) })); + /* the beam sits behind the boats */ + const clip = el("clipPath", { id: "mg-oc-sea" }, el("defs", {}, svg)); + el("rect", { x: 0, y: 0, width: 800, height: 504 }, clip); + const lightG = el("g", { "clip-path": "url(#mg-oc-sea)" }, svg); + const halo = el("path", { fill: YEL, "fill-opacity": 0.22 }, lightG); + const beam = el("path", { fill: YEL, "fill-opacity": 0.5 }, lightG); + const BOATS = [["TUI", 130, 290], ["web", 262, 364], ["desktop app", 402, 452]]; + const ang = (x, y) => Math.atan2(y - LY, x - LX) / D2R; + let user = null; + const boats = BOATS.map(([name, x, y], i) => { + const g = el("g", {}, svg); + const probe = txt(g, 0, 0, name, { "font-size": 24, "font-weight": 700 }); + const tw = probe.getComputedTextLength(); probe.remove(); + const hw = Math.max(70, tw / 2 + 30); + el("rect", { x: -hw - 14, y: -100, width: 2 * hw + 28, height: 152, rx: 18, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + const glow = el("circle", { cx: 12, cy: -72, r: 24, fill: YEL, "fill-opacity": 0.4 }, g); + el("path", { d: "M12 -4 V-62", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, g); + const lamp = el("circle", { cx: 12, cy: -72, r: 10, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: -hw + 18, y: -36, width: 44, height: 32, rx: 6, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M${-hw} -4 H${hw} L${hw - 22} 40 H${-hw + 22} Z`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + txt(g, 0, 26, name, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + button(g, `${name}: one background process shared by the TUI, web and desktop app`, () => { user = { i, t0: api.now() }; api.poke(); }); + return { g, x, y, a: ang(x + 12, y - 72), glow, lamp }; + }); + /* the lighthouse, on a rock at the right */ + const lh = el("g", {}, svg); + el("path", { d: `M${LX - 100} 478 Q${LX - 92} 430 ${LX - 52} 426 H${LX + 60} Q${LX + 104} 432 ${LX + 110} 478 Z`, fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, lh); + const tw = (y) => 30 + (y - 182) * (16 / 248); + el("path", { d: `M${LX - tw(430)} 430 L${LX - tw(182)} 182 H${LX + tw(182)} L${LX + tw(430)} 430 Z`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, lh); + [[230, 272], [318, 362]].forEach(([a, b]) => el("path", { d: `M${LX - tw(a)} ${a} H${LX + tw(a)} L${LX + tw(b)} ${b} H${LX - tw(b)} Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, lh)); + el("rect", { x: LX - 13, y: 392, width: 26, height: 38, rx: 10, fill: LIL, stroke: INK, "stroke-width": 3 }, lh); + el("rect", { x: LX - 50, y: 172, width: 100, height: 14, rx: 5, fill: YEL, stroke: INK, "stroke-width": 3 }, lh); + el("rect", { x: LX - 32, y: 118, width: 64, height: 54, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, lh); + const core = el("circle", { cx: LX, cy: LY, r: 15, fill: YEL, stroke: INK, "stroke-width": 3 }, lh); + el("path", { d: `M${LX - 12} 118 V172 M${LX + 12} 118 V172`, stroke: INK, "stroke-width": 2 }, lh); + el("path", { d: `M${LX - 44} 120 L${LX} 82 L${LX + 44} 120 Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, lh); + el("circle", { cx: LX, cy: 76, r: 7, fill: YEL, stroke: INK, "stroke-width": 3 }, lh); + const plate = el("g", { transform: `translate(${LX - 10} 500)` }, svg); + const pt = txt(plate, 0, 9, "OpenCode service", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + const pw = pt.getComputedTextLength() + 36; + el("rect", { x: -pw / 2, y: -24, width: pw, height: 48, rx: 24, fill: YEL, stroke: INK, "stroke-width": 3 }, plate).after(pt); + plate.setAttribute("transform", `translate(${Math.min(LX - 4, 768 - pw / 2)} 474)`); + /* the beam: down across the sea, then slowly back */ + const sweep = (t) => io(seg(t, 0.6, 6)) - io(seg(t, 6.4, 11.6)); + const th = (t) => 176 - 52 * sweep(t); + const litAt = boats.map((b) => { for (let s = 0; s < 6.2; s += 0.02) if (Math.abs(th(s) - b.a) < 5) return s; return 99; }); + const wedge = (a, h, len) => { const p = (d) => `${(LX + len * Math.cos(d * D2R)).toFixed(1)} ${(LY + len * Math.sin(d * D2R)).toFixed(1)}`; return `M${LX} ${LY} L${p(a - h)} L${p(a + h)} Z`; }; + const cap = strip(svg, 536 - 4, 740); + return (t, now) => { + const out = seg(t, 11.3, 11.8); + let a = th(t), held = -1; + if (user) { const w = now - user.t0, k = io(seg(w, 0, 0.5)) * (1 - io(seg(w, 3, 3.5))); a = lerp(a, boats[user.i].a, k); if (w < 3.5) held = user.i; else user = null; } + beam.setAttribute("d", wedge(a, 6, 760)); halo.setAttribute("d", wedge(a, 12, 760)); + const on = 1 - out; + beam.setAttribute("opacity", seg(t, 0.2, 0.6) * on + (held >= 0 ? 1 : 0) * out); + halo.setAttribute("opacity", seg(t, 0.2, 0.6) * on + (held >= 0 ? 1 : 0) * out); + core.setAttribute("fill", YEL); + boats.forEach((b, i) => { + tf(b.g, b.x, b.y + 3 * Math.sin(t * 1.6 + i * 2), 1, 1.5 * Math.sin(t * 1.3 + i)); + const lit = held === i ? 1 : seg(t, litAt[i], litAt[i] + 0.25) * on; + b.glow.setAttribute("opacity", lit); b.lamp.setAttribute("fill", lit > 0.5 ? YEL : SOFT); + }); + waves.forEach((w, i) => { const p = (t / 3 + i * 0.13) % 1; tf(w.n, w.x + 18 * p, w.y); w.n.setAttribute("opacity", Math.sin(Math.PI * p)); }); + cap.textContent = t < 6.2 || t >= 11.3 ? "One background process shared by the TUI, web and desktop app" : "a single background process on your machine"; + }; + }, + }; + + /* ---- 2. a domino run: one script tips every tool in a single fall ---- */ + SC.dominoes = { + dur: 12, still: 9.6, + build(svg, api) { + txt(svg, 44, 62, "What is Code Mode?", { "font-size": 32, "font-weight": 700 }); + const W = 40, H = 150, N = 9, X0 = 112, SP = 56; + const sm = (k) => k * k * (3 - 2 * k); + const g = (x) => 250 + 225 * sm(cl((x - 90) / 430)); + let d = "M30 590 V" + g(30); + for (let x = 30; x <= 770; x += 10) d += ` L${x} ${g(x).toFixed(1)}`; + el("path", { d: d + " V590 Z", fill: SOFT }, svg); + let top = "M30 " + g(30); + for (let x = 30; x <= 770; x += 10) top += ` L${x} ${g(x).toFixed(1)}`; + el("path", { d: top, stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + /* the card that carries the post's words */ + el("rect", { x: 452, y: 92, width: 314, height: 182, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const lines = [0, 1, 2, 3].map((k) => txt(svg, 476, 138 + k * 38, "", { "font-size": 26, "font-weight": 700 })); + const run = el("g", {}, svg); + el("rect", { x: 24, y: 84, width: 418, height: 186, rx: 20, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, run); + /* ramp and ball */ + el("path", { d: "M90 146 V" + g(90), stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, run); + el("path", { d: "M34 100 L102 143 L98 150 L30 107 Z", fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, run); + const ball = el("circle", { r: 14, fill: MINT, stroke: INK, "stroke-width": 3 }, run); + const COL = [SKY, LIL, MINT, BLUE]; + const ds = Array.from({ length: N }, (_, i) => { + const x = X0 + i * SP, by = g(x + W / 2), gr = el("g", {}, run); + el("rect", { x: -W, y: -H, width: W, height: H, rx: 7, fill: i ? COL[(i - 1) % 4] : YEL, stroke: INK, "stroke-width": 3 }, gr); + txt(gr, -W / 2, -H / 2 + 9, i ? "tool" : "script", { "text-anchor": "middle", "font-size": 25, "font-weight": 700, transform: `rotate(-90 ${-W / 2} ${-H / 2})` }); + return { gr, bx: x + W, by, t0: 1.25 + i * 0.3 }; + }); + button(run, "Replay the run: a single round trip instead of calling them one at a time", () => { api.seek(0.2); }); + /* does domino i, tipped to phi, touch the ground or domino i+1 (tipped to an)? checks points up its leading face */ + const hit = (i, phi, an) => { + const b = ds[i], s = Math.sin(phi * D2R), c = Math.cos(phi * D2R); + if (b.by - H * c >= g(b.bx + H * s) - 1.5) return true; + if (i === N - 1) return false; + const n = ds[i + 1], a = an * D2R, ca = Math.cos(a), sa = Math.sin(a); + for (const f of [0.3, 0.45, 0.6, 0.75, 0.9, 1]) { + const dx = b.bx + H * f * s - n.bx, dy = b.by - H * f * c - n.by; + const u = dx * ca + dy * sa, v = -dx * sa + dy * ca; + if (u >= -W && u <= 1.5 && v >= -H && v <= 0) return true; + } + return false; + }; + const EARLY = ["A script can call", "a tool, map over the", "results and call", "another tool"], LATE = ["Single round trip", "instead of calling", "them one at a time", ""]; + return (t) => { + const out = seg(t, 11.3, 11.8); + const angs = new Array(N); + for (let i = N - 1; i >= 0; i--) { + const k = seg(t, ds[i].t0, ds[i].t0 + 0.8), free = 90 * k * k; + let a = free; + for (let p = 0; p <= free; p += 0.5) if (hit(i, p, i < N - 1 ? angs[i + 1] : 0)) { a = p; break; } + angs[i] = a; + } + ds.forEach((b, i) => { b.gr.setAttribute("transform", `translate(${b.bx} ${b.by}) rotate(${angs[i] * (1 - out)})`); b.gr.setAttribute("opacity", 1 - out * 0.0); }); + /* the ball rolls down the ramp, knocks the script domino, settles back */ + const r = seg(t, 0.3, 1.25), back = seg(t, 1.25, 1.7); + const bx = lerp(47, 97, r * r) - 9 * Math.sin(Math.PI * back) - 3 * back, by = lerp(92, 123.5, r * r) - 8 * Math.sin(Math.PI * back) - 2 * back; + ball.setAttribute("cx", bx); ball.setAttribute("cy", by); + ball.setAttribute("opacity", seg(t, 0.1, 0.3) * (1 - out)); + const L = t >= 4.6 && t < 11.3 ? LATE : EARLY; + const m = L.filter(Boolean).length; + lines.forEach((n, k) => { n.textContent = L[k]; n.setAttribute("y", 138 + k * 38 + (4 - m) * 19); }); + }; + }, + }; + + /* ---- 3. a ferris wheel that never stops; cars clip on at the bottom as it turns ---- */ + SC.ferris = { + dur: 12, still: 10.9, + build(svg) { + const CX = 400, CY = 244, R = 172, GND = 512; + [[118, 118, 1.1], [690, 92, 0.9], [120, 400, 0.8], [688, 380, 1.0]].forEach(([x, y, s]) => { + const c = el("g", { transform: `translate(${x} ${y}) scale(${s})` }, svg); + [[-38, 2, 24], [-6, -16, 32], [32, -2, 25]].forEach(([cx, cy, r]) => el("circle", { cx, cy, r, fill: SOFT }, c)); + el("rect", { x: -62, y: 2, width: 119, height: 25, rx: 12.5, fill: SOFT }, c); + }); + el("path", { d: `M44 ${GND} H756`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("path", { d: `M${CX - 120} ${GND} L${CX} ${CY} L${CX + 120} ${GND}`, stroke: INK, "stroke-width": 7, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + el("path", { d: `M${CX - 72} ${GND - 100} H${CX + 72}`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + const wheel = el("g", {}, svg); + el("circle", { cx: 0, cy: 0, r: R, fill: "none", stroke: INK, "stroke-width": 5 }, wheel); + el("circle", { cx: 0, cy: 0, r: R - 22, fill: "none", stroke: LIL, "stroke-width": 4 }, wheel); + for (let k = 0; k < 12; k++) { const a = (k * 30 + 15) * D2R; el("path", { d: `M0 0 L${(R * Math.cos(a)).toFixed(1)} ${(R * Math.sin(a)).toFixed(1)}`, stroke: INK, "stroke-width": k % 2 ? 2 : 3 }, wheel); } + el("circle", { cx: 0, cy: 0, r: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, wheel); + el("circle", { cx: 0, cy: 0, r: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg).setAttribute("transform", `translate(${CX} ${CY})`); + el("rect", { x: CX - 92, y: GND - 18, width: 184, height: 18, rx: 6, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + /* six hanging points; each gets a car the first time it passes the bottom */ + const B = 15, OM = 30; + const order = [1, 0, 5, 4, 3, 2]; + const slots = Array.from({ length: 6 }, (_, k) => { + const pin = el("circle", { r: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const car = el("g", {}, svg); + const n = order.indexOf(k), name = n % 2 ? "shell" : "subagent"; + el("path", { d: "M0 0 V16", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, car); + el("path", { d: "M-46 24 Q0 6 46 24 Z", fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, car); + el("rect", { x: -64, y: 24, width: 128, height: 50, rx: 12, fill: n % 2 ? MINT : LIL, stroke: INK, "stroke-width": 3 }, car); + txt(car, 0, 57, name, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + let ta = (((90 - B - 60 * k) / OM) % 12 + 12) % 12; + return { pin, car, ta }; + }); + const cap = strip(svg, 530, 740); + return (t) => { + const out = seg(t, 11.3, 11.8), rot = OM * t; + wheel.setAttribute("transform", `translate(${CX} ${CY}) rotate(${rot})`); + slots.forEach((s, k) => { + const a = (B + 60 * k + rot) * D2R, x = CX + R * Math.cos(a), y = CY + R * Math.sin(a); + s.pin.setAttribute("cx", x); s.pin.setAttribute("cy", y); + const q = seg(t, s.ta - 0.35, s.ta + 0.15), lift = 1 - ob(q); + const sw = 3 * Math.sin(t * 2.2 + k); + s.car.setAttribute("transform", `translate(${x} ${y + 60 * lift}) rotate(${sw})`); + s.car.setAttribute("opacity", (q <= 0 ? 0 : cl(q * 3)) * (1 - out)); + }); + cap.textContent = t < 6 || t >= 11.3 ? "Hand work to a background subagent and continue" : "The shell tool can be invoked asynchronously"; + }; + }, + }; + + /* ---- 4. building bricks: plugins drop in and snap into a tower ---- */ + SC.bricks = { + dur: 12, still: 9.8, + build(svg, api) { + txt(svg, 44, 62, "What changed for plugins and the SDK?", { "font-size": 32, "font-weight": 700 }); + const BW = 170, BH = 58, TX = 286, BASE = 446, COL = [BLUE, YEL, LIL, MINT, SKY]; + const brick = (p, fill) => { + const g = el("g", {}, p); + [-38, 0, 38].forEach((x) => el("rect", { x: x - 13, y: -BH / 2 - 11, width: 26, height: 14, rx: 4, fill, stroke: INK, "stroke-width": 3 }, g)); + el("rect", { x: -BW / 2, y: -BH / 2, width: BW, height: BH, rx: 7, fill, stroke: INK, "stroke-width": 3 }, g); + txt(g, 0, 9, "plugin", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + return g; + }; + el("path", { d: "M40 490 H760", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const tower = el("g", {}, svg); + el("rect", { x: 40, y: 82, width: 480, height: 412, rx: 20, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, tower); + el("rect", { x: TX - 196, y: BASE, width: 392, height: 44, rx: 10, fill: SOFT, stroke: INK, "stroke-width": 3 }, tower); + txt(tower, TX, BASE + 32, "OpenCode 2.0", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + const bricks = []; + for (let r = 0; r < 5; r++) for (let c = 0; c < 2; c++) { + const i = bricks.length, off = r % 2 ? 16 : -16; + bricks.push({ g: brick(tower, COL[i % 5]), x: TX + off + (c ? BW / 2 : -BW / 2), y: BASE - BH * r - BH / 2, t0: 0.6 + i * 0.6 }); + } + let extra = -99; + const xb = { g: brick(tower, YEL), x: TX, y: BASE - BH * 5 - BH / 2 }; + button(tower, "Add a plugin: OpenCode can write a plugin for itself mid session and use it right away", () => { extra = api.now(); api.poke(); }); + /* the card beside the tower */ + el("rect", { x: 548, y: 160, width: 214, height: 260, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 655, 214, "there are now", { "text-anchor": "middle", "font-size": 26, "font-weight": 600, fill: MUTE }); + const num = txt(svg, 655, 324, "80+", { "text-anchor": "middle", "font-size": 92, "font-weight": 700 }); + txt(svg, 655, 384, "of them", { "text-anchor": "middle", "font-size": 26, "font-weight": 600, fill: MUTE }); + const track = el("rect", { x: 576, y: 284, width: 158, height: 22, rx: 11, fill: SOFT, stroke: INK, "stroke-width": 2 }, svg); + const fill = el("rect", { x: 576, y: 284, height: 22, rx: 11, fill: LIL, stroke: INK, "stroke-width": 2 }, svg); + const cap = strip(svg, 522, 740); + const drop = (b, k) => { + const y = b.y - 520 * (1 - k * k), land = k >= 1 ? 1 : 0; + b.g.setAttribute("transform", `translate(${b.x} ${y})`); + b.g.setAttribute("opacity", k <= 0 ? 0 : 1); + return land; + }; + return (t, now) => { + const out = seg(t, 11.3, 11.8); + let landed = 0; + bricks.forEach((b) => { landed += drop(b, seg(t, b.t0, b.t0 + 0.5)); b.g.setAttribute("opacity", (t >= b.t0 ? 1 : 0) * (1 - out)); }); + const w = now - extra, xk = seg(w, 0, 0.5); + drop(xb, xk); xb.g.setAttribute("opacity", w >= 0 && w < 6 ? 1 : 0); + const done = ob(seg(t, 6.6, 7.1)); + num.setAttribute("transform", `translate(655 300) scale(${cl(done, 0, 1.2)}) translate(-655 -300)`); + num.setAttribute("opacity", (done > 0.01 ? 1 : 0) * (1 - out)); + const f = cl(landed / 10), gone = done > 0.01 || out > 0 ? 0 : 1; + fill.setAttribute("width", Math.max(22, 158 * f)); fill.setAttribute("opacity", f > 0 ? gone : 0); + track.setAttribute("opacity", gone); + cap.textContent = w >= 0 && w < 6 ? "write a plugin for itself mid session and use it right away" : t < 7 || t >= 11.3 ? "nearly every 2.0 feature is an internal plugin" : "TUI plugins moved from experimental to stable"; + }; + }, + }; + + /* ---- title card, 16 by 9: a weathervane turning in the wind ---- */ + SC.hero = { + w: 960, h: 540, dur: 8, still: 4, + build(svg) { + txt(svg, 66, 150, "What changed", { "font-size": 30, "font-weight": 600, fill: MUTE }); + txt(svg, 60, 250, "OpenCode 2.0", { "font-size": 78, "font-weight": 700 }); + el("path", { d: "M68 284 H530", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + el("rect", { x: 62, y: 326, width: 330, height: 58, rx: 29, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 227, 364, "Checked 15 Oct 2026", { "text-anchor": "middle", "font-size": 27, "font-weight": 700 }); + el("path", { d: "M70 470 C190 414 300 510 420 454 S560 418 612 390", stroke: LIL, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-dasharray": "1 16" }, svg); + el("circle", { cx: 70, cy: 470, r: 11, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + const VX = 806; + const wind = [[650, 140, 60], [866, 120, 50], [640, 420, 54], [880, 380, 44]].map(([x, y, w]) => ({ x, y, n: el("path", { d: `M0 0 H${w}`, stroke: SKY, "stroke-width": 5, "stroke-linecap": "round" }, svg) })); + /* roof, pole, arms */ + el("path", { d: `M${VX - 112} 500 L${VX} 440 L${VX + 112} 500 Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("path", { d: `M${VX} 438 V118`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, svg); + el("path", { d: `M${VX - 92} 336 H${VX + 92}`, stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, svg); + el("path", { d: `M${VX - 48} 362 L${VX + 48} 310`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + [[VX - 92, 336, 11, LIL], [VX + 92, 336, 11, LIL], [VX - 48, 362, 8, SKY], [VX + 48, 310, 8, SKY]].forEach(([x, y, r, c]) => el("circle", { cx: x, cy: y, r, fill: c, stroke: INK, "stroke-width": 3 }, svg)); + el("circle", { cx: VX, cy: 336, r: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + /* the arrow turns about the pole */ + const arrow = el("g", {}, svg); + el("path", { d: "M-90 0 H80", stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, arrow); + el("path", { d: "M118 0 L76 -22 L84 0 L76 22 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, arrow); + el("path", { d: "M-60 0 L-96 -42 H-128 L-102 0 L-128 42 H-96 Z", fill: MINT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, arrow); + el("circle", { cx: VX, cy: 210, r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const cap = el("circle", { cx: VX, cy: 112, r: 12, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + [[600, 70, 14, SKY], [904, 262, 15, MINT], [520, 500, 12, 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)); + svg.appendChild(cap); + return (t) => { + const a = 28 * Math.sin((Math.PI * 2 * t) / 8) + 6 * Math.sin((Math.PI * 2 * t) / 2); + arrow.setAttribute("transform", `translate(${VX} 210) scale(${Math.cos(a * D2R).toFixed(3)} 1)`); + wind.forEach((w, i) => { const p = (t / 2 + i * 0.27) % 1; tf(w.n, w.x - 30 + 40 * p, w.y); w.n.setAttribute("opacity", Math.sin(Math.PI * p)); }); + }; + }, + }; + + /* ---- 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/posts/agent-tools-today-2026-10-15.md b/blog/src/posts/agent-tools-today-2026-10-15.md new file mode 100644 index 000000000..4bef0ea03 --- /dev/null +++ b/blog/src/posts/agent-tools-today-2026-10-15.md @@ -0,0 +1,58 @@ +--- +title: "Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season" +seoTitle: "Agent Tools Today (15 Oct 2026): Launches, Skills, MCP Servers" +description: "The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source." +date: 2026-10-15 +category: news +categoryLabel: News +type: Non-technical +series: agent-tools-today +primaryKeyword: "agent tools today 15 oct 2026" +secondaryKeywords: ["opencode 2.0", "hermes agent v0.22.0", "lfm2.5-24b-a2b", "pg_vectorsearch", "sgrep"] +tags: ["Daily Brief", "OpenCode", "Claude Code", "MCP", "Open Source"] +--- + +Agent tools today, 15 Oct 2026, in one line: Anomaly shipped OpenCode 2.0, a fresh Hermes Agent v0.22.0 desktop install sets itself up on a free trial model, and the loudest argument was whether agents go rogue or labs skip security basics. Every number was checked on 15 Oct. The edition before this one on the site is [13 Oct](/blog/agent-tools-today-2026-10-13/). + +[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 shipped no release in the last 24 hours, so it is not in the lists below. + +
Animation. A jigsaw puzzle on a white background. Four pieces slide in one after another and lock together, labelled Service, Code Mode, Background and Plugins. A final piece in the middle reads 2.0.
Anomaly's OpenCode 2.0 post lists a background service, Code Mode, background tasks and a rebuilt plugin system among its changes. The drawing shows them as puzzle pieces. Tap the puzzle to scatter and rebuild it.
+ +## Top 3 launches + +1. **OpenCode 2.0.** Anomaly's post, dated 12 Oct, calls it "a major overhaul". The OpenCode server now runs as one background process that the terminal, web and desktop apps all talk to. MCP servers are "opted into codemode by default", and the post says there are now "80+" internal plugins. Upgrade from the latest 1.x with `opencode upgrade --major`; 1.0 plugins do not work in 2.0. More than 370 points on Hacker News on 15 Oct. Full page: [OpenCode 2.0](/blog/opencode-2-0/). [Source](https://anoma.ly/notes/opencode-2-0/) +2. **LFM2.5-24B-A2B.** Liquid AI's post, dated 13 Oct, describes "a 24B-parameter reasoning model with just 2B active parameters, built for fast, private agents on-device". The post reports 184 tokens/s on an Apple M5 Max and says both models are open weight on Hugging Face. More than 100 points on Hacker News on 15 Oct. [Source](https://www.liquid.ai/blog/lfm2-5-24b-a2b) +3. **pg_vectorsearch.** A PostgreSQL extension from Timescale for approximate nearest neighbour search. The README says it can index existing pgvector columns "as-is", and marks the project "Status: alpha. Meant for testing, not production." PostgreSQL licence, more than 280 stars on 15 Oct. More than 190 points on Hacker News on 15 Oct. [Source](https://github.com/timescale/pg_vectorsearch) + +## Top 3 skills, MCP servers and plugins + +1. **sgrep.** Local code search for agents by words and meaning. The README says "No account, hosted index, or model server is needed", and it is candid that CK's semantic mode beats sgrep on its CoSQA test. Created 7 Oct. MIT, more than 260 stars on 15 Oct. [Source](https://github.com/context-dot-dev/sgrep) +2. **jevcrawl.** A web crawler and MCP server for agents, where a small model picks each link and passage. The README, read on 15 Oct, reports that on 50 WebWalkerQA tasks it answered 40% correctly at $0.025 per task, against 30% at $0.194 when Claude picks every link. Created 11 Oct. Apache 2.0, more than 50 stars on 15 Oct. [Source](https://github.com/RayenMlayeh/jevcrawl) +3. **Graphicle.** A code graph and project memory for coding agents, in one Rust program. The README lists Claude Code, Cursor, Codex, VS Code, Claude Desktop and "any MCP client", and says it needs no API key or account. Created 14 Oct. Apache 2.0, more than 130 stars on 15 Oct. More than 40 points on Hacker News. [Source](https://github.com/graphicle-ai/graphicle) + +## Top 3 moves from multi agent tools + +1. **Hermes Agent v0.22.0.** "The Threshold Release", published on GitHub on 14 Oct. The notes say a fresh desktop install "opens on a guided setup chat that runs on a free trial model", so a newcomer can talk to Hermes before owning an API key. They also list a one-click llama.cpp runtime, signed desktop packages "including the Microsoft Store" and "a 600-plugin catalog". MIT, more than 253,000 stars on 15 Oct. See [what is Hermes Agent](/blog/what-is-hermes-agent/). [Source](https://github.com/NousResearch/hermes-agent/releases/tag/v0.22.0) +2. **Claude Code v2.1.300.** Published 14 Oct. The notes add `/advisor` on Claude Platform on AWS, where "no advisor runs until you pick one", and a `prompt.change` mod hook. They also fix SessionEnd hooks sometimes not running when you exit during a background command. See [Claude Code hooks explained](/blog/claude-code-hooks-explained/). [Source](https://github.com/anthropics/claude-code/releases/tag/v2.1.300) +3. **Zed v1.24.2.** Published 14 Oct. The notes add "the ability to drag Terminal tabs into the Agent Panel" and better custom Amazon Bedrock model support. More than 91,000 stars on 15 Oct. [Source](https://github.com/zed-industries/zed/releases/tag/v1.24.2) + +## Top 3 rising repos + +1. **neckbeard.** A plugin for your coding agent that plans cloud infrastructure from a catalog of tested OpenTofu modules. The README promises infrastructure "you can actually review, with the monthly bill attached". Created 8 Sep. Apache 2.0, more than 180 stars on 15 Oct. [Source](https://github.com/bealesh/neckbeard) +2. **opencode-rust.** Its description reads "The open source AI coding agent, in Rust", with a CLI, a TUI and an HTTP server. It lives under a different GitHub account from Anomaly's anomalyco/opencode. Created 13 Oct. MIT, more than 70 stars on 15 Oct. [Source](https://github.com/vakandi/opencode-rust) +3. **heditor.** A video editing skill that its description says works with Claude Code, Codex, OpenCode, Cursor and Hermes, for editing vertical video. Created 13 Oct. GitHub does not detect a standard licence. More than 60 stars on 15 Oct. [Source](https://github.com/pycodebr/heditor) + +New repos can gain stars quickly for reasons other than use, so treat these as names to watch, not recommendations. + +## Top 4 things people are arguing about + +1. **Agents are not "going rogue".** A CSO Online article argues that recent incidents where models reached outside systems come from labs skipping basic containment and monitoring. Its opening blames lab failures, "not agents with minds of their own". More than 840 points and more than 460 comments on Hacker News on 15 Oct. See [agent security and sandboxing](/blog/agent-security-and-sandboxing/). [Source](https://www.csoonline.com/article/4234854/ai-agents-arent-going-rogue-frontier-labs-are-skipping-security-basics.html) +2. **The TV season TV4 says an agent did not delete.** Aftonbladet reported on 14 Oct, citing sources, that an AI agent let into a production server deleted the edited season of The Challenge Sverige. Its update the same afternoon says TV4's own inquiry put the loss down to human error at the producer, Mastiff, not AI. The premiere moves from 22 November to 2027. A Hacker News thread on the first report passed 140 points. [Source](https://www.aftonbladet.se/nojesbladet/a/31oE8A/the-challenge-sverige-raderad-av-ai-bara-veckor-innan-premiaren) +3. **Scion and Mononoke.** An engineer who helped build Meta's Mononoke source control server wrote that Anthropic's new Scion system follows Mononoke's "derived data" design, and that crediting only Sapling "borders on plagiarism". The post also says the author has no problem with the design being copied. More than 280 points on Hacker News on 15 Oct. [Source](https://sunshowers.io/posts/anthropic-mononoke/) +4. **How much code do agents add?** Parsimony is a benchmark that ranks models by how much code they add to solve a task, counting syntax units rather than lines. Its page says existing benchmarks "don't punish excess code". More than 75 points on Hacker News. [Source](https://parsimony.lasu.dev/) + +## How this list is built + +Each day our agents read GitHub releases and trending pages, the Hacker News front page, and the top posts on X and Reddit about coding agents. A name only makes a list with a link that was opened and a signal that can be shown: points, stars or a dated release. Repos are checked against the GitHub API before they appear, and a claim that exists only as a social post is left out. New here? Start with the [install guide](/blog/how-to-install-and-use-munder-difflin/). + + diff --git a/blog/src/posts/meta-muse-alternatives.md b/blog/src/posts/meta-muse-alternatives.md index ddfc5b1a0..dbe85d1cc 100644 --- a/blog/src/posts/meta-muse-alternatives.md +++ b/blog/src/posts/meta-muse-alternatives.md @@ -33,7 +33,7 @@ Plenty of people still can't use it, or don't want to. There are three common re - **You live outside the US.** Muse launched for US users aged 18 and over only. - **You don't want Meta holding your inbox, calendar and bank data.** Muse trains on your chats by default unless you switch it off, according to [Meta's own help page](https://www.meta.com/help/artificial-intelligence/1047255454427887/). A WIRED reporter also found it [kept asking for more personal data](https://techbriefly.com/2026/09/21/meta-muse-ai-data-collection-scrutiny/). -- **You want agents for your work, not your errands.** Muse is built for bookings, forms and shopping, not for writing code or running projects. For Meta's coding agent, see [Muse Code and Muse Wonder](/blog/muse-wonder/). +- **You want agents for your work, not your errands.** Muse is built for bookings, forms and shopping, not for writing code or running projects. For Meta's coding agent, see [Muse Code and Muse Wonder](/blog/muse-wonder/). Or read [Muse Code vs Claude Code](/blog/muse-code-vs-claude-code/). Here are seven alternatives, and which reason each one solves. diff --git a/blog/src/posts/muse-code-vs-claude-code.md b/blog/src/posts/muse-code-vs-claude-code.md new file mode 100644 index 000000000..4e25a17b6 --- /dev/null +++ b/blog/src/posts/muse-code-vs-claude-code.md @@ -0,0 +1,94 @@ +--- +title: "Muse Code vs Claude Code: Plans, Prices, Models and Where They Run" +seoTitle: "Muse Code vs Claude Code (Oct 2026): Plans, Models, Platforms" +description: "Muse Code vs Claude Code, checked 14 Oct 2026: the main plans and monthly prices, the models each runs, where each installs, and which one to pick." +date: 2026-10-14 +category: comparisons +categoryLabel: Comparisons +type: Non-technical +primaryKeyword: "muse code" +secondaryKeywords: ["muse code vs claude code", "meta muse code", "muse code pricing", "muse code plans"] +tags: ["Comparisons", "Claude Code", "CLI Agents", "AI Agents", "Cost"] +faq: + - q: "What is Muse Code?" + a: "Muse Code is Meta's coding agent. Meta's Muse Code page calls it a coding agent for your most complex coding workstreams, powered by Muse models. On 14 Oct 2026 the page gave a one line curl install and said it is available for MacOS and Windows." + - q: "How much does Muse Code cost?" + a: "Meta's Muse Code page listed four plans on 14 Oct 2026: Everyday Usage at $5.00/month, High Usage at $15.00/month, Power Usage at $50.00/month and Maximum Usage at $200.00/month. Usage grows from 10 to 50 prompts every 5 hours on Everyday Usage to 80x that on Maximum Usage." + - q: "Is Claude Code included in Claude Pro?" + a: "Yes. Anthropic's pricing page says Claude Code is included in all paid plans and shares the same usage limits as the rest of your plan. On 14 Oct 2026 Pro was $20 billed monthly, or $17 a month with the annual discount ($200 billed up front). The Free plan does not include Claude Code." + - q: "Does Muse Code run on Linux?" + a: "Meta's Muse Code page did not list Linux on 14 Oct 2026. It said Available for MacOS and Windows. Claude Code's docs give an install command for macOS, Linux and WSL, plus separate commands for Windows PowerShell and CMD." + - q: "Which models does Muse Code use?" + a: "Muse models, going by Meta's page. Every Muse Code plan lists access to Muse Spark, Muse Wonder and Muse Image, and the Power Usage plan adds the most powerful Muse models, including Muse Wonder Full. Our Muse Wonder explainer covers those models." +--- + +Pick Muse Code if price matters most: on 14 Oct 2026 Muse Code's cheapest plan was $5.00 a month, against $20 a month for Claude Pro billed monthly. Pick Claude Code if you need Linux, an IDE or the browser: Anthropic's docs list all three, and Meta's Muse Code page lists MacOS and Windows. Checked 14 Oct 2026. + +Both are coding agents made by AI labs to run their own models. For Meta's new models in detail, read our [Muse Wonder explainer](/blog/muse-wonder/). More head to heads sit in our [Comparisons hub](/blog/topics/comparisons/). + +## What is the difference between Muse Code and Claude Code? + +Muse Code runs Meta's Muse models and Claude Code runs Anthropic's Claude models. Meta's [Muse Code page](https://dev.meta.ai/products/muse-code) calls it "A coding agent for your most complex coding workstreams" and "powered by Muse models". Anthropic's [Claude Code overview](https://code.claude.com/docs/en/overview) calls Claude Code "an agentic coding tool that reads your codebase, edits files, runs commands, and integrates with your development tools." + +Meta's page highlights parallel work: "Multiple agents coordinate on every task. Workers in parallel, reviewers in the background". Claude Code's overview points to background agents for running several full sessions in parallel and watching them from one screen. + +## How much do Muse Code and Claude Code cost? + +Muse Code starts lower, at $5.00 a month on 14 Oct 2026; Claude Code needs a paid Claude plan or a Console account, though its CLI and IDE extensions also accept third party providers. The rows below are our selection of plans and what each vendor lists, in the vendor's own wording, from Meta's Muse Code page and Anthropic's [pricing page](https://claude.com/pricing), both read 14 Oct 2026. + +| Plan | Monthly price listed on 14 Oct 2026 | What it lists | +| --- | --- | --- | +| Muse Code Everyday Usage | $5.00/month | Muse Spark, Muse Wonder and Muse Image; 10 to 50 prompts every 5 hours | +| Muse Code High Usage | $15.00/month | 5x more usage than the Everyday Usage plan | +| Muse Code Power Usage | $50.00/month | Muse Wonder Full; 20x more usage than the Everyday Usage plan | +| Muse Code Maximum Usage | $200.00/month | 80x more usage than the Everyday Usage plan | +| Claude Free | $0 | No Claude Code | +| Claude Pro | $17 per month with annual subscription discount ($200 billed up front); $20 if billed monthly | Claude Code | +| Claude Max | From $100 per month | 5x or 20x more usage than Pro | + +
Animation. Four bells hang on a string, growing in size from left to right, tagged Everyday Usage, High Usage, Power Usage and Maximum Usage. Each rings in turn as a price tag reading $5.00, $15.00, $50.00 and $200.00 swings beneath it.
Muse Code's four plans, from $5.00 to $200.00 a month. Meta's Muse Code page, read 14 Oct 2026.
+ +Both sell usage in 5 hour windows. Anthropic's pricing page says every plan's limits reset on a rolling five-hour session window, paid plans add weekly limits, and Claude Code "shares the same usage limits as the rest of your plan". Meta's page gives no weekly limit for Muse Code. Our [Claude Code pricing guide](/blog/how-much-does-claude-code-cost/) covers the API route too. + +## Which models do they use? + +Muse Code uses Meta's Muse models, and Claude Code uses Claude models. Every Muse Code plan lists Muse Spark, Muse Wonder and Muse Image, and Power Usage adds "the most powerful Muse models, including Muse Wonder Full". Meta's page also claims Muse Code is "Co-trained" with those models for "Better tool use, fewer retries and higher-quality output than a generic wrapper." + +
Animation. Two umbrella stands stand side by side. Umbrellas tagged Muse Spark, Muse Wonder and Muse Image drop into the stand marked Muse Code, and umbrellas tagged Claude models drop into the stand marked Claude Code.
Muse models in Muse Code, Claude models in Claude Code. Meta's Muse Code page and Anthropic's pricing page, 14 Oct 2026.
+ +The model table on Meta's Muse Code page listed muse-spark-1.3 with a 1M context window at $1.25 input and $4.25 output per Mtok on 14 Oct 2026, and a contributor version at $0.10 and $0.20; the page says that version's data is used to improve Meta's products. On 14 Oct 2026, Anthropic's pricing page listed Opus 5.5, its "Daily driver for agentic coding and enterprise work", at $4 input and $20 output per MTok at standard pricing. + +## Where do they run? + +Claude Code runs on more surfaces, by each vendor's own list. Claude Code's overview names the terminal, IDE extensions for VS Code and JetBrains, a desktop app and the web at claude.ai/code. Meta's page offers "Muse Code in your CLI" and says "Available for MacOS and Windows." + +The terminal installs are near twins, down to the pipe into bash: + +```bash +# Muse Code, from Meta's Muse Code page +curl -fsSL https://dev.meta.ai/install.sh | bash + +# Claude Code on macOS, Linux and WSL, from Anthropic's overview +curl -fsSL https://claude.ai/install.sh | bash +``` + +
Animation. A brass telescope on a tripod swings slowly across two signs. Pointed at Muse Code it brings MacOS and Windows into focus. Pointed at Claude Code it brings terminal, IDE, desktop app and web into focus, one after another.
Where each runs, by Meta's Muse Code page and Anthropic's Claude Code overview, read 14 Oct 2026.
+ +Claude Code's overview also lists Windows PowerShell and CMD installs and a desktop app in beta on Ubuntu or Debian. The Muse Code page does not mention Linux. + +## Which should you pick? + +Our view: pick by budget and platform, since each comes from one lab and runs that lab's models. + +- **Pick Muse Code if** you want the lowest monthly price to start, you want to try Muse Wonder, and you work on a Mac or Windows machine. +- **Pick Claude Code if** you are on Linux, want it inside VS Code, JetBrains or a browser, or already pay for Claude Pro or Max, which include it. + +
Animation. A desk fan sits between two paper signs. It turns to the sign marked Muse Code, which flutters to show a tag reading $5.00 a month, then turns to the sign marked Claude Code, which flutters to show a tag reading terminal, IDE, desktop and web.
On 14 Oct 2026 Muse Code started at $5.00 a month, and Claude Code documented more places to run.
+ +We think the models matter more than the agents here, so try both on a real task from your own repo before you move a team. For a third lab's agent, see [Codex vs Claude Code](/blog/codex-cli-vs-claude-code/). + +## Can you use both? + +Yes. They are separate installs with separate sign-ins, and both vendors list MacOS and Windows. [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. Muse Code is not on its list of supported agents, so run Muse Code in its own terminal. Start with [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). + + diff --git a/blog/src/posts/opencode-2-0.md b/blog/src/posts/opencode-2-0.md new file mode 100644 index 000000000..0d5e5c765 --- /dev/null +++ b/blog/src/posts/opencode-2-0.md @@ -0,0 +1,106 @@ +--- +title: "OpenCode 2.0: what changed and how to upgrade" +description: "OpenCode 2.0 from Anomaly: the background service, Code Mode, background subagents, the new plugin API and SDK, and how to upgrade. Checked 15 Oct 2026." +date: 2026-10-15 +category: concepts +categoryLabel: Concepts +type: Technical +primaryKeyword: "opencode 2.0" +secondaryKeywords: ["opencode 2", "opencode upgrade", "opencode plugins", "opencode changelog", "opencode code mode", "opencode sdk", "opencode migrate from v1"] +tags: ["Concepts", "Open Source", "CLI Agents", "Engines"] +faq: + - q: "What is OpenCode 2.0?" + a: "OpenCode 2.0 is the new major release of Anomaly's open source coding agent. Anomaly's 2.0 post calls it a major overhaul and lists a shared background service, a rebuilt API, Code Mode for MCP tools, background subagents and shell commands, a new plugin API and an SDK." + - q: "How do I upgrade OpenCode to 2.0?" + a: "Anomaly's 2.0 post says to get onto the latest 1.x release, then run opencode upgrade --major. OpenCode's Migrate from V1 guide says 1.x and 2.0 both use the opencode command and are no longer installed side by side by default, so remove a package-managed 1.x install first. On 15 Oct 2026 GitHub's releases page and the npm registry both showed 1.19.0 as the latest version." + - q: "Do OpenCode 1.x plugins work in 2.0?" + a: "No. Anomaly's 2.0 post says 1.0 plugins will not work in 2.0, and OpenCode's Migrate from V1 guide says V1 plugin implementations do not run in V2. The guide says moving a file or renaming its config entry is not enough, and points to a separate plugin migration guide." + - q: "What is Code Mode in OpenCode?" + a: "Code Mode lets the agent write a script that calls MCP tools, so several calls happen in a single round trip. Anomaly's 2.0 post says the @opencode/codemode package turns MCP servers or OpenAPI endpoints into functions, and that all MCP servers in 2.0 are opted into codemode by default." + - q: "Does OpenCode 2.0 still read my 1.x config?" + a: "Yes, for supported fields. OpenCode's Migrate from V1 guide says V2 reads the same config locations as V1 and normalizes supported V1 fields in memory without rewriting the file. Supported global tui.json settings move to one global cli.json on the first V2 terminal start." +--- + +OpenCode 2.0 is what Anomaly calls "a major overhaul", and it has three breaking changes: plugins, the server API and the terminal config file. Anomaly's [2.0 post](https://anoma.ly/notes/opencode-2-0/) lists the new parts, and the [Migrate from V1 guide](https://opencode.ai/v2/docs/migrate-v1/) says other supported 1.x behaviour is "intended to remain compatible". Checked 15 Oct 2026. + +For the basics, read [what is OpenCode](/blog/what-is-opencode/), 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. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup. + +## What is new in OpenCode 2.0? + +A shared background service, a rebuilt API, Code Mode, background work, a new plugin API and an SDK. Anomaly's 2.0 post (12 Oct 2026, per its page metadata) calls these "some of the more impactful user-facing changes". On 15 Oct 2026 the [Hacker News thread](https://news.ycombinator.com/item?id=50094994) had more than 380 points. + +The table is our selection, built from Anomaly's 2.0 post and the Migrate from V1 guide, both read on 15 Oct 2026. + +| Area | 1.x | 2.0 | +| --- | --- | --- | +| Server | Client and server model, "mostly used by power users" | One background process shared by the TUI, web and desktop app | +| Server API | The old API, which Anomaly's post calls "bad" | Rebuilt from scratch, with the new `@opencode/client` package | +| Plugins | A plugin system that "never got the attention it needed" | A new plugin API; 1.x plugins do not run | +| TUI plugins | Experimental | Stable | +| Terminal config | Layered `tui.json(c)` files | One global `~/.config/opencode/cli.json`, auto migrated | +| MCP config | Servers under `mcp`, with `enabled` | Servers under `mcp.servers`, with `disabled` | +| Instruction files | `AGENTS.md`, with a `CLAUDE.md` fallback | `AGENTS.md` only, currently | + +## What is the OpenCode service? + +It is a single OpenCode process in the background that the TUI, the web interface and the desktop app all talk to. Anomaly's 2.0 post says the server "now automatically runs as a single background process on your machine". + +Scripts built on `@opencode/client` find the local service on their own, which Anomaly's post says is "particularly useful for scheduled tasks". Anomaly's post adds that you can call it with `opencode api` commands, or expose it to other hosts and connect with `opencode pair`. + +Anomaly's post says the API was rebuilt from scratch, and sums up the old one in three words: "It was bad." Anomaly's post also says OpenCode now calls its own API, for example to move the current session into a git worktree when you start new work. + +
Animation. A lighthouse labelled OpenCode service turns one beam slowly across the sea. The beam passes over boats labelled TUI, web and desktop app, and each boat's lamp lights up as the beam reaches it, while the lighthouse itself stays put.
One background process serves the TUI, the web interface and the desktop app, per Anomaly's 2.0 post.
+ +## What is Code Mode? + +Code Mode lets the model write a script that calls your MCP tools, instead of calling them one by one. Anomaly's 2.0 post says the `@opencode/codemode` package holds "a minimal JavaScript interpreter" that turns MCP servers or OpenAPI endpoints into functions the agent can call in a script. A script can call a tool, map over the results and call another tool, which Anomaly's post calls a "Single round trip instead of calling them one at a time". + +Anomaly's post says all MCP servers in 2.0 are "opted into codemode by default", so "you can add a thousand of them with minimal context bloat". Our [MCP server guide](/blog/how-to-add-an-mcp-server-to-claude-code/) covers the Claude Code side. + +
Animation. A small ball rolls into the first domino, labelled script. A long curving run of dominoes, each labelled tool, tips over one after another in a single unbroken fall until the last one lands.
Code Mode runs several tool calls from one script, a single round trip, per Anomaly's 2.0 post.
+ +## What are background subagents and shell commands? + +They let OpenCode run work in the background while you keep prompting. Anomaly's 2.0 post says the primary agent can now hand work to a background subagent and continue. Anomaly's post says this "happens automatically", and that you can also trigger it by hand, "even with a different model". + +Background shell commands do the same for long jobs. Anomaly's post says the shell tool "can be invoked asynchronously" and the agent is notified when the work is complete. Anomaly's post also says a hotkey moves a foreground command or subagent to the background when it takes longer than you expect. The Migrate from V1 guide adds that custom commands set to run as a subagent "now run automatically in the background and report their results to the parent session". + +
Animation. A ferris wheel keeps turning at a steady pace. At the bottom, new cars labelled subagent and shell clip on as they pass, and the wheel never stops to load them.
Background subagents and shell commands run while you keep prompting, per Anomaly's 2.0 post.
+ +## What changed for plugins and the SDK? + +Plugins have a new API, and OpenCode can now run as a library. Anomaly's 2.0 post says the 1.0 plugin system "never got the attention it needed", so Anomaly built nearly every 2.0 feature as an internal plugin, and "there are now 80+ of them". Anomaly's post says TUI plugins moved from experimental to stable, and that everything in 2.0 can be hot reloaded, so OpenCode can write a plugin for itself mid session and use it right away. + +OpenCode's [plugins docs](https://opencode.ai/v2/docs/build/plugins) define a plugin with `Plugin.define` from `@opencode/plugin`, and say plugins under `.opencode/plugins/` "are loaded automatically". + +
Animation. Building bricks, each labelled plugin, drop in one at a time and snap onto each other until they form a tower labelled OpenCode 2.0. A card beside the tower fills up, then reads there are now 80+ of them.
Anomaly's 2.0 post says nearly every 2.0 feature is an internal plugin, and there are now 80+ of them.
+ +Anomaly's post calls the new `@opencode/sdk` package "the exact same code that powers our CLI", for running your own OpenCode on a server or in Durable Objects. OpenCode's [SDK docs](https://opencode.ai/v2/docs/build/sdk) say it "opens no HTTP listener". The SDK section of Anomaly's post ends: "This one is still in development as things are shifting but you can already use it." + +## How do you upgrade to OpenCode 2.0? + +Get onto the latest 1.x release, then run this, per Anomaly's 2.0 post: + +``` +opencode upgrade --major +``` + +For the desktop app, Anomaly's post says to download the latest from [opencode.ai/download](https://opencode.ai/download). + +On 15 Oct 2026 the [GitHub releases page](https://github.com/anomalyco/opencode/releases/latest) showed v1.19.0, published 11 Oct 2026, as the latest release. The [npm registry](https://registry.npmjs.org/opencode-ai) gave 1.19.0 as the latest tag for `opencode-ai`, and listed no 2.x version. + +The Migrate from V1 guide lists three breaking changes: + +1. Plugins use a new plugin API. V1 plugin implementations do not run in V2, so port them. +2. The server API and clients have new contracts. Move integrations to `@opencode/client`. +3. Terminal settings move from `tui.json(c)` files to one global `~/.config/opencode/cli.json`, with supported settings migrated on the first V2 terminal start. + +The Migrate from V1 guide also says both versions use the `opencode` command and "are no longer installed side by side by default", so remove a package-managed 1.x install first. The guide says V2 reads your existing config "without rewriting the source file", and converting it to the V2 shape is optional. + +Two more catches in the Migrate from V1 guide: V2 "does not run language servers", and it "currently only discovers `AGENTS.md`", so move any `CLAUDE.md` guidance there. The guide says to keep your 1.x setup until V2 does what you need. + +## OpenCode in Munder Difflin + +Munder Difflin runs OpenCode as one of its engines, starting the `opencode` CLI you have installed. For comparisons, see [OpenCode vs Claude Code](/blog/opencode-vs-claude-code/) and [Claude Code alternatives](/blog/claude-code-alternatives/). + + diff --git a/blog/src/posts/opencode-vs-claude-code.md b/blog/src/posts/opencode-vs-claude-code.md index 9dd73f800..83ff3fea1 100644 --- a/blog/src/posts/opencode-vs-claude-code.md +++ b/blog/src/posts/opencode-vs-claude-code.md @@ -24,7 +24,7 @@ faq: Pick OpenCode if you want an open source agent that runs almost any model, and Claude Code if you want Anthropic's own agent on a Claude plan you already pay for. Checked 6 Oct 2026. -Both read your repo, edit files and run commands. The differences are licence, price, models and login. New to one of them? Read [what is OpenCode](/blog/what-is-opencode/) first. More head to heads sit in our [Comparisons hub](/blog/topics/comparisons/). +Both read your repo, edit files and run commands. The differences are licence, price, models and login. New to one of them? Read [what is OpenCode](/blog/what-is-opencode/) first. See [what changed in OpenCode 2.0](/blog/opencode-2-0/) before upgrading. More head to heads sit in our [Comparisons hub](/blog/topics/comparisons/). ## OpenCode vs Claude Code at a glance, checked 6 Oct 2026 diff --git a/docs/blog/agent-tools-today-2026-10-03/index.html b/docs/blog/agent-tools-today-2026-10-03/index.html index c5fd1aba0..8101d471a 100644 --- a/docs/blog/agent-tools-today-2026-10-03/index.html +++ b/docs/blog/agent-tools-today-2026-10-03/index.html @@ -249,50 +249,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-04/index.html b/docs/blog/agent-tools-today-2026-10-04/index.html index 7b61ba9cb..8fb4878fb 100644 --- a/docs/blog/agent-tools-today-2026-10-04/index.html +++ b/docs/blog/agent-tools-today-2026-10-04/index.html @@ -248,50 +248,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-05/index.html b/docs/blog/agent-tools-today-2026-10-05/index.html index 6d72f50b7..b056b0d5e 100644 --- a/docs/blog/agent-tools-today-2026-10-05/index.html +++ b/docs/blog/agent-tools-today-2026-10-05/index.html @@ -246,50 +246,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-06/index.html b/docs/blog/agent-tools-today-2026-10-06/index.html index a4b26d7df..d5668a124 100644 --- a/docs/blog/agent-tools-today-2026-10-06/index.html +++ b/docs/blog/agent-tools-today-2026-10-06/index.html @@ -247,50 +247,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-07/index.html b/docs/blog/agent-tools-today-2026-10-07/index.html index 02d86760b..217f81cc3 100644 --- a/docs/blog/agent-tools-today-2026-10-07/index.html +++ b/docs/blog/agent-tools-today-2026-10-07/index.html @@ -247,50 +247,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-08/index.html b/docs/blog/agent-tools-today-2026-10-08/index.html index d3a145901..21f7a81a8 100644 --- a/docs/blog/agent-tools-today-2026-10-08/index.html +++ b/docs/blog/agent-tools-today-2026-10-08/index.html @@ -246,50 +246,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-09/index.html b/docs/blog/agent-tools-today-2026-10-09/index.html index 68dd122f6..5ceba798a 100644 --- a/docs/blog/agent-tools-today-2026-10-09/index.html +++ b/docs/blog/agent-tools-today-2026-10-09/index.html @@ -247,50 +247,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-10/index.html b/docs/blog/agent-tools-today-2026-10-10/index.html index b8efe9ca6..c2bf9ba23 100644 --- a/docs/blog/agent-tools-today-2026-10-10/index.html +++ b/docs/blog/agent-tools-today-2026-10-10/index.html @@ -247,50 +247,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-11/index.html b/docs/blog/agent-tools-today-2026-10-11/index.html index ada86e6ec..aafb39e47 100644 --- a/docs/blog/agent-tools-today-2026-10-11/index.html +++ b/docs/blog/agent-tools-today-2026-10-11/index.html @@ -245,50 +245,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 10 Oct 2026: MXC SDK 1.0, Claude Code 2.1.296, Microsoft-Decision-1

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 10 Oct 2026, each with its source.

+

Agent Tools Today, 12 Oct 2026: WordCraft 0.5.0, Genkit Dart 1.0, 500 Billion Tokens

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 12 Oct 2026, each with its source.

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-12/index.html b/docs/blog/agent-tools-today-2026-10-12/index.html index df559b9a0..de5878410 100644 --- a/docs/blog/agent-tools-today-2026-10-12/index.html +++ b/docs/blog/agent-tools-today-2026-10-12/index.html @@ -243,50 +243,50 @@

How this list is built
- +
- + 5 min
-

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

+

Agent Tools Today, 15 Oct 2026: OpenCode 2.0, Hermes Agent v0.22.0, a Lost TV Season

+

The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 15 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

+

Agent Tools Today, 13 Oct 2026: Muse Wonder, GLM-Legal, the Codex Daemon Issue

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 13 Oct 2026, each with its source.

News - Read → + Read →
- +
- + 5 min
-

Agent Tools Today, 10 Oct 2026: MXC SDK 1.0, Claude Code 2.1.296, Microsoft-Decision-1

-

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 10 Oct 2026, each with its source.

+

Agent Tools Today, 11 Oct 2026: Dynamic Workflows, TypeSafe AI's Raise, OpenCode 1.19.0

+

The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 11 Oct 2026, each with its source.

News - 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 1b230bb94..c96c679a3 100644 --- a/docs/blog/agent-tools-today-2026-10-13/index.html +++ b/docs/blog/agent-tools-today-2026-10-13/index.html @@ -237,56 +237,56 @@

How this list is built