diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index c5cf84101..ee33e11e0 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -4333,6 +4333,18 @@ "inline": {}, "youtube": [] }, + "agent-tools-today-2026-10-06": { + "title": "Agent Tools Today, 6 Oct 2026: OpenAI Text Watermarks, Reflection Beam, Cloudflare Web Search", + "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": [] + }, "what-is-hermes-agent": { "title": "Hermes Agent: what it is, how to install it and what it costs", "category": "concepts", @@ -4435,8 +4447,8 @@ "category": "comparisons", "hero": { "file": "assets/media/openclaw-alternatives/hero.png", - "alt": "Title card. The words OpenClaw alternatives: 7 picks, with Michael and Jim from the office cast standing beside them", - "prompt": "Title card: the kit's title scene (assets/mg/kit.js).", + "alt": "Title card on white. The words OpenClaw alternatives, 7 picks for chat, code and cloud, above six coloured name pills, with a Claude pill dropping from a hopper onto a yellow flap", + "prompt": "Title card: the hero scene in assets/media/openclaw-alternatives/motion.js, shot at 1600 by 900.", "status": "ready" }, "inline": {} @@ -4446,8 +4458,19 @@ "category": "comparisons", "hero": { "file": "assets/media/claude-code-alternatives/hero.png", - "alt": "Title card. The words Claude Code alternatives: 7 coding agents, with Michael, Oscar and Kevin from the office cast standing beside them", - "prompt": "Title card: the kit's title scene (assets/mg/kit.js).", + "alt": "Title card on white. The words Claude Code alternatives, 7 coding agents to switch to, above a tray of seven coloured pegs: a circle, square, triangle, hexagon, star and cross sit in place and a diamond hovers over its empty slot", + "prompt": "Title card: the hero scene in assets/media/claude-code-alternatives/motion.js, shot at 1600 by 900.", + "status": "ready" + }, + "inline": {} + }, + "openai-text-watermarking": { + "title": "OpenAI text watermarking: textGrain, who gets it, limits", + "category": "concepts", + "hero": { + "file": "assets/media/openai-text-watermarking/hero.png", + "alt": "Title card on white. The words OpenAI text watermarking above two rows of grey word shapes, where a frame hanging from a large key has turned some of the words yellow in a pattern", + "prompt": "Still of the hero scene in assets/media/openai-text-watermarking/motion.js.", "status": "ready" }, "inline": {} diff --git a/blog/src/assets/media/agent-tools-today-2026-10-06/motion.css b/blog/src/assets/media/agent-tools-today-2026-10-06/motion.css new file mode 100644 index 000000000..d22cb70cf --- /dev/null +++ b/blog/src/assets/media/agent-tools-today-2026-10-06/motion.css @@ -0,0 +1,11 @@ +/* Bright coded scene for "Agent Tools Today, 6 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 .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +@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-06/motion.js b/blog/src/assets/media/agent-tools-today-2026-10-06/motion.js new file mode 100644 index 000000000..14af3f1c9 --- /dev/null +++ b/blog/src/assets/media/agent-tools-today-2026-10-06/motion.js @@ -0,0 +1,135 @@ +/* Bright coded scene for "Agent Tools Today, 6 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'; + 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 bo = (x) => { const n = 7.5625, d = 2.75; if (x < 1 / d) return n * x * x; if (x < 2 / d) return n * (x -= 1.5 / d) * x + 0.75; if (x < 2.5 / d) return n * (x -= 2.25 / d) * x + 0.9375; return n * (x -= 2.625 / d) * x + 0.984375; }; + 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 SC = {}; + + /* ---- the day at a glance: five parcels drop in unmarked, get stamped, and stack up with a name tag ---- */ + const LAUNCH = [ + ["OpenAI text watermarking", "API customers worldwide can opt in to text watermarking", YEL], + ["Beam", "501 billion total parameters, 23 billion active", BLUE], + ["Cloudflare Web Search API", "Agents search the web through AI Gateway", LIL], + ["Claude Code v2.1.290", "It adds claude attach and claude logs ", SKY], + ["Codex CLI 0.160.1", "A patch on 5 Oct with one fix", MINT], + ]; + SC.today = { + dur: 12, still: 9.5, + build(svg, api) { + const BY = 400, CY = BY - 27, X0 = 90, XS = 185, X1 = 280, TX = 384, GAP = 1.5; + txt(svg, 44, 62, "Top 5 launches", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 60, "tap a parcel", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + // ground, pallet, belt + el("path", { d: "M36 474 H454", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("rect", { x: 336, y: 460, width: 96, height: 13, rx: 6, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M72 428 V472 M258 428 V472", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("rect", { x: 36, y: BY, width: 262, height: 28, rx: 14, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const dash = el("path", { d: "M54 414 H282", stroke: INK, "stroke-width": 3, "stroke-linecap": "round", "stroke-dasharray": "8 16" }, svg); + // stamper: two posts, a bar, a piston + el("path", { d: `M${XS - 50} 246 V${BY} M${XS + 50} 246 V${BY}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const rod = el("path", { d: "", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("rect", { x: XS - 62, y: 220, width: 124, height: 28, rx: 14, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + const head = el("rect", { x: -32, y: -22, width: 64, height: 22, rx: 8, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + const tip = strip(svg, 520, 720); + let pinned = -1; + const jx = [0, 6, -5, 4, -3]; + const ps = LAUNCH.map(([name, line, col], i) => { + const g = el("g", { opacity: 0 }, svg), tag = el("g", {}, g), box = el("g", {}, g); + el("path", { d: "M30 0 H56", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, tag); + const shape = el("path", { fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, tag); + el("circle", { cx: 70, cy: 0, r: 5.5, fill: col, stroke: INK, "stroke-width": 2.5 }, tag); + const label = txt(tag, 86, 7.5, name, { "font-size": 21 }); + const w = (label.getComputedTextLength && label.getComputedTextLength()) || name.length * 12, R = 86 + w + 18; + shape.setAttribute("d", `M68 -22 H${R - 12} Q${R} -22 ${R} -10 V10 Q${R} 22 ${R - 12} 22 H68 L52 7 V-7 Z`); + el("rect", { x: -36, y: -33, width: 72, height: 66, rx: 14, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, box); + const face = el("rect", { x: -30, y: -27, width: 60, height: 54, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, box); + const mark = txt(box, 0, 9, "?", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + button(g, `${name}: ${line}`, () => { pinned = i; api.poke(); }); + return { g, tag, box, face, mark, col, T: 0.4 + i * GAP, sx: TX + jx[i], sy: 460 - 29 - i * 56 }; + }); + // the chute the parcels fall out of, drawn last so they appear from behind it + const chute = el("g", {}, svg); + el("rect", { x: -44, y: -40, width: 88, height: 80, rx: 18, fill: SOFT, stroke: INK, "stroke-width": 3 }, chute); + txt(chute, 0, 13, "?", { "text-anchor": "middle", "font-size": 38, "font-weight": 700 }); + return (t, now) => { + dash.setAttribute("stroke-dashoffset", -now * 70); + let press = 0, last = -1, shake = 0; + const gone = 1 - seg(t, 11.3, 11.9); + ps.forEach((p, i) => { + const s = t - p.T; + if (s < 0) { p.g.setAttribute("opacity", 0); return; } + shake = Math.max(shake, Math.sin(seg(s, 0, 0.35) * Math.PI)); + const dn = oc(seg(s, 0.85, 1)) * (1 - io(seg(s, 1, 1.2))); press = Math.max(press, dn); + const hop = seg(s, 1.45, 2), h = io(hop), done = s >= 0.98, landed = s >= 2; + const bx = X0 + (XS - X0) * io(seg(s, 0.5, 0.85)) + (X1 - XS) * io(seg(s, 1.15, 1.45)); + const x = bx + (p.sx - bx) * h, y = 162 + (CY - 162) * bo(seg(s, 0, 0.5)) + (p.sy - CY) * h - Math.sin(hop * Math.PI) * (70 + i * 12); + tf(p.g, x, y); p.g.setAttribute("opacity", gone); + const pop = done ? ob(seg(s, 0.98, 1.3)) : 1, land = landed ? Math.sin(seg(s, 2, 2.35) * Math.PI) * 0.12 : 0; + p.box.setAttribute("transform", `translate(0 ${27 * (dn * 0.14 + land)}) rotate(${360 * h}) scale(${(0.8 + 0.2 * pop) * (1 + land)} ${(0.8 + 0.2 * pop) * (1 - dn * 0.14 - land)})`); + p.face.setAttribute("fill", done ? p.col : "#fff"); p.mark.textContent = done ? String(i + 1) : "?"; + const k = ob(seg(s, 2, 2.45)); + p.tag.setAttribute("transform", `translate(30 0) rotate(${-24 * (1 - k) + (landed ? Math.sin(now * 2 + i) * 1.2 : 0)}) scale(${cl(k, 0, 1.2)}) translate(-30 0)`); + p.tag.setAttribute("opacity", landed ? 1 : 0); + if (landed) last = i; + }); + const hy = 300 + (CY - 27 - 300) * press; + tf(head, XS, hy); rod.setAttribute("d", `M${XS} 248 V${hy - 22}`); + tf(chute, X0, 160, 1 + 0.06 * shake, Math.sin(shake * Math.PI * 2) * 3); + const show = pinned >= 0 ? pinned : last; + tip.textContent = show < 0 || (pinned < 0 && gone < 1) ? "the question was where things come from" : LAUNCH[show][1]; + }; + }, + }; + + /* ---- 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('600 21px "Space Grotesk"')]).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-06/today.png b/blog/src/assets/media/agent-tools-today-2026-10-06/today.png new file mode 100644 index 000000000..06ed1a1ef Binary files /dev/null and b/blog/src/assets/media/agent-tools-today-2026-10-06/today.png differ diff --git a/blog/src/assets/media/claude-code-alternatives/coins.png b/blog/src/assets/media/claude-code-alternatives/coins.png new file mode 100644 index 000000000..46892c53c Binary files /dev/null and b/blog/src/assets/media/claude-code-alternatives/coins.png differ diff --git a/blog/src/assets/media/claude-code-alternatives/hero.png b/blog/src/assets/media/claude-code-alternatives/hero.png index b83d6689e..85970b8c9 100644 Binary files a/blog/src/assets/media/claude-code-alternatives/hero.png and b/blog/src/assets/media/claude-code-alternatives/hero.png differ diff --git a/blog/src/assets/media/claude-code-alternatives/motion.css b/blog/src/assets/media/claude-code-alternatives/motion.css new file mode 100644 index 000000000..e968362ec --- /dev/null +++ b/blog/src/assets/media/claude-code-alternatives/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Claude Code alternatives". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/claude-code-alternatives/motion.js b/blog/src/assets/media/claude-code-alternatives/motion.js new file mode 100644 index 000000000..d704c91bb --- /dev/null +++ b/blog/src/assets/media/claude-code-alternatives/motion.js @@ -0,0 +1,295 @@ +/* Bright coded scenes for "Claude Code alternatives". 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 }); + }; + + /* ---- seven peg shapes, drawn around 0,0 ---- */ + const poly = (pts) => "M" + pts.map((p) => p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" L") + " Z"; + const ring = (n, r, r2, rot) => Array.from({ length: n }, (_, i) => { const a = rot + (i / n) * 6.2832, q = r2 && i % 2 ? r2 : r; return [Math.cos(a) * q, Math.sin(a) * q]; }); + const SHAPES = [ + (r) => `M${-r} 0 a${r} ${r} 0 1 0 ${2 * r} 0 a${r} ${r} 0 1 0 ${-2 * r} 0 Z`, + (r) => poly(ring(4, r * 1.2, 0, 0.7854)), + (r) => poly([[0, -r], [r, r * 0.8], [-r, r * 0.8]]), + (r) => poly(ring(4, r * 1.08, 0, 0)), + (r) => poly(ring(6, r * 1.05, 0, 0)), + (r) => poly(ring(10, r * 1.15, r * 0.6, -1.5708)), + (r) => { const a = r * 0.38; return poly([[-a, -r], [a, -r], [a, -a], [r, -a], [r, a], [a, a], [a, r], [-a, r], [-a, a], [-r, a], [-r, -a], [-a, -a]]); }, + ]; + const PEGCOL = [YEL, BLUE, LIL, SKY, MINT, YEL, LIL]; + const lock = (g, open, fill) => { + el("path", { d: open ? "M-14 -2 V-24 a14 14 0 0 1 28 0 V-16" : "M-14 -2 V-16 a14 14 0 0 1 28 0 V-2", stroke: INK, "stroke-width": 5, fill: "none", "stroke-linecap": "round" }, g); + el("rect", { x: -25, y: -4, width: 50, height: 40, rx: 9, fill, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: "M0 12 V21", stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, g); + }; + + const SC = {}; + + /* ---- 1. four reasons drop on a balance and tip it away from "stay" ---- */ + SC.tipping = { + dur: 11.4, still: 9.2, + build(svg, api) { + txt(svg, 44, 62, "Four reasons people look elsewhere", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 100, "tap a weight", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const PX = 400, PY = 190, ARM = 232, DROP = 150; + el("path", { d: `M${PX} ${PY} V448`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, svg); + el("rect", { x: PX - 90, y: 440, width: 180, height: 20, rx: 10, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const beam = el("path", { stroke: INK, "stroke-width": 8, "stroke-linecap": "round", fill: "none" }, svg); + el("circle", { cx: PX, cy: PY, r: 13, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + const pan = () => { + const g = el("g", {}, svg); + el("path", { d: `M0 0 L-100 ${DROP} M0 0 L100 ${DROP}`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round", fill: "none" }, g); + el("rect", { x: -108, y: DROP, width: 216, height: 13, rx: 6.5, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + el("circle", { r: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + return g; + }; + const left = pan(), right = pan(); + el("rect", { x: -80, y: DROP - 92, width: 160, height: 92, rx: 18, fill: MINT, stroke: INK, "stroke-width": 3 }, left); + txt(left, 0, DROP - 35, "stay", { "text-anchor": "middle", "font-size": 32, "font-weight": 700 }); + const LINES = ["Price: Claude Pro, $20 if billed monthly", "Licence: its licence file says “All rights reserved”", "Model choice: it is built around Claude models", "Or wanting an editor"]; + const ART = [ + (g) => txt(g, 0, 10, "$20", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }), + (g) => { const k = el("g", { transform: "translate(0 -3) scale(0.72)" }, g); lock(k, false, "#fff"); }, + (g) => [-24, 0, 24].forEach((x, i) => el("circle", { cx: x, cy: 0, r: 9, fill: i === 0 ? INK : "none", stroke: INK, "stroke-width": 2.5, "stroke-dasharray": i === 0 ? "" : "3 4" }, g)), + (g) => { el("rect", { x: -28, y: -19, width: 56, height: 38, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); el("path", { d: "M-28 -8 H28 M-18 2 H6 M-18 10 H16", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, g); }, + ]; + const COL = [YEL, LIL, BLUE, SKY], SLOT = [[-48, -33], [48, -33], [-48, -101], [48, -101]], NAME = ["Price", "Licence", "Model choice", "An editor"]; + let pinned = -1; + const tiles = SLOT.map(([sx, sy], i) => { + const g = el("g", {}, right); + el("rect", { x: -50, y: -37, width: 100, height: 74, rx: 20, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + el("rect", { x: -45, y: -32, width: 90, height: 64, rx: 16, fill: COL[i], stroke: INK, "stroke-width": 3 }, g); + ART[i](g); + button(g, `${NAME[i]}. ${LINES[i]}`, () => { pinned = i; api.poke(); }); + return { g, sx, sy: DROP + sy, land: 1.5 + i * 1.75 }; + }); + const cap = strip(svg, 500, 720); + const spring = (x) => (x <= 0 ? 0 : 1 - Math.exp(-4.2 * x) * Math.cos(8.5 * x)); + return (t) => { + const back = io(seg(t, 10.4, 11.2)); + let a = -9, last = -1; + tiles.forEach((w, i) => { a += 4.5 * spring(t - w.land); if (t >= w.land) last = i; }); + a -= 18 * back; + const r = (a * Math.PI) / 180, dx = Math.cos(r) * ARM, dy = Math.sin(r) * ARM; + beam.setAttribute("d", `M${PX - dx} ${PY - dy} L${PX + dx} ${PY + dy}`); + tf(left, PX - dx, PY - dy); tf(right, PX + dx, PY + dy); + tiles.forEach((w, i) => { + const q = seg(t, w.land - 0.42, w.land), fall = -(w.sy + PY - 96) * (1 - q * q) * 0.75; + const sq = Math.sin(seg(t, w.land, w.land + 0.3) * Math.PI) * 0.1; + w.g.setAttribute("transform", `translate(${w.sx} ${w.sy + fall + 32 * sq}) scale(${1 + sq} ${1 - sq})`); + w.g.setAttribute("opacity", seg(t, w.land - 0.42, w.land - 0.3) * (1 - seg(t, 10.2, 10.6))); + }); + cap.textContent = pinned >= 0 ? LINES[pinned] : t > 8.4 && t < 10.4 ? "If none of that bothers you, stay: it is a good tool." : last < 0 || t >= 10.4 ? "Four reasons: price, licence, model choice, or an editor" : LINES[last]; + }; + }, + }; + + /* ---- 2. two cards slide on top of each other; only the locks refuse to match ---- */ + SC.twins = { + dur: 11, still: 7, + build(svg) { + txt(svg, 44, 62, "Lay one over the other", { "font-size": 32, "font-weight": 700 }); + const CW = 340, CH = 292, CY = 138; + const card = (name, right) => { + const g = el("g", {}, svg); + el("rect", { x: 10, y: 10, width: CW, height: CH, rx: 26, fill: YEL }, g); + el("rect", { x: 0, y: 0, width: CW, height: CH, rx: 26, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + txt(g, right ? CW - 6 : 6, -14, name, { "text-anchor": right ? "end" : "start", "font-size": 24, "font-weight": 700 }); + el("rect", { x: 26, y: 26, width: 60, height: 46, rx: 10, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: "M40 40 L50 49 L40 58 M56 59 H72", stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); + txt(g, 106, 58, "Terminal", { "font-size": 24 }); + el("path", { d: `M24 92 H${CW - 24} M24 172 H${CW - 24}`, stroke: SOFT, "stroke-width": 3, "stroke-linecap": "round" }, g); + el("path", { d: "M28 132 L46 110 H86 V154 H46 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("circle", { cx: 50, cy: 132, r: 5, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + txt(g, 106, 141, "$20 a month", { "font-size": 24 }); + txt(g, 26, 204, "Licence", { "font-size": 20, "font-weight": 500, fill: MUTE }); + el("circle", { cx: CW / 2, cy: 228, r: 44, fill: SOFT, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", "stroke-linecap": "round" }, g); + return g; + }; + const A = card("Claude Code", false), B = card("Codex CLI", true); + const ticks = [48, 132].map((y) => { const g = el("g", {}, svg); el("circle", { r: 17, fill: MINT, stroke: INK, "stroke-width": 3 }, g); el("path", { d: "M-8 0 L-2 6 L8 -6", stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); return { g, y: CY + y }; }); + const mk = (open, fill, label) => { const g = el("g", {}, svg), ic = el("g", {}, g); lock(ic, open, fill); const l = txt(g, 0, 74, label, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); return { g, ic, l }; }; + const LA = mk(false, LIL, "Proprietary"), LB = mk(true, MINT, "Apache 2.0"); + const cap = strip(svg, 500, 720); + return (t) => { + const m = io(seg(t, 2.3, 3.5)) - io(seg(t, 9.6, 10.6)), s = ob(seg(t, 5.2, 5.9)) - io(seg(t, 8.9, 9.5)); + const ax = 40 + 190 * m, bx = 420 - 190 * m, pop = ob(seg(t, 0.1, 0.6)); + A.setAttribute("transform", `translate(${ax} ${CY})`); B.setAttribute("transform", `translate(${bx} ${CY})`); + const bump = seg(m, 0.72, 1), jig = bump >= 1 && s < 0.05 ? Math.sin(t * 34) * 4 : 0; + const lx = ax + CW / 2 - 31 * bump - jig, rx = bx + CW / 2 + 31 * bump + jig, ly = CY + 222; + tf(LA.g, lx + (112 - lx) * s, ly - 70 * s, (0.85 + 0.15 * pop) * (1 + 0.25 * s), -jig * 1.5); + tf(LB.g, rx + (688 - rx) * s, ly - 70 * s, (0.85 + 0.15 * pop) * (1 + 0.25 * s), jig * 1.5); + const lo = cl(s * 3); + LA.l.setAttribute("opacity", lo); LB.l.setAttribute("opacity", lo); + ticks.forEach((k, i) => { const p = ob(seg(t, 3.7 + i * 0.6, 4.1 + i * 0.6)) * (1 - seg(t, 9.3, 9.6)); tf(k.g, 230 + CW - 44, k.y, cl(p, 0, 1.3)); k.g.setAttribute("opacity", p <= 0.01 ? 0 : 1); }); + cap.textContent = t < 3.5 || t > 9.6 ? "Both are terminal agents made by a model lab" : t < 5.2 ? "The first plan that lists each CLI costs $20 a month" : "The licence is the real difference"; + }; + }, + }; + + /* ---- 3. coin stacks for each starting price, and a plank that sits level on the three $20 ones ---- */ + SC.coins = { + dur: 11.6, still: 9, + build(svg, api) { + txt(svg, 44, 62, "What it costs a month to start", { "font-size": 32, "font-weight": 700 }); + txt(svg, 44, 98, "tap a stack", { "font-size": 20, fill: MUTE, "font-weight": 500 }); + const G = 430, CWD = 88, CHT = 26, X = (i) => 64 + i * 131; + const IT = [ + ["OpenCode", "free models", 0, "Free", "OpenCode includes free models"], + ["OpenCode", "Go", 1, "$10", "OpenCode Go was $10 a month"], + ["Claude", "Pro", 2, "$20", "Claude Pro: $20 if billed monthly"], + ["ChatGPT", "Plus", 2, "$20", "ChatGPT Plus was $20 a month"], + ["Cursor", "Individual", 2, "$20", "Cursor Individual was $20 a month"], + ["Claude", "Max", 10, "from $100", "Claude Max: from $100 a month"], + ]; + el("path", { d: `M16 ${G + 2} H784`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + let pinned = -1, tcur = 0.7; + const stacks = IT.map(([n1, n2, n, price, line], i) => { + const x = X(i), g = el("g", {}, svg); + el("rect", { x: x - 62, y: G - Math.max(n, 1) * CHT - 44, width: 124, height: Math.max(n, 1) * CHT + 110, rx: 16, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + if (!n) el("rect", { x: x - CWD / 2, y: G - CHT, width: CWD, height: CHT, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 2.5, "stroke-dasharray": "4 6", "stroke-linecap": "round" }, g); + const start = tcur; + const cs = Array.from({ length: n }, (_, j) => { const c = el("rect", { x: -CWD / 2, y: -CHT, width: CWD, height: CHT, rx: 12, fill: YEL, stroke: INK, "stroke-width": 3 }, g); return { c, y: G - j * CHT, d: start + j * (n > 4 ? 0.13 : 0.2) }; }); + const done = n ? cs[n - 1].d + 0.4 : start + 0.3; tcur = done + 0.25; + const lab = txt(g, x, G - Math.max(n, 1) * CHT - 14, price, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(g, x, G + 34, n1, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(g, x, G + 58, n2, { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + button(g, line, () => { pinned = i; api.poke(); }); + return { x, cs, lab, done, n, ly: G - Math.max(n, 1) * CHT - 14 }; + }); + const PT = tcur + 0.3, plank = el("g", { "pointer-events": "none" }, svg); + el("rect", { x: -182, y: -30, width: 364, height: 30, rx: 10, fill: MINT, stroke: INK, "stroke-width": 3 }, plank); + const three = txt(svg, X(3), G - 2 * CHT - 44, "three cost exactly $20", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + const cap = strip(svg, 512, 720); + return (t) => { + const out = seg(t, 10.8, 11.4); + stacks.forEach((s) => { + s.cs.forEach((c) => { const q = seg(t, c.d, c.d + 0.32), b = Math.sin(seg(t, c.d + 0.32, c.d + 0.6) * Math.PI) * -5; tf(c.c, s.x, c.y - 250 * (1 - q * q) + b); c.c.setAttribute("opacity", seg(t, c.d, c.d + 0.08) * (1 - out)); }); + const p = ob(seg(t, s.done - 0.1, s.done + 0.3)); + const hide = s.n === 2 ? 1 - seg(t, PT - 0.1, PT + 0.15) : 1; + s.lab.setAttribute("transform", `translate(${s.x} ${s.ly}) scale(${cl(p, 0, 1.3)}) translate(${-s.x} ${-s.ly})`); + s.lab.setAttribute("opacity", (p <= 0.01 ? 0 : 1) * hide * (1 - out)); + }); + const q = seg(t, PT, PT + 0.45), land = seg(t, PT + 0.45, PT + 1.1), wob = Math.exp(-5 * land) * Math.sin(land * 16) * (q >= 1 ? 1 : 0); + tf(plank, X(3), G - 2 * CHT - 2 - 300 * (1 - q * q), 1, wob * 4 + (1 - q) * -12); + plank.setAttribute("opacity", seg(t, PT, PT + 0.1) * (1 - out)); + const tp = ob(seg(t, PT + 0.6, PT + 1)); + three.setAttribute("opacity", (tp <= 0.01 ? 0 : 1) * (1 - out)); + three.setAttribute("transform", `translate(0 ${10 * (1 - cl(tp, 0, 1.2))})`); + let last = -1; stacks.forEach((s, i) => { if (t >= s.done - 0.1) last = i; }); + cap.textContent = pinned >= 0 ? IT[pinned][4] : t > PT + 0.5 && t < 10.8 ? "Three steps cost exactly $20, Claude Pro included" : last < 0 || t >= 10.8 ? "Prices checked 5 Oct 2026" : IT[last][4]; + }; + }, + }; + + /* ---- 4. a shape sorter: each reason is a peg that only fits one tool ---- */ + SC.pegs = { + dur: 11.6, still: 9.9, + build(svg, api) { + txt(svg, 44, 62, "Pick by the reason you are leaving", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a tile", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const PICK = [ + ["Codex CLI", "You want the same workflow, different lab", "Codex CLI"], + ["Munder Difflin", "You want to keep Claude Code and add others", "Munder Difflin"], + ["OpenCode", "You want any model, local ones included", "OpenCode"], + ["Cursor", "You want an editor", "Cursor, or Cline if you are staying in VS Code"], + ["Gemini CLI", "You already pay for Gemini API access", "Gemini CLI"], + ["Pi", "You want to build your own harness", "Pi"], + ["Aider", "You want every change in git", "Aider"], + ]; + const POS = [[110, 142], [303, 142], [497, 142], [690, 142], [207, 290], [400, 290], [593, 290]], TW = 156, TH = 92, R = 28, TRAY = [400, 468]; + let pinned = -1; + const tiles = PICK.map(([name, why, pick], i) => { + const [x, y] = POS[i], g = el("g", {}, svg), box = el("g", {}, g); + el("rect", { x: -TW / 2 - 6, y: -TH / 2 - 6, width: TW + 12, height: TH + 46, rx: 24, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + el("rect", { x: -TW / 2, y: -TH / 2, width: TW, height: TH, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 3 }, box); + el("path", { d: SHAPES[i](R), fill: SOFT, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", "stroke-linecap": "round", "stroke-linejoin": "round" }, box); + txt(g, 0, TH / 2 + 28, name, { "text-anchor": "middle", "font-size": 21, "font-weight": 700 }); + tf(g, x, y); + button(g, `${why}: ${pick}`, () => { pinned = i; api.poke(); }); + return { box, x, y, t0: 0.5 + i * 1.25 }; + }); + el("rect", { x: TRAY[0] - 78, y: TRAY[1] + 12, width: 156, height: 26, rx: 13, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, svg); + const pegs = PICK.map((_, i) => el("path", { d: SHAPES[i](R), fill: PEGCOL[i], stroke: INK, "stroke-width": 3, "stroke-linejoin": "round", "pointer-events": "none" }, svg)); + const cap = strip(svg, 536, 740); + return (t) => { + const out = seg(t, 10.9, 11.5); + let cur = -1; + tiles.forEach((k, i) => { + const a = k.t0, land = a + 1.05, pop = ob(seg(t, a, a + 0.3)), p = io(seg(t, a + 0.6, land)), snap = Math.sin(seg(t, land, land + 0.35) * Math.PI); + const x = TRAY[0] + (k.x - TRAY[0]) * p, y = TRAY[1] - 22 + (k.y - TRAY[1] + 22) * p - 70 * Math.sin(Math.PI * p) + (p <= 0 ? Math.sin(t * 7) * 3 : 0); + tf(pegs[i], x, y, cl(pop, 0, 1.4) * (1.3 - 0.3 * p) * (1 + 0.14 * snap), p > 0 && p < 1 ? Math.sin(p * Math.PI) * 24 : 0); + pegs[i].setAttribute("opacity", (t < a ? 0 : 1) * (1 - out)); + k.box.setAttribute("transform", `scale(${1 + 0.07 * snap})`); + if (t >= a) cur = i; + }); + const show = pinned >= 0 ? pinned : cur; + cap.textContent = show < 0 || (pinned < 0 && t >= 10.9) ? "Seven reasons, seven shapes" : `${PICK[show][1]}: ${PICK[show][2]}`; + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 64, 150, "Claude Code", { "font-size": 76, "font-weight": 700 }); + txt(svg, 64, 236, "alternatives", { "font-size": 76, "font-weight": 700 }); + el("path", { d: "M66 262 H500", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 318, "7 coding agents to switch to", { "font-size": 25, "font-weight": 500, fill: MUTE }); + el("rect", { x: 56, y: 372, width: 848, height: 112, rx: 30, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + SHAPES.forEach((f, i) => { const x = 120 + i * 120, up = i === 3; if (up) el("path", { d: f(30), fill: "#fff", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", "stroke-linecap": "round", "stroke-linejoin": "round", transform: `translate(${x} 428)` }, svg); el("path", { d: f(30), fill: PEGCOL[i], stroke: INK, "stroke-width": 3, "stroke-linejoin": "round", transform: `translate(${x} ${up ? 330 : 428}) rotate(${up ? 14 : 0})` }, svg); }); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/claude-code-alternatives/pegs.png b/blog/src/assets/media/claude-code-alternatives/pegs.png new file mode 100644 index 000000000..531017de2 Binary files /dev/null and b/blog/src/assets/media/claude-code-alternatives/pegs.png differ diff --git a/blog/src/assets/media/claude-code-alternatives/picker.png b/blog/src/assets/media/claude-code-alternatives/picker.png deleted file mode 100644 index 600db0c18..000000000 Binary files a/blog/src/assets/media/claude-code-alternatives/picker.png and /dev/null differ diff --git a/blog/src/assets/media/claude-code-alternatives/prices.png b/blog/src/assets/media/claude-code-alternatives/prices.png deleted file mode 100644 index 3f899481e..000000000 Binary files a/blog/src/assets/media/claude-code-alternatives/prices.png and /dev/null differ diff --git a/blog/src/assets/media/claude-code-alternatives/reasons.png b/blog/src/assets/media/claude-code-alternatives/reasons.png deleted file mode 100644 index ab0da94b8..000000000 Binary files a/blog/src/assets/media/claude-code-alternatives/reasons.png and /dev/null differ diff --git a/blog/src/assets/media/claude-code-alternatives/tipping.png b/blog/src/assets/media/claude-code-alternatives/tipping.png new file mode 100644 index 000000000..0159f749b Binary files /dev/null and b/blog/src/assets/media/claude-code-alternatives/tipping.png differ diff --git a/blog/src/assets/media/claude-code-alternatives/twins.png b/blog/src/assets/media/claude-code-alternatives/twins.png new file mode 100644 index 000000000..20eace3ca Binary files /dev/null and b/blog/src/assets/media/claude-code-alternatives/twins.png differ diff --git a/blog/src/assets/media/claude-code-alternatives/versus.png b/blog/src/assets/media/claude-code-alternatives/versus.png deleted file mode 100644 index 6ccb6f38d..000000000 Binary files a/blog/src/assets/media/claude-code-alternatives/versus.png and /dev/null differ diff --git a/blog/src/assets/media/openai-text-watermarking/grain.png b/blog/src/assets/media/openai-text-watermarking/grain.png new file mode 100644 index 000000000..52db4136f Binary files /dev/null and b/blog/src/assets/media/openai-text-watermarking/grain.png differ diff --git a/blog/src/assets/media/openai-text-watermarking/hero.png b/blog/src/assets/media/openai-text-watermarking/hero.png new file mode 100644 index 000000000..46b62568b Binary files /dev/null and b/blog/src/assets/media/openai-text-watermarking/hero.png differ diff --git a/blog/src/assets/media/openai-text-watermarking/motion.css b/blog/src/assets/media/openai-text-watermarking/motion.css new file mode 100644 index 000000000..32bcbfa9f --- /dev/null +++ b/blog/src/assets/media/openai-text-watermarking/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "OpenAI text watermarking". 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/openai-text-watermarking/motion.js b/blog/src/assets/media/openai-text-watermarking/motion.js new file mode 100644 index 000000000..acd44aa4f --- /dev/null +++ b/blog/src/assets/media/openai-text-watermarking/motion.js @@ -0,0 +1,309 @@ +/* Bright coded scenes for "OpenAI text watermarking". 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 }); + }; + + /* ---- shared bits: a grey word pill, a measured chip, a key, a "no" mark ---- */ + const GREY = "#E4DCEE"; + const wide = (n, s) => { let w = 0; try { w = n.getComputedTextLength(); } catch (e) { w = 0; } return w || s.length * parseFloat(n.getAttribute("font-size")) * 0.56; }; + const word = (p, cx, cy, w, h) => { const g = el("g", {}, p), r = el("rect", { x: -w / 2, y: -h / 2, width: w, height: h, rx: h / 2, fill: GREY }, g); tf(g, cx, cy); return { g, r, cx, cy }; }; + const paint = (p, kind) => { // 0 plain, 1 carries the pattern, 2 swapped + p.r.setAttribute("fill", kind === 1 ? YEL : kind === 2 ? "#fff" : GREY); + p.r.setAttribute("stroke", kind ? INK : "none"); p.r.setAttribute("stroke-width", 2.5); + p.r.setAttribute("stroke-dasharray", kind === 2 ? "4 5" : "none"); + }; + const chip = (p, cx, y, s, fill) => { + const g = el("g", {}, p), r = el("rect", { y, height: 40, rx: 20, fill, stroke: INK, "stroke-width": 2.5 }, g), n = txt(g, cx, y + 27, s, { "text-anchor": "middle", "font-size": 20 }); + const fit = (str) => { n.textContent = str; const w = wide(n, str) + 36; r.setAttribute("x", cx - w / 2); r.setAttribute("width", w); }; + fit(s); return { g, r, n, fit }; + }; + const key = (g, fill) => { + el("path", { d: "M-2 0 H30 M17 0 V10 M26 0 V7", stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round" }, g); + el("circle", { cx: -15, cy: 0, r: 13, fill, stroke: INK, "stroke-width": 3 }, g); + el("circle", { cx: -15, cy: 0, r: 4, fill: "#fff", stroke: INK, "stroke-width": 2 }, g); + }; + const nope = (g, r, w = 3) => { el("circle", { r, fill: "none", stroke: MUTE, "stroke-width": w }, g); el("path", { d: `M${-r * 0.7} ${-r * 0.7} L${r * 0.7} ${r * 0.7}`, stroke: MUTE, "stroke-width": w, "stroke-linecap": "round" }, g); }; + const para = (svg, x0, x1, y0, rows, pitch, h, cut) => { // a ragged paragraph of word pills + const out = []; let x = x0, row = 0; + for (let i = 0; i < 200; i++) { + const w = 44 + ((i * i * 7 + i * 3) % 9) * 9; + if (x + w > x1) { row++; x = x0; } + if (row >= rows || (row === rows - 1 && x > cut)) break; + const p = word(svg, x + w / 2, y0 + row * pitch, w, h); p.mark = (i * 5 + row) % 3 === 0; out.push(p); x += w + 10; + } + return out; + }; + + const SC = {}; + + /* ---- 1. a key slides over a plain paragraph and a pattern of word choices lights up ---- */ + SC.grain = { + dur: 11, still: 8.2, + build(svg) { + txt(svg, 44, 62, "What is OpenAI text watermarking?", { "font-size": 32, "font-weight": 700 }); + el("rect", { x: 72, y: 108, width: 680, height: 212, rx: 26, fill: SKY }, svg); + el("rect", { x: 60, y: 96, width: 680, height: 212, rx: 26, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const pills = para(svg, 88, 712, 133, 4, 46, 30, 430); + const scan = el("g", {}, svg); + el("rect", { x: -38, y: 84, width: 76, height: 238, rx: 22, fill: SKY, "fill-opacity": 0.3, stroke: INK, "stroke-width": 3 }, scan); + el("path", { d: "M0 322 V336", stroke: INK, "stroke-width": 3 }, scan); + el("circle", { cx: 0, cy: 358, r: 24, fill: LIL, stroke: INK, "stroke-width": 3 }, scan); + const kg = el("g", {}, scan); key(kg, YEL); tf(kg, -1, 358, 0.62); + const add = txt(svg, 400, 416, "does not add", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + const nots = ["hidden characters", "invisible spaces", "unusual punctuation"].map((s) => { + const g = el("g", {}, svg), r = el("rect", { y: -20, height: 40, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 2.5, "stroke-dasharray": "5 5" }, g), ic = el("g", {}, g), n = txt(g, 0, 7, s, { "font-size": 20 }); + nope(ic, 9, 2.5); const w = wide(n, s) + 54; r.setAttribute("x", -w / 2); r.setAttribute("width", w); tf(ic, -w / 2 + 21, 0); n.setAttribute("x", -w / 2 + 38); + return { g, w }; + }); + let nx = 400 - (nots.reduce((a, c) => a + c.w, 0) + 24) / 2; + nots.forEach((c) => { c.x = nx + c.w / 2; nx += c.w + 12; }); + const cap = strip(svg, 508, 620); + return (t) => { + const sx = -70 + 738 * io(seg(t, 2.2, 6.4)), out = 1 - seg(t, 10, 10.7); + tf(scan, sx, 0); scan.setAttribute("opacity", out); + pills.forEach((p, i) => { + const a = ob(seg(t, 0.2 + i * 0.05, 0.5 + i * 0.05)), k = p.mark ? cl((sx + 38 - p.cx) / 40) : 0; + tf(p.g, p.cx, p.cy, a * (1 + 0.18 * Math.sin(k * Math.PI))); paint(p, k > 0 ? 1 : 0); + p.g.setAttribute("opacity", a > 0 ? out : 0); + }); + nots.forEach((c, i) => { const k = ob(seg(t, 6.5 + i * 0.35, 6.95 + i * 0.35)); tf(c.g, c.x, 450, 0.6 + 0.4 * k); c.g.setAttribute("opacity", seg(t, 6.5 + i * 0.35, 6.7 + i * 0.35) * out); }); + add.setAttribute("opacity", seg(t, 6.3, 6.6) * out); + cap.textContent = t < 2 ? "A model writes one token at a time" : t < 6.5 ? "A detector with the same key looks for the pattern" : "It is part of the wording itself."; + }; + }, + }; + + /* ---- 2. three lamps: one you switch on yourself, one on a timer, one with no switch at all ---- */ + SC.switches = { + dur: 10.5, still: 7.6, + build(svg, api) { + txt(svg, 44, 62, "Who gets the watermark, and when?", { "font-size": 32, "font-weight": 700 }); + el("path", { d: "M40 96 H760", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const X = [140, 400, 660], BY = 182; + const lamps = X.map((cx, i) => { + const g = el("g", {}, svg), rays = el("g", { opacity: 0 }, g); + el("path", { d: `M${cx} 96 V${BY - 50}`, stroke: INK, "stroke-width": 3 }, g); + for (let j = 0; j < 7; j++) { const a = ((15 + j * 25) * Math.PI) / 180; el("path", { d: `M${cx + Math.cos(a) * 46} ${BY + Math.sin(a) * 46} L${cx + Math.cos(a) * 60} ${BY + Math.sin(a) * 60}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round", "stroke-dasharray": i === 1 ? "3 6" : "none" }, rays); } + el("circle", { cx, cy: BY, r: 34, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const glow = el("circle", { cx, cy: BY, r: 34, fill: YEL, "fill-opacity": 0, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: cx - 15, y: BY - 52, width: 30, height: 22, rx: 6, fill: INK }, g); + el("path", { d: `M${cx - 12} ${BY + 8} q6 -20 12 0 q6 -20 12 0`, stroke: INK, "stroke-width": 2.5, fill: "none", "stroke-linecap": "round" }, g); + return { g, rays, glow, cx }; + }); + const PY = 262, plates = X.map((cx) => { const g = el("g", {}, svg); el("rect", { x: cx - 54, y: PY, width: 108, height: 116, rx: 18, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); return g; }); + // the API: a rocker the reader can flip + el("rect", { x: X[0] - 62, y: PY - 8, width: 124, height: 132, rx: 24, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, plates[0]); + el("rect", { x: X[0] - 17, y: PY + 20, width: 34, height: 76, rx: 17, fill: "#fff", stroke: INK, "stroke-width": 3 }, plates[0]); + const knob = el("circle", { cx: X[0], r: 17, fill: GREY, stroke: INK, "stroke-width": 3 }, plates[0]); + // the EU: a timer dial that runs on its own + el("circle", { cx: X[1], cy: PY + 58, r: 34, fill: "#fff", stroke: INK, "stroke-width": 3 }, plates[1]); + for (let j = 0; j < 4; j++) el("path", { d: "M0 -26 V-20", stroke: INK, "stroke-width": 3, "stroke-linecap": "round", transform: `translate(${X[1]} ${PY + 58}) rotate(${j * 90})` }, plates[1]); + const hand = el("path", { d: "M0 0 V-19", stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, plates[1]); + el("circle", { cx: X[1], cy: PY + 58, r: 5, fill: SKY, stroke: INK, "stroke-width": 2.5 }, plates[1]); + // outside the EU: a blank plate, two screws and no switch + el("rect", { x: X[2] - 17, y: PY + 28, width: 34, height: 60, rx: 17, fill: "none", stroke: MUTE, "stroke-width": 2.5, "stroke-dasharray": "5 6" }, plates[2]); + [PY + 14, PY + 102].forEach((y) => { el("circle", { cx: X[2], cy: y, r: 5, fill: "#fff", stroke: INK, "stroke-width": 2 }, plates[2]); }); + const T = [["OpenAI API,", "worldwide", "for select models", "From 5 Oct 2026"], ["ChatGPT and Codex", "in the EU", "eligible text output", "across all plans"], ["ChatGPT and Codex", "outside the EU", "rollout is EU only", "No date given"]]; + T.forEach((r, i) => { + txt(svg, X[i], 412, r[0], { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); txt(svg, X[i], 439, r[1], { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + txt(svg, X[i], 524, r[2], { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); txt(svg, X[i], 550, r[3], { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + }); + const c0 = chip(svg, X[0], 454, "Off by default", "#fff"); + chip(svg, X[1], 454, "Over the coming weeks", SKY); + chip(svg, X[2], 454, "Not included", SOFT); + let user = null, uT = -9, cur = false, shown = null; + button(plates[0], "OpenAI API switch. Opt in, off by default.", () => { user = !cur; uT = api.now(); api.poke(); }); + return (t, now) => { + let k; + if (user != null) { const q = ob(seg(now, uT, uT + 0.4)); k = user ? q : 1 - q; } else k = ob(seg(t, 1.8, 2.25)) - io(seg(t, 9.6, 10.1)); + cur = user != null ? user : t >= 1.95 && t < 9.85; + knob.setAttribute("cy", PY + 79 - 42 * k); knob.setAttribute("fill", cur ? YEL : GREY); + lamps[0].glow.setAttribute("fill-opacity", cl(k)); lamps[0].rays.setAttribute("opacity", cl(k)); + if (shown !== cur) { shown = cur; c0.fit(cur ? "Opt in" : "Off by default"); c0.r.setAttribute("fill", cur ? YEL : "#fff"); plates[0].setAttribute("aria-pressed", cur ? "true" : "false"); } + const e = 0.6 * oc(seg(t, 3, 7)) * (1 - seg(t, 9.6, 10.1)) * (0.88 + 0.12 * Math.sin(now * 3)); + lamps[1].glow.setAttribute("fill-opacity", e); lamps[1].rays.setAttribute("opacity", e * 1.4); + hand.setAttribute("transform", `translate(${X[1]} ${PY + 58}) rotate(${t * 60})`); + lamps.forEach((l, i) => l.g.setAttribute("transform", `rotate(${Math.sin(now * 1.3 + i * 2) * 1.6} ${l.cx} 96)`)); + const w = seg(t, 5, 5.7); plates[2].setAttribute("transform", `translate(${Math.sin(w * Math.PI * 5) * (1 - w) * 6} 0)`); + }; + }, + }; + + /* ---- 3. a needle that swings up as the passage grows and falls back as words are swapped ---- */ + SC.signal = { + dur: 14, still: 11.6, + build(svg, api) { + txt(svg, 44, 62, "How reliable is the detection?", { "font-size": 32, "font-weight": 700 }); + const sheet = el("g", {}, svg), card = el("rect", { x: 60, y: 94, width: 680, height: 76, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, sheet); + const WS = [[52, 68, 44, 60, 72, 48, 56, 64, 40, 64], [64, 48, 60, 40, 72, 56, 44, 68, 52, 64]], RANK = { 3: 0, 14: 1, 8: 2, 11: 3, 17: 4 }, pills = []; + WS.forEach((ws, row) => { let x = 80; ws.forEach((w, col) => { const i = row * 10 + col, p = word(sheet, x + w / 2, 132 + row * 54, w, 36); p.row = row; p.col = col; p.mark = (i * 3 + row) % 5 < 3; p.rank = i in RANK ? RANK[i] : -1; p.ut = null; p.s = 0; pills.push(p); x += w + 8; }); }); + const G = [240, 418], R = 124, LEN = Math.PI * R, arc = `M${G[0] - R} ${G[1]} A${R} ${R} 0 0 1 ${G[0] + R} ${G[1]}`; + el("path", { d: arc, stroke: SOFT, "stroke-width": 26, fill: "none", "stroke-linecap": "round" }, svg); + const prog = el("path", { d: arc, stroke: YEL, "stroke-width": 26, fill: "none", "stroke-linecap": "round" }, svg); + el("path", { d: `M${G[0] - R + 13} ${G[1]} A${R - 13} ${R - 13} 0 0 1 ${G[0] + R - 13} ${G[1]}`, stroke: INK, "stroke-width": 2, fill: "none", "stroke-dasharray": "2 10", "stroke-linecap": "round" }, svg); + const needle = el("path", { d: "M0 0 H-98", stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, svg); + el("circle", { cx: G[0], cy: G[1], r: 14, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, G[0] - R, 462, "weaker", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + txt(svg, G[0] + R, 462, "stronger", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + const read = el("g", {}, svg), ls = [290, 316, 342].map((y) => txt(read, 452, y, "", { "font-size": 20 })); + const big = txt(read, 450, 406, "", { "font-size": 54, "font-weight": 700 }); + txt(read, 452, 438, "Detection rate", { "font-size": 20, "font-weight": 500, fill: MUTE }); + const L = [["200 token passages", "content such as psychology", "About 80%"], ["400 token passages", "content such as psychology", "About 95%"], ["400 token passages,", "unedited", "About 92%"], ["Same passages,", "10% of words replaced", "with synonyms", "66%"], ["Same passages,", "25% of words replaced", "17%"]]; + const SX = 180, SW = 440, SY = 520, VAL = [92, 66, 17], CNT = [0, 2, 5], NAMES = ["unedited", "10% replaced", "25% replaced"]; + el("path", { d: `M${SX} ${SY} H${SX + SW}`, stroke: SOFT, "stroke-width": 16, "stroke-linecap": "round" }, svg); + const fill = el("path", { d: `M${SX} ${SY} H${SX}`, stroke: YEL, "stroke-width": 16, "stroke-linecap": "round" }, svg); + NAMES.forEach((s, i) => { el("circle", { cx: SX + (SW * i) / 2, cy: SY, r: 5, fill: INK }, svg); txt(svg, SX + (SW * i) / 2, 566, s, { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); }); + const hit = el("rect", { x: SX - 24, y: SY - 26, width: SW + 48, height: 52, fill: "#fff", "fill-opacity": 0 }, svg); hit.style.cursor = "pointer"; + const knob = el("g", { role: "slider", tabindex: "0", "aria-label": "How many words were replaced", "aria-valuemin": 0, "aria-valuemax": 2, class: "mg-hit" }, svg); + el("circle", { r: 34, fill: "#fff", "fill-opacity": 0 }, knob); + el("circle", { r: 21, fill: "#fff", stroke: INK, "stroke-width": 3 }, knob); + el("path", { d: "M-6 -6 L-12 0 L-6 6 M6 -6 L12 0 L6 6", stroke: INK, "stroke-width": 2.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, knob); + let user = null, uT = -9, vFrom = 0, kFrom = 0, val = 0, kPos = 0, shown = -1; + const go = (s) => { if (s === user) return; const now = api.now(); pills.forEach((p) => { if (p.rank < 0) return; p.ut = p.rank < CNT[s] ? (p.s > 0.5 ? now - 1 : p.ut == null ? now : p.ut) : null; }); vFrom = val; kFrom = kPos; user = s; uT = now; api.poke(); }; + const pick = (e) => { const r = svg.getBoundingClientRect(); go(Math.round(cl((((e.clientX - r.left) / r.width) * 800 - SX) / SW) * 2)); }; + let down = false; + knob.addEventListener("pointerdown", (e) => { down = true; knob.setPointerCapture(e.pointerId); pick(e); }); + knob.addEventListener("pointermove", (e) => { if (down) pick(e); }); + knob.addEventListener("pointerup", () => { down = false; }); + hit.addEventListener("pointerdown", pick); + knob.addEventListener("keydown", (e) => { const s = e.key === "ArrowRight" || e.key === "ArrowUp" ? 1 : e.key === "ArrowLeft" || e.key === "ArrowDown" ? -1 : 0; if (s) { e.preventDefault(); go(cl((user == null ? 0 : user) + s, 0, 2)); } }); + return (t, now) => { + const auto = user == null; let v, kp, st; + if (auto) { + v = 80 * ob(seg(t, 1.3, 2.1)) + 15 * ob(seg(t, 3.7, 4.5)) - 3 * io(seg(t, 5.6, 6.2)) - 26 * ob(seg(t, 7.8, 8.5)) - 49 * ob(seg(t, 10, 10.7)) - 17 * io(seg(t, 13.1, 13.8)); + kp = io(seg(t, 7.6, 8.1)) + io(seg(t, 9.8, 10.3)) - 2 * io(seg(t, 13.1, 13.8)); + st = t < 3.5 ? 0 : t < 5.8 ? 1 : t < 7.75 ? 2 : t < 9.95 ? 3 : 4; + } else { v = vFrom + (VAL[user] - vFrom) * ob(seg(now, uT, uT + 0.55)); kp = kFrom + (user - kFrom) * oc(seg(now, uT, uT + 0.3)); st = 2 + user; } + val = v; kPos = kp; v = cl(v, 0, 100); + const gone = auto ? 1 - seg(t, 13.1, 13.7) : 1; + card.setAttribute("height", auto ? 76 + 54 * ob(seg(t, 2.8, 3.3)) : 130); + const f = auto ? Math.abs(Math.cos(seg(t, 5.5, 6.1) * Math.PI)) : 1; sheet.setAttribute("transform", `translate(0 159) scale(1 ${Math.max(f, 0.02)}) translate(0 -159)`); + pills.forEach((p) => { + const d = p.row ? 3 + p.col * 0.06 : 0.3 + p.col * 0.07, a = auto ? ob(seg(t, d, d + 0.35)) : 1, n = p.rank; let s = 0; + if (n >= 0) s = auto ? (n < 2 ? seg(t, 7.7 + n * 0.15, 8.1 + n * 0.15) : seg(t, 9.9 + (n - 2) * 0.12, 10.3 + (n - 2) * 0.12)) : p.ut == null ? 0 : seg(now, p.ut, p.ut + 0.4); + p.s = s; paint(p, s > 0.5 ? 2 : p.mark ? 1 : 0); + p.g.setAttribute("transform", `translate(${p.cx} ${p.cy}) scale(${a} ${a * Math.max(Math.abs(Math.cos(s * Math.PI)), 0.05)})`); p.g.setAttribute("opacity", a > 0 ? gone : 0); + }); + prog.setAttribute("stroke-dasharray", `${(LEN * v) / 100} 999`); prog.setAttribute("opacity", v > 0.5 ? 1 : 0); + needle.setAttribute("transform", `translate(${G[0]} ${G[1]}) rotate(${(180 * v) / 100})`); + if (shown !== st) { shown = st; const r = L[st], sm = r.slice(0, -1); ls.forEach((n, i) => { const j = i - (3 - sm.length), head = j === 0; n.textContent = j < 0 ? "" : sm[j]; n.setAttribute("font-size", head ? 22 : 20); n.setAttribute("font-weight", head ? 700 : 500); n.setAttribute("fill", head ? INK : MUTE); }); big.textContent = r[r.length - 1]; knob.setAttribute("aria-valuenow", Math.max(st - 2, 0)); knob.setAttribute("aria-valuetext", `${NAMES[Math.max(st - 2, 0)]}, detection ${r[r.length - 1]}`); } + read.setAttribute("opacity", auto ? seg(t, 1.2, 1.6) * gone : 1); + tf(knob, SX + (SW * kp) / 2, SY); fill.setAttribute("d", `M${SX} ${SY} H${SX + (SW * kp) / 2}`); + }; + }, + }; + + /* ---- 4. a "proof" stamp on a rail bounces off five tags and leaves no mark on any of them ---- */ + SC.stamp = { + dur: 12.6, still: 10.8, + build(svg) { + txt(svg, 44, 62, "What does a watermark not prove?", { "font-size": 32, "font-weight": 700 }); + const X = (i) => 96 + i * 152, T = (i) => 1 + i * 1.8, RY = 94, HOV = 262, HIT = 292, S = 1.1, TALL = 130 * S; + const N = [["Not human", "effort."], ["Not", "ownership."], ["Not", "identity."], ["Not", "accuracy."], ["No watermark", "is not proof", "of a human."]]; + el("path", { d: `M40 ${RY} H760`, stroke: SOFT, "stroke-width": 16, "stroke-linecap": "round" }, svg); + el("path", { d: `M40 ${RY} H760`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const tags = N.map((lines, i) => { + const g = el("g", {}, svg), no = el("g", {}, g), lab = el("g", {}, svg); + el("rect", { x: -60, y: -52, width: 120, height: 104, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + el("circle", { r: 32, fill: SOFT, stroke: MUTE, "stroke-width": 2.5, "stroke-dasharray": "5 6" }, g); + g.appendChild(no); nope(no, 32, 4); + lines.forEach((s, j) => txt(lab, X(i), 438 + j * 26, s, { "text-anchor": "middle", "font-size": 20 })); + return { g, no, lab }; + }); + const rod = el("path", { stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + const cart = el("rect", { x: -24, y: RY - 13, width: 48, height: 26, rx: 9, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + const boing = el("g", {}, svg); + [-1, 1].forEach((d) => [-16, 0, 16].forEach((a) => el("path", { d: "M66 0 H80", stroke: INK, "stroke-width": 3, "stroke-linecap": "round", transform: `scale(${d} 1) rotate(${a})` }, boing))); + const st = el("g", {}, svg); + el("rect", { x: -11, y: -88, width: 22, height: 42, fill: BLUE, stroke: INK, "stroke-width": 3 }, st); + el("circle", { cx: 0, cy: -106, r: 24, fill: BLUE, stroke: INK, "stroke-width": 3 }, st); + el("rect", { x: -46, y: -12, width: 92, height: 12, rx: 4, fill: INK }, st); + el("rect", { x: -52, y: -50, width: 104, height: 40, rx: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, st); + txt(st, 0, -23, "proof", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + strip(svg, 524, 700).textContent = "It does not prove who wrote a text, or that a person did not."; + const spring = (k) => 1 - Math.cos(k * 7.854) * Math.pow(1 - k, 3); + return (t) => { + let x = X(0), y = HOV, rot = 0, bz = 0; const gone = 1 - seg(t, 11.8, 12.3); + for (let i = 1; i < 5; i++) x += 152 * io(seg(t, T(i) - 0.75, T(i) - 0.1)); + x += (400 - X(4)) * io(seg(t, 9.5, 10.2)) + (X(0) - 400) * io(seg(t, 11.8, 12.5)); + tags.forEach((g, i) => { + const a = T(i), dn = seg(t, a, a + 0.3), up = seg(t, a + 0.3, a + 1), sh = seg(t, a + 0.3, a + 0.9); + if (t >= a && t < a + 1) { y = HOV + (HIT - HOV) * (up > 0 ? 1 - spring(up) : dn * dn); rot = Math.sin(up * Math.PI * 3) * (1 - up) * 7; bz = Math.sin(seg(t, a + 0.28, a + 0.62) * Math.PI); } + tf(g.g, X(i) + Math.sin(sh * Math.PI * 5) * (1 - sh) * 7, 348); + const k = ob(seg(t, a + 0.4, a + 0.75)); tf(g.no, 0, 0, k); g.no.setAttribute("opacity", k > 0 ? gone : 0); + const o = oc(seg(t, a + 0.45, a + 0.8)); g.lab.setAttribute("opacity", o * gone); g.lab.setAttribute("transform", `translate(0 ${8 * (1 - o)})`); + }); + tf(st, x, y, S, rot); tf(cart, x, 0); tf(boing, x, HIT - 28); boing.setAttribute("opacity", bz); + const r = (rot * Math.PI) / 180; rod.setAttribute("d", `M${x} ${RY} L${x + Math.sin(r) * TALL} ${y - Math.cos(r) * TALL}`); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 64, 150, "OpenAI text", { "font-size": 76, "font-weight": 700 }); + txt(svg, 64, 236, "watermarking", { "font-size": 76, "font-weight": 700 }); + el("path", { d: "M66 262 H560", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 318, "textGrain, who gets it, limits", { "font-size": 25, "font-weight": 500, fill: MUTE }); + const SXH = 772; + para(svg, 64, 896, 410, 2, 50, 34, 900).forEach((p) => paint(p, p.mark && p.cx < SXH ? 1 : 0)); + el("rect", { x: SXH - 42, y: 374, width: 84, height: 122, rx: 22, fill: SKY, "fill-opacity": 0.3, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: `M${SXH} 336 V374`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + const k = el("g", {}, svg); key(k, LIL); k.setAttribute("transform", `translate(${SXH} 250) rotate(90) scale(3)`); + 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/openai-text-watermarking/signal.png b/blog/src/assets/media/openai-text-watermarking/signal.png new file mode 100644 index 000000000..8bf2fbc5f Binary files /dev/null and b/blog/src/assets/media/openai-text-watermarking/signal.png differ diff --git a/blog/src/assets/media/openai-text-watermarking/stamp.png b/blog/src/assets/media/openai-text-watermarking/stamp.png new file mode 100644 index 000000000..d515de118 Binary files /dev/null and b/blog/src/assets/media/openai-text-watermarking/stamp.png differ diff --git a/blog/src/assets/media/openai-text-watermarking/switches.png b/blog/src/assets/media/openai-text-watermarking/switches.png new file mode 100644 index 000000000..11380a26e Binary files /dev/null and b/blog/src/assets/media/openai-text-watermarking/switches.png differ diff --git a/blog/src/assets/media/openclaw-alternatives/cast.png b/blog/src/assets/media/openclaw-alternatives/cast.png deleted file mode 100644 index 726e57c35..000000000 Binary files a/blog/src/assets/media/openclaw-alternatives/cast.png and /dev/null differ diff --git a/blog/src/assets/media/openclaw-alternatives/hero.png b/blog/src/assets/media/openclaw-alternatives/hero.png index 01395f9dc..1c340058e 100644 Binary files a/blog/src/assets/media/openclaw-alternatives/hero.png and b/blog/src/assets/media/openclaw-alternatives/hero.png differ diff --git a/blog/src/assets/media/openclaw-alternatives/motion.css b/blog/src/assets/media/openclaw-alternatives/motion.css new file mode 100644 index 000000000..75d1d3827 --- /dev/null +++ b/blog/src/assets/media/openclaw-alternatives/motion.css @@ -0,0 +1,11 @@ +/* Bright coded scenes for "OpenClaw Alternatives". 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 .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +@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/openclaw-alternatives/motion.js b/blog/src/assets/media/openclaw-alternatives/motion.js new file mode 100644 index 000000000..756d0a009 --- /dev/null +++ b/blog/src/assets/media/openclaw-alternatives/motion.js @@ -0,0 +1,313 @@ +/* Bright coded scenes for "OpenClaw Alternatives". 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; + + /* ---- the seven picks, in the post's order: name, colour, the post's "Best for" line ---- */ + const PICKS = [ + ["Hermes Agent", LIL, "the same job, with memory that grows"], + ["Munder Difflin", YEL, "people who used OpenClaw mostly to get code written"], + ["NanoClaw", SKY, "isolation first"], + ["ZeroClaw", BLUE, "a small always on service"], + ["nanobot", MINT, "Python people who want to read the core"], + ["Goose", LIL, "a desktop app first"], + ["Claude", SOFT, "no server to run"], + ]; + const pill = (p, name, col, w = 180, h = 44) => { + const g = el("g", {}, p); + el("rect", { x: -w / 2 - 5, y: -h / 2 - 5, width: w + 10, height: h + 10, rx: h / 2 + 5, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + el("rect", { x: -w / 2, y: -h / 2, width: w, height: h, rx: h / 2, fill: col, stroke: INK, "stroke-width": 3 }, g); + txt(g, 0, 7, name, { "text-anchor": "middle", "font-size": 20 }); + return g; + }; + const bin = (svg, x, w, top, bot) => el("path", { d: `M${x} ${top} V${bot - 22} Q${x} ${bot} ${x + 22} ${bot} H${x + w - 22} Q${x + w} ${bot} ${x + w} ${bot - 22} V${top}`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const hopper = (svg, x, y) => el("path", { d: `M${x - 62} ${y} H${x + 62} L${x + 26} ${y + 62} H${x - 26} Z`, fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + + const SC = {}; + + /* ---- 1. seven picks drop out of a hopper and a flap sorts them: six free, one hosted ---- */ + SC.sorter = { + dur: 12, still: 9.6, + build(svg, api) { + txt(svg, 44, 62, "Seven picks, sorted", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 60, "tap a name", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const top = 262, bot = 440, FX = 500, FY = 214; + bin(svg, 40, 412, top, bot); bin(svg, 548, 212, top, bot); + const lab = [txt(svg, 246, 476, "", { "text-anchor": "middle", "font-size": 22 }), txt(svg, 654, 476, "", { "text-anchor": "middle", "font-size": 22 })]; + const slot = (i) => (i === 6 ? [654, 408] : [148 + (i % 2) * 196, 408 - Math.floor(i / 2) * 52]); + const D = (i) => 0.8 + i * 1.05; + let pinned = -1, lastT = 0; + const flap = el("g", {}, svg); + el("rect", { x: -84, y: -7, width: 168, height: 14, rx: 7, fill: YEL, stroke: INK, "stroke-width": 3 }, flap); + el("circle", { r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, flap); + const pills = PICKS.map(([name, col, best], i) => { + const g = pill(svg, name, col); + button(g, `${name}. Best for: ${best}`, () => { pinned = i; api.poke(); }); + return g; + }); + hopper(svg, FX, 84); + const tip = strip(svg, 516, 736); + const angle = (t) => { + const flip = ob(seg(t, D(6) - 0.55, D(6) - 0.1)), back = io(seg(t, 11.2, 11.9)); + let a = -18 + 36 * (flip - back); + for (let i = 0; i < 7; i++) { const s = t - D(i) - 0.3; if (s > 0 && s < 0.7) a += Math.sin(s * 22) * 3 * (1 - s / 0.7); } + return a; + }; + return (t) => { + if (t < lastT) pinned = -1; lastT = t; + const a = angle(t), out = seg(t, 11.3, 11.9), n = [0, 0]; let last = -1; + tf(flap, FX, FY, 1, a); + pills.forEach((g, i) => { + const d = D(i), dir = i === 6 ? 1 : -1, [sx, sy] = slot(i); + const k1 = seg(t, d, d + 0.3), k2 = io(seg(t, d + 0.3, d + 0.6)), k3 = seg(t, d + 0.6, d + 1.1); + const s = 0.55 + 0.45 * ob(k3), off = 22 * 0.55 + 9, ra = (a * Math.PI) / 180; + const on = (k) => [FX + dir * 78 * k * Math.cos(ra) + off * Math.sin(ra), FY + dir * 78 * k * Math.sin(ra) - off * Math.cos(ra)]; + let [x, y] = on(k2); + if (k1 < 1) y = lerp(118, y, k1 * k1); + if (k3 > 0) { const [ex, ey] = on(1); x = lerp(ex, sx, oc(k3)); y = lerp(ey, sy, k3 * k3) - Math.sin(seg(t, d + 1.1, d + 1.45) * Math.PI) * 7; } + tf(g, x, y, s * (1 - 0.5 * out), a * k1 * (1 - cl(k3 * 1.6))); + g.setAttribute("opacity", t < d ? 0 : 1 - out); + g.lastChild.previousSibling.setAttribute("stroke-width", pinned === i ? 4.5 : 3); + if (t >= d + 1.1) { n[i === 6 ? 1 : 0]++; last = i; } + }); + lab[0].textContent = `${n[0]} free and open source`; lab[1].textContent = `${n[1]} hosted`; + lab[0].setAttribute("fill", n[0] ? INK : MUTE); lab[1].setAttribute("fill", n[1] ? INK : MUTE); + const show = pinned >= 0 ? pinned : last; + tip.textContent = show < 0 ? "Each one is best for a different job" : `${PICKS[show][0]}: ${PICKS[show][2]}`; + }; + }, + }; + + /* ---- 2. one command carries four parcels from OpenClaw to Hermes Agent ---- */ + SC.move = { + dur: 11, still: 7.4, + build(svg, api) { + txt(svg, 44, 62, "The easiest move", { "font-size": 32, "font-weight": 700 }); + const top = 160, bot = 388, LX = 48, RX = 468, W = 284; + const things = [["settings", SKY], ["memories", LIL], ["skills", MINT], ["API keys", YEL]]; + const at = (x0, i) => [x0 + 74 + (i % 2) * 136, i < 2 ? 270 : 346]; + el("path", { d: `M190 134 Q400 34 610 134`, stroke: INK, "stroke-width": 2, fill: "none", "stroke-dasharray": "2 12", "stroke-linecap": "round" }, svg); + [[LX, "OpenClaw"], [RX, "Hermes Agent"]].forEach(([x, name]) => { + bin(svg, x, W, top, bot).setAttribute("stroke-width", 4); + el("path", { d: `M${x + 40} ${bot + 2} v10 M${x + W - 40} ${bot + 2} v10`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + txt(svg, x + 4, 432, name, { "font-size": 24, "font-weight": 700 }); + el("rect", { x: x + W - 76, y: 408, width: 76, height: 34, rx: 17, fill: MINT, stroke: INK, "stroke-width": 2 }, svg); + txt(svg, x + W - 38, 432, "MIT", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + things.forEach((_, i) => { const [cx, cy] = at(x, i); el("rect", { x: cx - 62, y: cy - 32, width: 124, height: 64, rx: 12, fill: "none", stroke: "#C9BDD8", "stroke-width": 2, "stroke-dasharray": "5 6" }, svg); }); + }); + const box = (name, col, ghost) => { + const g = el("g", {}, svg); + el("rect", Object.assign({ x: -62, y: -32, width: 124, height: 64, rx: 12, fill: ghost ? "#fff" : col, stroke: INK, "stroke-width": ghost ? 2.5 : 3 }, ghost ? { "stroke-dasharray": "6 6" } : {}), g); + if (!ghost) el("rect", { x: -13, y: -32, width: 26, height: 12, rx: 3, fill: "#fff", stroke: INK, "stroke-width": 2 }, g); + txt(g, 0, ghost ? 7 : 13, name, { "text-anchor": "middle", "font-size": 20, fill: ghost ? MUTE : INK }); + return g; + }; + const solid = things.map(([n, c]) => box(n, c)), ghosts = things.map(([n, c]) => box(n, c, true)); + const tick = el("g", {}, svg); + el("circle", { r: 24, fill: MINT, stroke: INK, "stroke-width": 3 }, tick); + el("path", { d: "M-10 1 L-3 8 L11 -8", stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, tick); + const cmd = el("g", {}, svg); + el("rect", { x: -145, y: -26, width: 290, height: 52, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, cmd); + txt(cmd, 0, 7, "hermes claw migrate", { "text-anchor": "middle", "font-family": MONO, "font-size": 20, "font-weight": 700 }); + let dry = -99; + const btn = el("g", {}, svg); + el("rect", { x: 473, y: 457, width: 160, height: 62, rx: 31, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, btn); + el("rect", { x: 478, y: 462, width: 150, height: 52, rx: 26, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-dasharray": "7 6" }, btn); + txt(btn, 553, 495, "--dry-run", { "text-anchor": "middle", "font-family": MONO, "font-size": 20, "font-weight": 700 }); + button(btn, "--dry-run: preview the import first", () => { dry = api.now(); api.poke(); }); + const cap = strip(svg, 534, 700); + const fly = (g, i, k, sc = 1) => { + const [sx, sy] = at(LX, i), [dx, dy] = at(RX, i), m = io(k); + tf(g, lerp(sx, dx, m), lerp(sy, dy, m) - Math.sin(Math.PI * m) * (sy - 122), sc, Math.sin(Math.PI * m) * (i % 2 ? 9 : -9)); + }; + const D = (i) => 1.4 + i * 1.1; + return (t, now) => { + const s = now - dry, on = s >= 0 && s < 4.2; + if (!on && dry > 0) { dry = -99; api.seek(0); return; } + const tt = on ? 0 : t, back = on ? 0 : io(seg(t, 10.2, 10.9)); + let done = 0; + solid.forEach((g, i) => { + const k = seg(tt, D(i), D(i) + 0.9) - back, land = Math.sin(seg(tt, D(i) + 0.9, D(i) + 1.25) * Math.PI); + fly(g, i, k, 1 + 0.08 * land); if (k >= 1) done++; + }); + ghosts.forEach((g, i) => { const k = on ? seg(s, 0.3 + i * 0.35, 1.2 + i * 0.35) : 0; fly(g, i, k); g.setAttribute("opacity", on && k > 0 ? 1 - seg(s, 3.6, 4.1) : 0); }); + const press = on ? 0 : Math.sin(seg(tt, 0.9, 1.3) * Math.PI); + tf(cmd, 317, 488, 1 - 0.08 * press); + btn.firstChild.nextSibling.setAttribute("fill", on ? SKY : "#fff"); + const tk = on ? 0 : ob(seg(tt, 5.9, 6.3)) * (1 - seg(tt, 10.2, 10.6)); + tf(tick, 740, 160, tk); tick.setAttribute("opacity", tk > 0.01 ? 1 : 0); + cap.textContent = on ? "--dry-run previews the import first" : done === 4 ? "Imported: settings, memories, skills and API keys" : tt > 1 && done < 4 && back === 0 ? "It imports your settings, memories, skills and API keys" : "One command moves an OpenClaw setup to Hermes Agent"; + }; + }, + }; + + /* ---- 3. price tags on a line flip over: seven say Free, one says $20 ---- */ + SC.tags = { + dur: 11, still: 7, + build(svg, api) { + txt(svg, 44, 62, "What it costs to start", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 60, "tap the Claude tag", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const names = ["OpenClaw", "Hermes Agent", "Munder Difflin", "NanoClaw", "ZeroClaw", "nanobot", "Goose", "Claude"]; + const lines = [112, 296], xs = [112, 304, 496, 688]; + lines.forEach((y) => el("path", { d: `M24 ${y} Q400 ${y + 18} 776 ${y}`, stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round" }, svg)); + let max = false, flipAt = -9; + const tags = names.map((name, i) => { + const x = xs[i % 4], u = (x - 24) / 752, y = lines[i < 4 ? 0 : 1] + 2 * u * (1 - u) * 18, paid = i === 7; + const g = el("g", {}, svg), card = el("g", {}, g); + el("path", { d: "M0 0 V30", stroke: INK, "stroke-width": 2.5 }, card); + if (paid) el("path", { d: "M-91 40 L-63 13 H63 L91 40 V140 Q91 157 74 157 H-74 Q-91 157 -91 140 Z", fill: "none", stroke: INK, "stroke-width": 2, "stroke-linejoin": "round", class: "mg-ring" }, card); + const body = el("path", { d: "M-86 42 L-60 18 H60 L86 42 V140 Q86 152 74 152 H-74 Q-86 152 -86 140 Z", fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, card); + el("circle", { cx: 0, cy: 34, r: 5.5, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, card); + const nm = txt(card, 0, 76, name, { "text-anchor": "middle", "font-size": 20 }); + const pr = txt(card, 0, paid ? 112 : 122, "?", { "text-anchor": "middle", "font-size": 32, "font-weight": 700 }); + const sub = txt(card, 0, 138, "", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + el("rect", { x: -7, y: -12, width: 14, height: 24, rx: 5, fill: YEL, stroke: INK, "stroke-width": 2.5 }, g); + if (paid) button(g, "Claude: switch between Pro and Max", () => { max = !max; flipAt = api.now(); api.poke(); }); + return { g, card, body, nm, pr, sub, x, y, paid, d: 1 + i * 0.6 }; + }); + const cap = strip(svg, 500, 560); cap.textContent = "The software costs nothing. The model does."; + return (t, now) => { + tags.forEach((o, i) => { + const k = seg(t, o.d, o.d + 0.5) - seg(t, 10.2, 10.7), s = t - o.d; + let sx = Math.abs(Math.cos(Math.PI * k)), up = k > 0.5, showMax = max; + if (o.paid) { const kk = seg(now, flipAt, flipAt + 0.5); sx *= Math.abs(Math.cos(Math.PI * kk)); if (kk < 0.5) showMax = !max; } + const swing = (s > 0 ? Math.sin(s * 7) * 9 * Math.exp(-s * 1.5) : 0) + Math.sin(now * 1.3 + i * 1.7) * 1.2; + tf(o.g, o.x, o.y, 1, swing); + o.card.setAttribute("transform", `scale(${Math.max(sx, 0.02)} 1)`); + o.body.setAttribute("fill", !up ? SOFT : o.paid ? YEL : MINT); + o.pr.setAttribute("fill", up ? INK : MUTE); + if (o.paid) { + o.nm.textContent = !up ? "Claude" : showMax ? "Claude Max" : "Claude Pro"; + o.pr.textContent = !up ? "?" : showMax ? "From $100" : "$20"; + o.pr.setAttribute("font-size", up && showMax ? 28 : 32); o.pr.setAttribute("y", up && showMax ? 120 : up ? 112 : 122); + o.sub.textContent = up && !showMax ? "a month" : ""; + } else o.pr.textContent = up ? "Free" : "?"; + }); + }; + }, + }; + + /* ---- 4. two switches, the job then whose computer, and the pick pops out ---- */ + SC.switches = { + dur: 12, still: 6, + build(svg, api) { + const P = 4; + txt(svg, 44, 62, "Two switches, one pick", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 60, "tap to flip", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const combos = [ + { a: 0, b: 0, dim: 0, pick: "Hermes Agent", col: LIL, why: "OpenClaw's job with a learning loop and an import command" }, + { a: 1, b: 0, dim: 0, pick: "Munder Difflin", col: YEL, why: "The work is code and you want several agents on it at once" }, + { a: 1, b: 1, dim: 1, pick: "Claude", col: SKY, why: "You would rather pay than host" }, + ]; + let cur = 0; + const hit = el("g", {}, svg); + el("rect", { x: 70, y: 96, width: 660, height: 238, rx: 28, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, hit); + const sw = (y, head, l, r, col) => { + const g = el("g", {}, hit); + txt(g, 400, y - 46, head, { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + const track = el("rect", { x: 290, y: y - 30, width: 220, height: 60, rx: 30, fill: col, stroke: INK, "stroke-width": 3 }, g); + const L = txt(g, 270, y + 8, l, { "text-anchor": "end", "font-size": 22 }), R = txt(g, 530, y + 8, r, { "font-size": 22 }); + const knob = el("g", {}, g); + el("circle", { r: 23, fill: "#fff", stroke: INK, "stroke-width": 3 }, knob); + el("path", { d: "M-6 -7 V7 M0 -7 V7 M6 -7 V7", stroke: INK, "stroke-width": 2, "stroke-linecap": "round" }, knob); + return (p, dim) => { + tf(knob, 320 + 160 * p, y); g.setAttribute("opacity", 1 - 0.68 * dim); + L.setAttribute("fill", p < 0.5 ? INK : MUTE); R.setAttribute("fill", p >= 0.5 ? INK : MUTE); + L.setAttribute("font-weight", p < 0.5 ? 700 : 500); R.setAttribute("font-weight", p >= 0.5 ? 700 : 500); + }; + }; + const s1 = sw(170, "first, the job", "chat assistant", "code", SKY), s2 = sw(288, "then, whose computer", "yours", "Anthropic's", MINT); + button(hit, "Flip to the next combination", () => api.seek(((cur + 1) % 3) * P + 0.01)); + const chev = el("path", { d: "M-13 -7 L0 7 L13 -7", stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + const card = el("g", {}, svg); + const shade = el("rect", { x: -190, y: -34, width: 400, height: 88, rx: 24, fill: YEL }, card); + el("rect", { x: -200, y: -44, width: 400, height: 88, rx: 24, fill: "#fff", stroke: INK, "stroke-width": 3 }, card); + const name = txt(card, 0, 14, "", { "text-anchor": "middle", "font-size": 40, "font-weight": 700 }); + const cap = strip(svg, 516, 720); + return (t) => { + const i = Math.floor(t / P) % 3, u = (t % P) / P, c = combos[i], p = combos[(i + 2) % 3]; + cur = i; + const m1 = ob(seg(u, 0.02, 0.14)), m2 = ob(seg(u, 0.08, 0.2)); + s1(lerp(p.a, c.a, m1), lerp(p.dim, c.dim, seg(u, 0, 0.12))); s2(lerp(p.b, c.b, m2), 0); + const pop = ob(seg(u, 0.22, 0.34)) * (1 - io(seg(u, 0.93, 1))); + tf(card, 400, 424, Math.max(pop, 0)); card.setAttribute("opacity", pop > 0.02 ? 1 : 0); + shade.setAttribute("fill", c.col); name.textContent = c.pick; + tf(chev, 400, 350 + Math.sin(seg(u, 0.14, 0.3) * Math.PI) * 8); + cap.textContent = pop > 0.02 ? c.why : "Pick by the job first, then by whose computer does the work"; + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 64, 138, "OpenClaw", { "font-size": 76, "font-weight": 700 }); + txt(svg, 64, 224, "alternatives", { "font-size": 76, "font-weight": 700 }); + el("path", { d: "M66 250 H500", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 306, "7 picks for chat, code and cloud", { "font-size": 25, "font-weight": 500, fill: MUTE }); + hopper(svg, 760, 70); + const fl = el("g", {}, svg); + el("rect", { x: -84, y: -7, width: 168, height: 14, rx: 7, fill: YEL, stroke: INK, "stroke-width": 3 }, fl); + el("circle", { r: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, fl); + tf(fl, 760, 246, 1, -18); + tf(pill(svg, PICKS[6][0], PICKS[6][1]), 760, 178, 0.9); + const spots = [[160, 392, -3], [356, 386, 2], [552, 392, -2], [748, 330, -14], [258, 452, 2], [454, 448, -2], [650, 452, 3]]; + [0, 1, 2, 3, 4, 5].forEach((i, j) => { const [x, y, r] = spots[j < 3 ? j : j + 1]; tf(pill(svg, PICKS[i][0], PICKS[i][1]), x, y, 1, r); }); + el("path", { d: "M48 492 H848", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, 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/openclaw-alternatives/move.png b/blog/src/assets/media/openclaw-alternatives/move.png new file mode 100644 index 000000000..f9998ff9c Binary files /dev/null and b/blog/src/assets/media/openclaw-alternatives/move.png differ diff --git a/blog/src/assets/media/openclaw-alternatives/pick.png b/blog/src/assets/media/openclaw-alternatives/pick.png deleted file mode 100644 index eb5264f49..000000000 Binary files a/blog/src/assets/media/openclaw-alternatives/pick.png and /dev/null differ diff --git a/blog/src/assets/media/openclaw-alternatives/prices.png b/blog/src/assets/media/openclaw-alternatives/prices.png deleted file mode 100644 index 18b6ead68..000000000 Binary files a/blog/src/assets/media/openclaw-alternatives/prices.png and /dev/null differ diff --git a/blog/src/assets/media/openclaw-alternatives/sorter.png b/blog/src/assets/media/openclaw-alternatives/sorter.png new file mode 100644 index 000000000..221db2826 Binary files /dev/null and b/blog/src/assets/media/openclaw-alternatives/sorter.png differ diff --git a/blog/src/assets/media/openclaw-alternatives/switches.png b/blog/src/assets/media/openclaw-alternatives/switches.png new file mode 100644 index 000000000..212be049b Binary files /dev/null and b/blog/src/assets/media/openclaw-alternatives/switches.png differ diff --git a/blog/src/assets/media/openclaw-alternatives/tags.png b/blog/src/assets/media/openclaw-alternatives/tags.png new file mode 100644 index 000000000..c27b8ba83 Binary files /dev/null and b/blog/src/assets/media/openclaw-alternatives/tags.png differ diff --git a/blog/src/assets/media/openclaw-alternatives/versus.png b/blog/src/assets/media/openclaw-alternatives/versus.png deleted file mode 100644 index b59d8b399..000000000 Binary files a/blog/src/assets/media/openclaw-alternatives/versus.png and /dev/null differ diff --git a/blog/src/posts/agent-tools-today-2026-10-06.md b/blog/src/posts/agent-tools-today-2026-10-06.md new file mode 100644 index 000000000..f4ea86c0d --- /dev/null +++ b/blog/src/posts/agent-tools-today-2026-10-06.md @@ -0,0 +1,62 @@ +--- +title: "Agent Tools Today, 6 Oct 2026: OpenAI Text Watermarks, Reflection Beam, Cloudflare Web Search" +seoTitle: "Agent Tools Today (6 Oct 2026): Launches, Skills, MCP Servers" +description: "The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 6 Oct 2026, each with its source." +date: 2026-10-06 +category: news +categoryLabel: News +type: Non-technical +series: agent-tools-today +primaryKeyword: "agent tools today 6 oct 2026" +secondaryKeywords: ["openai text watermarking", "reflection beam", "cloudflare web search api", "removemacai", "plannotator"] +tags: ["Daily Brief", "Claude Code", "Codex", "Skills", "Open Source"] +--- + +Agent tools today, 6 Oct 2026, in one line: the question was where things come from. OpenAI started watermarking text, Wikimedia said it found agent activity on its sites that it believes is OpenAI's, and Reflection announced an open weight model you cannot download yet. Every number was read on 6 Oct. The previous edition is [here](/blog/agent-tools-today-2026-10-04/). + +
Animation. Five unmarked parcels drop from a box with a question mark onto a conveyor, get stamped with a number, and stack into a tower. Each one gets a name tag: OpenAI text watermarking, Beam, Cloudflare Web Search API, Claude Code v2.1.290 and Codex CLI 0.160.1.
The five launches of the day, tagged as they arrive. Tap a parcel for its one line.
+ +## Top 5 launches + +1. **OpenAI text watermarking.** On 5 Oct OpenAI said API customers worldwide can opt in to text watermarking for select models, and that eligible ChatGPT and Codex output in the EU gets an invisible watermark over the coming weeks. The detector is limited to approved researchers and expert organizations. More in [our explainer](/blog/openai-text-watermarking/). [Source](https://openai.com/index/eu-text-provenance) +2. **Beam.** Reflection AI announced its first open weight model on 5 Oct: 501 billion total parameters, 23 billion active, built for coding, reasoning and agentic work. The weights are not out: the post says later this month, with a sign up for early access. 347 points on Hacker News. [Source](https://reflection.ai/blog/introducing-beam) +3. **Cloudflare Web Search API.** In beta, per a changelog entry dated 2 Oct. Agents search the web through AI Gateway, with Ceramic.ai, Exa or Linkup as the provider. 498 points on Hacker News. [Source](https://developers.cloudflare.com/changelog/post/2026-10-02-introducing-web-search-api/) +4. **Claude Code v2.1.290.** Released 5 Oct. It adds `claude attach ` and `claude logs `, and gives plugin hooks an `agentId` so a hook can tell a subagent's permission check from the main session's. [Source](https://github.com/anthropics/claude-code/releases/tag/v2.1.290) +5. **Codex CLI 0.160.1.** A patch on 5 Oct with one fix: remote stdio MCP servers keep `SYSTEMROOT`, `TEMP` and `TMP` when launched with explicitly configured remote environment variables. Background: [what is Codex](/blog/what-is-codex/). [Source](https://github.com/openai/codex/releases/tag/rust-v0.160.1) + +## Top 4 skills, MCP servers and plugins + +1. **Plannotator 0.28.0.** Released 5 Oct. A question you ask in Ask AI now goes to the Claude Code, Pi or OpenCode 2 session that opened Plannotator. On Claude Code 2.1.287 and later, in the interactive terminal, the plugin runs as a mod, on by default. Apache 2.0, 9,156 stars on 6 Oct. [Source](https://github.com/backnotprop/plannotator/releases/tag/v0.28.0) +2. **cinetic.** An agent skill that makes your coding agent direct a short film from code, built with Remotion by default or HyperFrames. Version 1.1.0 landed on 4 Oct. Install with `npx skills add Leonxlnx/cinetic`. MIT, 81 stars on 6 Oct. [Source](https://github.com/Leonxlnx/cinetic) +3. **Agent Memory Repo.** An open spec that treats agent memory as a git repo: clone, grep, update, commit. Created 4 Oct, MIT, 266 stars on 6 Oct. Related: [long term memory for Claude Code](/blog/give-claude-code-long-term-memory/). [Source](https://github.com/AgentMemoryRepo/agentmemoryrepo) +4. **dsh-plugin-upgrade-skill.** Community skills that help an agent migrate DeepSeek Harness plugins when a new version breaks them: 11 skills, 195 upgrade cards and 63 benchmark tasks. MIT, 222 stars on 6 Oct. [Source](https://github.com/oh-my-dsh/dsh-plugin-upgrade-skill) + +## Top 5 moves from multi agent tools + +1. **Orca.** Release v1.4.221 on 5 Oct carries a security fix. When Orca marked a folder as trusted for GitHub Copilot, it could leave `~/.copilot/config.json`, which can hold login tokens, readable by other users on the same machine. The file is now set to owner only when Orca writes to it, so a file an older version left open is fixed the next time Orca adds a trusted folder. [Source](https://github.com/stablyai/orca/releases/tag/v1.4.221) +2. **[Munder Difflin](https://harnessmd.com/download).** Free and open source: no new release today. The latest is v0.5.5, from 1 Oct. [Source](https://github.com/HarnessMD/munder-difflin/releases) +3. **Pi.** Two more patch releases on 5 Oct. In v1.0.3 the `azure` provider also serves Foundry Chat Completions deployments. v1.0.4 adds `*` patterns to `--tools` and a `--no-mcp` flag that turns MCP off for one run. Background: [what is Pi agent](/blog/what-is-pi-agent/). [Source](https://github.com/earendil-works/pi/releases) +4. **Herdr GPUI.** Release v20261005.1 on 5 Oct: fan out one prompt to several agents, send line notes on an agent's uncommitted changes back to it, and checkpoint agent turns. Apache 2.0, 904 stars on 6 Oct. [Source](https://github.com/penso/herdr-gpui/releases/tag/v20261005.1) +5. **OpenClaw.** A prerelease, 2026.10.1-beta.1, on 5 Oct, with fixes to sessions, memory, replies and media. The latest stable release is still v2026.9.8. See [OpenClaw alternatives](/blog/openclaw-alternatives/). [Source](https://github.com/openclaw/openclaw/releases) + +## Top 5 rising repos + +1. **RemoveMacAI.** Turns off Apple Intelligence on macOS 27, removes its downloaded models and stops macOS downloading them again. Created 29 Sep, v0.2.5 on 5 Oct. MIT, 2,174 stars on 6 Oct, 758 points on Hacker News. [Source](https://github.com/omlahore/RemoveMacAI) +2. **framefields.** Code first video rendered on WebGPU, in one npm package that coding agents drive with TypeScript. It reached Hacker News as Gitframes (56 points) before the rename. Beta, Apache 2.0, 211 stars on 6 Oct. [Source](https://github.com/gatewai-dev/framefields) +3. **squeez.** A hook based token compressor for seven AI CLI hosts, Claude Code and Codex CLI among them. Its README claims up to 95% compression of bash output. v1.48.15 on 5 Oct. Apache 2.0, 213 stars on 6 Oct. [Source](https://github.com/claudioemmanuel/squeez) +4. **Minigraf.** An embedded graph database, pitched as graph memory for AI agents, with Datalog queries and bi-temporal history. 114 stars on 6 Oct, 31 points on Hacker News. [Source](https://github.com/project-minigraf/minigraf) +5. **mold 3.0.0.** Not an agent tool. The linker was rewritten from C++ to Rust, and 3.0.0 on 5 Oct is the first release of the Rust version. MIT, 17,437 stars on 6 Oct, 233 points on Hacker News. [Source](https://github.com/rui314/mold/releases/tag/v3.0.0) + +New repos can gain stars quickly for reasons other than use, so treat these as names to watch, not recommendations. + +## Top 3 things people are arguing about + +1. **Whose agents are on your site?** The Wikimedia Foundation said on 5 Oct it found activity it believes came from agents operated by OpenAI: edits that were almost all in sandbox areas, failed attempts to misuse its Etherpad, and millions of automated API requests. It found no evidence that its systems or data were compromised. 262 points on Hacker News. [Source](https://diff.wikimedia.org/2026/10/05/openai-rogue-agent-activities-found-on-wikimedia-projects/) +2. **How far does a text watermark go?** OpenAI's own figures, as reported by Unite.AI: on 400 token passages, replacing 10% of words with synonyms cut detection from about 92% to 66%, and replacing 25% cut it to 17%. [Source](https://www.unite.ai/openai-begins-phased-text-watermarking-under-eu-ai-act-rules/) +3. **Capability or efficiency?** Reflection's own post says Kimi K3 remains ahead on raw capability and that Beam's advantage is efficiency at inference time. The Hacker News thread had 105 comments. [Source](https://news.ycombinator.com/item?id=49969183) + +## 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? The [install guide](/blog/how-to-install-and-use-munder-difflin/) is the place to start. + + diff --git a/blog/src/posts/claude-code-alternatives.md b/blog/src/posts/claude-code-alternatives.md index 48a5fcd6c..1ac504434 100644 --- a/blog/src/posts/claude-code-alternatives.md +++ b/blog/src/posts/claude-code-alternatives.md @@ -32,7 +32,7 @@ This list is for people who use Claude Code, or nearly did, and want something e We see four reasons people look elsewhere: price, licence, model choice, or wanting an editor. On 5 Oct 2026, [Anthropic's pricing page](https://claude.com/pricing) put Claude Code in Claude Pro ($20 if billed monthly) and Max (from $100 a month), and not in the Free plan. -
Animation. Michael, Kevin, Oscar, Dwight and Jim walk on stage one by one and each says one reason people look for a Claude Code alternative: price, licence, models and editors.
Four reasons, five colleagues. Prices from Anthropic's pricing page, 5 Oct 2026.
+
Animation. A balance starts tipped towards a block marked stay. Four weights drop on the other pan one by one, for price, licence, model choice and wanting an editor, and the balance tips the other way.
Four reasons, one balance. Prices from Anthropic's pricing page, 5 Oct 2026.
The Claude Code repo is public, but its licence file says "All rights reserved", so it is not open source; [is Claude Code open source](/blog/is-claude-code-open-source/) explains why that matters. It is also built around Claude models. If none of that bothers you, stay: it is a good tool. @@ -74,21 +74,21 @@ Every row was checked on 5 Oct 2026 against the tool's own site, pricing page or Codex CLI is the closest, in our view. Both are terminal agents made by a model lab, and the first plan that lists each CLI costs $20 a month (5 Oct 2026). The licence is the real difference. -
Animation. A table fills in row by row, setting Claude Code against Codex CLI on licence, first plan with the CLI, models, GitHub stars and latest release, while Oscar checks each row.
Stars and releases from GitHub; licences from each repo's licence file, 5 Oct 2026.
+
Animation. Two cards, Claude Code and Codex CLI, slide on top of each other. Terminal and 20 dollars a month match and get a tick, but the two padlocks do not fit and pop out: a closed one marked Proprietary and an open one marked Apache 2.0.
Same shape, same $20 plan, different licence. Licences from each repo's licence file, 5 Oct 2026.
## Is there a free Claude Code alternative? Yes: OpenCode includes free models, and Cline, Pi and Aider are free software that bill you only through your model provider. Cursor's Hobby plan is free too, with limited Agent requests. ChatGPT Free includes limited Codex access, though the Codex pricing page lists only the desktop app on that plan. -
Animation. Six price steps rise from OpenCode's free models to Claude Max at 100 dollars a month while Kevin points at the three steps that cost 20 dollars.
Monthly prices from each vendor's pricing page, 5 Oct 2026.
+
Animation. Coins drop into six stacks, from OpenCode free models and OpenCode Go at 10 dollars up to Claude Max from 100 dollars. A plank lands flat across Claude Pro, ChatGPT Plus and Cursor Individual, because all three cost 20 dollars a month.
Monthly prices from each vendor's pricing page, 5 Oct 2026.
Free software is not a free model: with an API key you pay by the token. Read [Claude Code pricing](/blog/how-much-does-claude-code-cost/) before you leave for the money alone. ## Who should pick what? -Pick by the reason you are leaving. Kevin's maths is right, by the way: three steps on that ladder cost exactly $20, Claude Pro included (5 Oct 2026), so price alone rarely settles it. +Pick by the reason you are leaving. The coin stacks above are level for a reason: three of them cost exactly $20, Claude Pro included (5 Oct 2026), so price alone rarely settles it. -
Animation. Pam draws four question boxes joined by arrows marked no: want an editor, pay for ChatGPT, want open source, want them all. Each box opens to name a tool.
Four questions, in the order that rules tools out fastest.
+
Animation. A shape sorter with seven tiles, one for each tool. Seven pegs pop up in turn, each standing for a reason to leave, and each one drops into the only tile it fits.
Seven reasons, seven tools: each peg fits one hole.
- **You want the same workflow, different lab:** Codex CLI. - **You want to keep Claude Code and add others:** Munder Difflin. @@ -98,4 +98,4 @@ Pick by the reason you are leaving. Kevin's maths is right, by the way: three st - **You want to build your own harness:** Pi. - **You want every change in git:** Aider. - + diff --git a/blog/src/posts/openai-text-watermarking.md b/blog/src/posts/openai-text-watermarking.md new file mode 100644 index 000000000..37fc1509b --- /dev/null +++ b/blog/src/posts/openai-text-watermarking.md @@ -0,0 +1,117 @@ +--- +title: "OpenAI text watermarking: textGrain, who gets it, limits" +seoTitle: "ChatGPT Watermark: OpenAI Text Watermarking (textGrain)" +description: "OpenAI text watermarking (textGrain) is opt in for the API now and is coming to eligible ChatGPT and Codex text in the EU. Limits checked 6 Oct 2026." +date: 2026-10-06 +category: concepts +categoryLabel: Concepts +type: Non-technical +primaryKeyword: "openai text watermarking" +secondaryKeywords: ["chatgpt watermark", "textgrain", "does chatgpt watermark text", "codex watermark", "openai watermark detector", "can you remove chatgpt watermark"] +tags: ["Concepts", "Codex", "OpenAI"] +faq: + - q: "Does ChatGPT watermark text outside the EU?" + a: "Not by default. OpenAI's 5 Oct 2026 announcement says the ChatGPT and Codex rollout is for the EU only, and that it is not making text watermarking a global default at launch. Outside the EU, text is marked when an API customer has switched watermarking on for a supported model." + - q: "Can you remove a ChatGPT watermark?" + a: "OpenAI does not claim the watermark is permanent. In its test of 400 token passages, replacing 10% of words with synonyms cut detection from about 92% to 66%, and replacing 25% cut it to 17%. OpenAI's help page adds that substantial paraphrasing or translation can make the watermark undetectable. Light edits and copy and paste are what it is designed to survive." + - q: "Is there a public OpenAI watermark detector for text?" + a: "No. OpenAI says the text detector is limited to approved researchers and expert organizations, reviewed case by case, because of the risk of missed watermarks and false positives. Its openai.com/verify tool checks images and audio, not text." + - q: "Does the ChatGPT watermark add hidden characters?" + a: "No. OpenAI's help page says textGrain does not add hidden characters, invisible spaces or unusual punctuation. The signal is in the pattern of word choices, so copying and pasting adds no hidden material. The watermark is part of the wording, so OpenAI expects it to remain when the wording is kept." + - q: "Does text watermarking make OpenAI models worse or slower?" + a: "OpenAI says no. Its published benchmark table for its Astra model shows moves in both directions, which OpenAI says fall within normal run to run noise, for example 72.80% without the watermark and 71.68% with it on DeepSWE v1.1. The help page calls the speed impact negligible. These are OpenAI's own tests, checked 6 Oct 2026." +--- + +OpenAI text watermarking is textGrain, an invisible signal in the model's word choices. [OpenAI announced it](https://openai.com/index/eu-text-provenance) on 5 Oct 2026: opt in for API customers worldwide, and on for eligible ChatGPT and Codex text output in the EU over the coming weeks. Our view: a compliance signal, not proof of who wrote a text. Checked 6 Oct 2026. + +Codex is one of the coding agents people run inside [Munder Difflin](https://harnessmd.com/download), which 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. So this change reaches some of our readers directly. The [Concepts hub](/blog/topics/concepts/) has more explainers like this one. + +## What is OpenAI text watermarking? + +It is a way to mark text from OpenAI models so software can spot it later. OpenAI's [announcement](https://openai.com/index/eu-text-provenance) says the EU AI Act requires generative AI providers to make generated text identifiable in a machine-readable way. textGrain is OpenAI's answer for text. + +The watermark is not something you can see. OpenAI's [help page](https://help.openai.com/en/articles/8912793-provenance-signals-content-credentials-synthid-in-openai-generated-content) says it does not add hidden characters, invisible spaces or unusual punctuation. It is part of the wording itself. + +It is also not an AI detector of the usual kind. The same page says third party tools typically use classifiers that analyze text after it is written, while the EU AI Act requires a signal embedded in the generated text. OpenAI says it plans to release textGrain as open source. + +
Animation. A paragraph of plain grey word shapes is scanned by a frame that carries a key, and some of the words light up yellow in a pattern. Three labels below say it does not add hidden characters, invisible spaces or unusual punctuation.
The signal is in the word choices, not in extra characters. From OpenAI's help page, checked 6 Oct 2026.
+ +## Who gets the watermark, and when? + +Eligible ChatGPT and Codex users in the EU get it over the coming weeks, and API customers anywhere can opt in for select models. These scopes come from OpenAI's [announcement](https://openai.com/index/eu-text-provenance). [Unite.AI](https://www.unite.ai/openai-begins-phased-text-watermarking-under-eu-ai-act-rules/) dates it 5 October 2026: + +| Where | What happens | When | +| --- | --- | --- | +| ChatGPT and Codex in the EU | Watermark added to eligible text output, across all plans | Over the coming weeks | +| ChatGPT and Codex outside the EU | Not included. OpenAI says the rollout is EU only and not a global default at launch | No date given | +| OpenAI API, worldwide | Opt in, for select models. Off by default | From 5 Oct 2026 | +| Text watermark detector | Applications open for approved researchers and expert organizations | From 5 Oct 2026 | + +The EU rules behind this already apply. The European Commission's [Code of Practice page](https://digital-strategy.ec.europa.eu/en/policies/code-practice-ai-generated-content) says the Article 50 transparency obligations apply from 2 August 2026. Signing the code is voluntary. The obligations are not. + +
Animation. Three hanging lamps. The OpenAI API lamp has a switch marked opt in, the lamp for ChatGPT and Codex in the EU runs on a timer marked over the coming weeks, and the lamp for ChatGPT and Codex outside the EU has no switch and is marked not included.
Who gets the watermark, from OpenAI's announcement. Flip the API switch. Checked 6 Oct 2026.
+ +## How does textGrain work? + +It nudges the model's random word choices with a secret key, and a detector with the same key looks for the pattern. A model writes one token at a time, a token being a word or a piece of one. OpenAI's [help page](https://help.openai.com/en/articles/8912793-provenance-signals-content-credentials-synthid-in-openai-generated-content) says the system builds several adjusted sets of likelihoods for the next token, and the key selects which set to use. Averaged together, the sets match the model's original likelihoods. + +The [technical report](https://cdn.openai.com/pdf/e9508624-d767-41b6-a26d-e34ca798ada6/textgrain-entropy-calibrated-watermarking-for-language-model-text.pdf), dated 5 October 2026, says the detector needs the generated text and the secret key. Our reading: without that key, a third party site cannot run this check, and OpenAI has not said it shares the key. + +## How reliable is the detection? + +Stronger on long, loosely worded text, and much weaker on short, exact or edited text. All figures below are OpenAI's own, from the [announcement](https://openai.com/index/eu-text-provenance) and the [help page](https://help.openai.com/en/articles/8912793-provenance-signals-content-credentials-synthid-in-openai-generated-content). The length rows and the language rows are separate tests, both at a 1% false positive rate. + +| Test | Detection rate | +| --- | --- | +| 200 token passages, content such as psychology | About 80% | +| 400 token passages, content such as psychology | About 95% | +| Content such as mathematics | "Substantially lower", no figure given in the text | +| 400 token passages, unedited | About 92% | +| Same passages, 10% of words replaced with synonyms | 66% | +| Same passages, 25% of words replaced | 17% | +| Spanish, the highest baseline of the 24 official EU languages | 69.0% | +| Romanian, the lowest baseline, before OpenAI raised the watermark strength | 42.2% | + +OpenAI says it raised the watermark strength for languages under 60%, and gives no figures for the result in the text. + +So can you remove a ChatGPT watermark? OpenAI's numbers say replacing one word in ten with a synonym already weakens it a lot, and the help page says substantial paraphrasing or translation can make it undetectable. OpenAI [says](https://openai.com/index/eu-text-provenance) these limits are part of why the detector is not public at launch. + +
Animation. A dial needle swings to about 80% for 200 token passages and about 95% for 400 token passages. In a second test it falls from about 92% unedited to 66% with 10% of words replaced and 17% with 25% replaced.
OpenAI's own detection figures. Drag the slider to replace words. Checked 6 Oct 2026.
+ +## What does a watermark not prove? + +It does not prove who wrote a text, or that a person did not. OpenAI's [announcement](https://openai.com/index/eu-text-provenance) lists five limits: + +* **Not human effort.** It can indicate an OpenAI system generated or processed part of a passage, not how much editing or judgment a person added. +* **Not ownership.** It says nothing about who owns the text or who is responsible for it. +* **Not identity.** It does not link a person, account, prompt or conversation to the text. +* **Not accuracy.** It does not tell you whether the passage is true. +* **No watermark is not proof of a human.** The text may be too short, edited, translated, from an unsupported model, older than the watermark, or from another company's tools. + +
Animation. A stamp marked proof slides along a rail and bounces off five tags without leaving a mark: not human effort, not ownership, not identity, not accuracy, and no watermark is not proof of a human.
The five limits OpenAI lists for its text watermark. Checked 6 Oct 2026.
+ +## Does it affect Codex and code? + +Eligible Codex text output in the EU is in scope, and OpenAI does not spell out what happens to code. The [announcement](https://openai.com/index/eu-text-provenance) covers "eligible ChatGPT and Codex text output" and does not define eligible. The [help page](https://help.openai.com/en/articles/8912793-provenance-signals-content-credentials-synthid-in-openai-generated-content) says code is harder to watermark, because there are fewer plausible choices for what comes next. It adds that the EU Code of Practice does not require watermarks in code snippets, or in outputs shorter than 200 tokens, about 150 words in English. That page names ChatGPT alone for the EU watermark, so we go by the announcement. + +If Codex is new to you, start with [what Codex is](/blog/what-is-codex/) and [how to install the Codex CLI](/blog/how-to-install-codex-cli/). + +## How do you turn it on in the API? + +You switch it on in the platform settings, for one project or for the whole organization. OpenAI's [help page](https://help.openai.com/en/articles/8912793-provenance-signals-content-credentials-synthid-in-openai-generated-content) gives two places: + +1. **Whole organization:** Organization settings, then Data controls, then Text provenance. +2. **One project:** Project Settings, then Text provenance. + +Turn on Allow text watermarking, select your models, then select Save. Switching it on does not give you the detector. + +## What should you do now? + +Very little, unless you build on the API. + +* **In the EU, on ChatGPT or Codex:** nothing to set up. The pages we opened describe no user switch for it. +* **Building on the API:** it stays off unless you choose it. OpenAI's [help page](https://help.openai.com/en/articles/8912793-provenance-signals-content-credentials-synthid-in-openai-generated-content) says it cannot advise on your legal obligations, so ask your own legal team. +* **Judging someone's text:** do not treat a missing watermark as proof of anything. +* **Choosing a coding agent:** our view is that this should not decide it. [Codex CLI vs Claude Code](/blog/codex-cli-vs-claude-code/) and our [Codex Max plan tips](/blog/codex-max-plan-tips/) cover what does. + + diff --git a/blog/src/posts/openclaw-alternatives.md b/blog/src/posts/openclaw-alternatives.md index 479cdb086..c0a3055be 100644 --- a/blog/src/posts/openclaw-alternatives.md +++ b/blog/src/posts/openclaw-alternatives.md @@ -26,7 +26,7 @@ thumb: "/blog/assets/media/openclaw-alternatives/hero.png" The best OpenClaw alternative is Hermes Agent if you want the same job done, a personal assistant in your chat apps, and [Munder Difflin](https://harnessmd.com/download) if your real work is code. NanoClaw, ZeroClaw, nanobot, Goose and Claude cover tighter isolation, smaller installs and a hosted option. Checked 5 Oct 2026. -
Animation. Michael, Jim, Dwight, Kevin and Oscar walk on one by one and each says one line about choosing between seven OpenClaw alternatives.
Seven alternatives. Six are free and open source, one is hosted.
+
Animation. Seven named pills drop out of a hopper one at a time and a tilting flap sorts them. Six land in a tray marked free and open source, and the last one, Claude, lands in a tray marked hosted.
Seven alternatives. Six are free and open source, one is hosted.
This guide sits in our [Comparisons hub](/blog/topics/comparisons/), next to the wider list of [open source AI agents](/blog/open-source-ai-agents/). @@ -77,7 +77,7 @@ Licences come from each repo, checked on 5 Oct 2026. The Claude price is from [c Hermes Agent, for most people who liked what OpenClaw does. It does the same job under the same licence, and it is the only pick here whose README documents an import from `~/.openclaw`. -
Animation. A table compares OpenClaw and Hermes Agent on licence, GitHub stars, latest release, chat apps and switching, then Oscar gives the verdict.
OpenClaw is the bigger project. Hermes Agent is the easiest move.
+
Animation. Four parcels marked settings, memories, skills and API keys hop from an OpenClaw crate to a Hermes Agent crate when the command hermes claw migrate is pressed. Both crates carry an MIT tag.
OpenClaw is the bigger project. Hermes Agent is the easiest move.
OpenClaw still wins on size and reach: more stars, more channels, and native apps for macOS, iOS, Android, Windows and Linux. If none of the three reasons above bothers you, stay. That is a fair outcome for a list like this. @@ -85,7 +85,7 @@ OpenClaw still wins on size and reach: more stars, more channels, and native app Yes: six of the seven are free and open source, and so is OpenClaw. The software costs nothing. The model does, unless you run a local one or reuse a plan you already pay for. Goose, for example, can use an existing Claude, ChatGPT or Gemini subscription. -
Animation. Six price steps appear in a row, four flat at free and two rising to 20 and 100 dollars a month, while Kevin points at them.
Prices to start per month, with Claude figures from claude.com/pricing on 5 Oct 2026.
+
Animation. Eight price tags hang on two lines and flip over one by one. OpenClaw and six alternatives say Free, and the Claude tag says Pro, 20 dollars a month.
Prices to start per month, with Claude figures from claude.com/pricing on 5 Oct 2026.
Claude is the one pick here where the agent work starts on a paid plan: on 5 Oct 2026, Pro is $20 a month billed monthly, and Max is listed "From $100". @@ -93,7 +93,7 @@ Claude is the one pick here where the agent work starts on a paid plan: on 5 Oct Pick by the job first, then by whose computer does the work. -
Animation. Four boxes draw themselves in a row, asking what job, whose computer, how isolated and which model, while Pam points at each one.
Tap a box for the pick that goes with it.
+
Animation. Two switches flip, one for the job and one for whose computer, and a card pops out with the pick: Hermes Agent, then Munder Difflin, then Claude.
Tap the switches for the next pick.
- **Hermes Agent** if you want OpenClaw's job with a learning loop and an import command. - **Munder Difflin** if the work is code and you want several agents on it at once. @@ -105,4 +105,4 @@ Pick by the job first, then by whose computer does the work. For hosted agents from the other big vendors, see [ChatGPT dots alternatives](/blog/chatgpt-dots-alternatives/). - + 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 337da4a40..586938ff7 100644 --- a/docs/blog/agent-tools-today-2026-10-03/index.html +++ b/docs/blog/agent-tools-today-2026-10-03/index.html @@ -249,6 +249,22 @@

How this list is built +
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 c31140b9b..88496b9f5 100644 --- a/docs/blog/agent-tools-today-2026-10-04/index.html +++ b/docs/blog/agent-tools-today-2026-10-04/index.html @@ -248,6 +248,22 @@

How this list is built +
diff --git a/docs/blog/agent-tools-today-2026-10-06/index.html b/docs/blog/agent-tools-today-2026-10-06/index.html new file mode 100644 index 000000000..558c7b99c --- /dev/null +++ b/docs/blog/agent-tools-today-2026-10-06/index.html @@ -0,0 +1,339 @@ + + + + + + + + +Agent Tools Today (6 Oct 2026): Launches, Skills, MCP Servers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +

Agent Tools Today, 6 Oct 2026: OpenAI Text Watermarks, Reflection Beam, Cloudflare Web Search

+

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

+ +
+ +
Hand drawn sketch: a front page with five numbered lines, a clock, and a yellow character reading it out
+ +
+
+

Agent tools today, 6 Oct 2026, in one line: the question was where things come from. OpenAI started watermarking text, Wikimedia said it found agent activity on its sites that it believes is OpenAI’s, and Reflection announced an open weight model you cannot download yet. Every number was read on 6 Oct. The previous edition is here.

+
Animation. Five unmarked parcels drop from a box with a question mark onto a conveyor, get stamped with a number, and stack into a tower. Each one gets a name tag: OpenAI text watermarking, Beam, Cloudflare Web Search API, Claude Code v2.1.290 and Codex CLI 0.160.1.
The five launches of the day, tagged as they arrive. Tap a parcel for its one line.
+

Top 5 launches

+
    +
  1. OpenAI text watermarking. On 5 Oct OpenAI said API customers worldwide can opt in to text watermarking for select models, and that eligible ChatGPT and Codex output in the EU gets an invisible watermark over the coming weeks. The detector is limited to approved researchers and expert organizations. More in our explainer. Source
  2. +
  3. Beam. Reflection AI announced its first open weight model on 5 Oct: 501 billion total parameters, 23 billion active, built for coding, reasoning and agentic work. The weights are not out: the post says later this month, with a sign up for early access. 347 points on Hacker News. Source
  4. +
  5. Cloudflare Web Search API. In beta, per a changelog entry dated 2 Oct. Agents search the web through AI Gateway, with Ceramic.ai, Exa or Linkup as the provider. 498 points on Hacker News. Source
  6. +
  7. Claude Code v2.1.290. Released 5 Oct. It adds claude attach <name> and claude logs <name>, and gives plugin hooks an agentId so a hook can tell a subagent’s permission check from the main session’s. Source
  8. +
  9. Codex CLI 0.160.1. A patch on 5 Oct with one fix: remote stdio MCP servers keep SYSTEMROOT, TEMP and TMP when launched with explicitly configured remote environment variables. Background: what is Codex. Source
  10. +
+

Top 4 skills, MCP servers and plugins

+
    +
  1. Plannotator 0.28.0. Released 5 Oct. A question you ask in Ask AI now goes to the Claude Code, Pi or OpenCode 2 session that opened Plannotator. On Claude Code 2.1.287 and later, in the interactive terminal, the plugin runs as a mod, on by default. Apache 2.0, 9,156 stars on 6 Oct. Source
  2. +
  3. cinetic. An agent skill that makes your coding agent direct a short film from code, built with Remotion by default or HyperFrames. Version 1.1.0 landed on 4 Oct. Install with npx skills add Leonxlnx/cinetic. MIT, 81 stars on 6 Oct. Source
  4. +
  5. Agent Memory Repo. An open spec that treats agent memory as a git repo: clone, grep, update, commit. Created 4 Oct, MIT, 266 stars on 6 Oct. Related: long term memory for Claude Code. Source
  6. +
  7. dsh-plugin-upgrade-skill. Community skills that help an agent migrate DeepSeek Harness plugins when a new version breaks them: 11 skills, 195 upgrade cards and 63 benchmark tasks. MIT, 222 stars on 6 Oct. Source
  8. +
+

Top 5 moves from multi agent tools

+
    +
  1. Orca. Release v1.4.221 on 5 Oct carries a security fix. When Orca marked a folder as trusted for GitHub Copilot, it could leave ~/.copilot/config.json, which can hold login tokens, readable by other users on the same machine. The file is now set to owner only when Orca writes to it, so a file an older version left open is fixed the next time Orca adds a trusted folder. Source
  2. +
  3. Munder Difflin. Free and open source: no new release today. The latest is v0.5.5, from 1 Oct. Source
  4. +
  5. Pi. Two more patch releases on 5 Oct. In v1.0.3 the azure provider also serves Foundry Chat Completions deployments. v1.0.4 adds * patterns to --tools and a --no-mcp flag that turns MCP off for one run. Background: what is Pi agent. Source
  6. +
  7. Herdr GPUI. Release v20261005.1 on 5 Oct: fan out one prompt to several agents, send line notes on an agent’s uncommitted changes back to it, and checkpoint agent turns. Apache 2.0, 904 stars on 6 Oct. Source
  8. +
  9. OpenClaw. A prerelease, 2026.10.1-beta.1, on 5 Oct, with fixes to sessions, memory, replies and media. The latest stable release is still v2026.9.8. See OpenClaw alternatives. Source
  10. +
+

Top 5 rising repos

+
    +
  1. RemoveMacAI. Turns off Apple Intelligence on macOS 27, removes its downloaded models and stops macOS downloading them again. Created 29 Sep, v0.2.5 on 5 Oct. MIT, 2,174 stars on 6 Oct, 758 points on Hacker News. Source
  2. +
  3. framefields. Code first video rendered on WebGPU, in one npm package that coding agents drive with TypeScript. It reached Hacker News as Gitframes (56 points) before the rename. Beta, Apache 2.0, 211 stars on 6 Oct. Source
  4. +
  5. squeez. A hook based token compressor for seven AI CLI hosts, Claude Code and Codex CLI among them. Its README claims up to 95% compression of bash output. v1.48.15 on 5 Oct. Apache 2.0, 213 stars on 6 Oct. Source
  6. +
  7. Minigraf. An embedded graph database, pitched as graph memory for AI agents, with Datalog queries and bi-temporal history. 114 stars on 6 Oct, 31 points on Hacker News. Source
  8. +
  9. mold 3.0.0. Not an agent tool. The linker was rewritten from C++ to Rust, and 3.0.0 on 5 Oct is the first release of the Rust version. MIT, 17,437 stars on 6 Oct, 233 points on Hacker News. Source
  10. +
+

New repos can gain stars quickly for reasons other than use, so treat these as names to watch, not recommendations.

+

Top 3 things people are arguing about

+
    +
  1. Whose agents are on your site? The Wikimedia Foundation said on 5 Oct it found activity it believes came from agents operated by OpenAI: edits that were almost all in sandbox areas, failed attempts to misuse its Etherpad, and millions of automated API requests. It found no evidence that its systems or data were compromised. 262 points on Hacker News. Source
  2. +
  3. How far does a text watermark go? OpenAI’s own figures, as reported by Unite.AI: on 400 token passages, replacing 10% of words with synonyms cut detection from about 92% to 66%, and replacing 25% cut it to 17%. Source
  4. +
  5. Capability or efficiency? Reflection’s own post says Kimi K3 remains ahead on raw capability and that Beam’s advantage is efficiency at inference time. The Hacker News thread had 105 comments. Source
  6. +
+

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? The install guide is the place to start.

+ + + + + + +
+ + +
+ + + + +
+ + +
+ +
+ + + + + + diff --git a/docs/blog/agents-rule-of-two/index.html b/docs/blog/agents-rule-of-two/index.html index c97d740cf..154b924c7 100644 --- a/docs/blog/agents-rule-of-two/index.html +++ b/docs/blog/agents-rule-of-two/index.html @@ -331,50 +331,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/ai-agent-glossary/index.html b/docs/blog/ai-agent-glossary/index.html index fde3cfbee..82345aac5 100644 --- a/docs/blog/ai-agent-glossary/index.html +++ b/docs/blog/ai-agent-glossary/index.html @@ -303,50 +303,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/aider-vs-claude-code/index.html b/docs/blog/aider-vs-claude-code/index.html index e3d4ffe65..bd4353e06 100644 --- a/docs/blog/aider-vs-claude-code/index.html +++ b/docs/blog/aider-vs-claude-code/index.html @@ -296,7 +296,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -312,7 +312,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/asd-ste100-prompting/index.html b/docs/blog/asd-ste100-prompting/index.html index 6d9b4d418..b3929f369 100644 --- a/docs/blog/asd-ste100-prompting/index.html +++ b/docs/blog/asd-ste100-prompting/index.html @@ -359,7 +359,7 @@

FAQ

-
+ + + -
+ +

+ + + + +
- - - - - - -
-
-
+
diff --git a/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html b/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html index a472ac517..a50dd7b9e 100644 --- a/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html +++ b/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html @@ -633,7 +633,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -649,7 +649,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/is-claude-code-max-worth-it/index.html b/docs/blog/is-claude-code-max-worth-it/index.html index 4a22bea17..438faf58d 100644 --- a/docs/blog/is-claude-code-max-worth-it/index.html +++ b/docs/blog/is-claude-code-max-worth-it/index.html @@ -411,50 +411,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/is-claude-code-open-source/index.html b/docs/blog/is-claude-code-open-source/index.html index 9a51d55af..b76384fa1 100644 --- a/docs/blog/is-claude-code-open-source/index.html +++ b/docs/blog/is-claude-code-open-source/index.html @@ -292,50 +292,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/is-ollama-good-for-coding/index.html b/docs/blog/is-ollama-good-for-coding/index.html index 057a88a0a..90b25e035 100644 --- a/docs/blog/is-ollama-good-for-coding/index.html +++ b/docs/blog/is-ollama-good-for-coding/index.html @@ -332,50 +332,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/local-first-vs-cloud-agent-sdks/index.html b/docs/blog/local-first-vs-cloud-agent-sdks/index.html index d5880283f..c47f63ef5 100644 --- a/docs/blog/local-first-vs-cloud-agent-sdks/index.html +++ b/docs/blog/local-first-vs-cloud-agent-sdks/index.html @@ -373,7 +373,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -389,7 +389,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/long-running-agents-the-2026-shift/index.html b/docs/blog/long-running-agents-the-2026-shift/index.html index 35bc9cb55..92a476a92 100644 --- a/docs/blog/long-running-agents-the-2026-shift/index.html +++ b/docs/blog/long-running-agents-the-2026-shift/index.html @@ -350,50 +350,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/loop-engineering-for-ai-agents/index.html b/docs/blog/loop-engineering-for-ai-agents/index.html index d8ebba8b2..8ba83321c 100644 --- a/docs/blog/loop-engineering-for-ai-agents/index.html +++ b/docs/blog/loop-engineering-for-ai-agents/index.html @@ -291,50 +291,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/meta-muse-alternatives/index.html b/docs/blog/meta-muse-alternatives/index.html index b30e4d916..ef1c759d6 100644 --- a/docs/blog/meta-muse-alternatives/index.html +++ b/docs/blog/meta-muse-alternatives/index.html @@ -350,7 +350,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -366,7 +366,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/meta-muse-grok-bot-privacy/index.html b/docs/blog/meta-muse-grok-bot-privacy/index.html index 6329f4527..2cdd71ea1 100644 --- a/docs/blog/meta-muse-grok-bot-privacy/index.html +++ b/docs/blog/meta-muse-grok-bot-privacy/index.html @@ -337,7 +337,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -353,7 +353,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/meta-muse-vs-grok-bot/index.html b/docs/blog/meta-muse-vs-grok-bot/index.html index 6d4eedabe..b05291e2c 100644 --- a/docs/blog/meta-muse-vs-grok-bot/index.html +++ b/docs/blog/meta-muse-vs-grok-bot/index.html @@ -341,7 +341,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -357,7 +357,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/munder-difflin-faq/index.html b/docs/blog/munder-difflin-faq/index.html index 6934e438a..bc283fc89 100644 --- a/docs/blog/munder-difflin-faq/index.html +++ b/docs/blog/munder-difflin-faq/index.html @@ -320,50 +320,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/munder-difflin-vs-chatgpt-dots/index.html b/docs/blog/munder-difflin-vs-chatgpt-dots/index.html index 79749ab0b..5fb689aaf 100644 --- a/docs/blog/munder-difflin-vs-chatgpt-dots/index.html +++ b/docs/blog/munder-difflin-vs-chatgpt-dots/index.html @@ -363,7 +363,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -379,7 +379,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/omniagent-alternatives/index.html b/docs/blog/omniagent-alternatives/index.html index 759990523..8ef872e22 100644 --- a/docs/blog/omniagent-alternatives/index.html +++ b/docs/blog/omniagent-alternatives/index.html @@ -312,7 +312,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -328,7 +328,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/open-source-ai-agents/index.html b/docs/blog/open-source-ai-agents/index.html index db5750c59..3760afdd7 100644 --- a/docs/blog/open-source-ai-agents/index.html +++ b/docs/blog/open-source-ai-agents/index.html @@ -379,7 +379,7 @@

Related in Comparisons

- 6 min + 5 min

Claude Code Alternatives: 7 Coding Agents to Switch To

Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

@@ -395,7 +395,7 @@

Claude Code Alternatives: 7 Coding
- 6 min + 5 min

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

diff --git a/docs/blog/open-source-ai-tools-on-purpose/index.html b/docs/blog/open-source-ai-tools-on-purpose/index.html index 7d67681f5..601e40f26 100644 --- a/docs/blog/open-source-ai-tools-on-purpose/index.html +++ b/docs/blog/open-source-ai-tools-on-purpose/index.html @@ -322,50 +322,50 @@

FAQ

Related in Concepts

diff --git a/docs/blog/openai-text-watermarking/index.html b/docs/blog/openai-text-watermarking/index.html new file mode 100644 index 000000000..14419b227 --- /dev/null +++ b/docs/blog/openai-text-watermarking/index.html @@ -0,0 +1,480 @@ + + + + + + + + +ChatGPT Watermark: OpenAI Text Watermarking (textGrain) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +

OpenAI text watermarking: textGrain, who gets it, limits

+

OpenAI text watermarking (textGrain) is opt in for the API now and is coming to eligible ChatGPT and Codex text in the EU. Limits checked 6 Oct 2026.

+ +
+ +
Title card on white. The words OpenAI text watermarking above two rows of grey word shapes, where a frame hanging from a large key has turned some of the words yellow in a pattern
+ +
+
+

OpenAI text watermarking is textGrain, an invisible signal in the model’s word choices. OpenAI announced it on 5 Oct 2026: opt in for API customers worldwide, and on for eligible ChatGPT and Codex text output in the EU over the coming weeks. Our view: a compliance signal, not proof of who wrote a text. Checked 6 Oct 2026.

+

Codex is one of the coding agents people run inside Munder Difflin, which 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. So this change reaches some of our readers directly. The Concepts hub has more explainers like this one.

+

What is OpenAI text watermarking?

+

It is a way to mark text from OpenAI models so software can spot it later. OpenAI’s announcement says the EU AI Act requires generative AI providers to make generated text identifiable in a machine-readable way. textGrain is OpenAI’s answer for text.

+

The watermark is not something you can see. OpenAI’s help page says it does not add hidden characters, invisible spaces or unusual punctuation. It is part of the wording itself.

+

It is also not an AI detector of the usual kind. The same page says third party tools typically use classifiers that analyze text after it is written, while the EU AI Act requires a signal embedded in the generated text. OpenAI says it plans to release textGrain as open source.

+
Animation. A paragraph of plain grey word shapes is scanned by a frame that carries a key, and some of the words light up yellow in a pattern. Three labels below say it does not add hidden characters, invisible spaces or unusual punctuation.
The signal is in the word choices, not in extra characters. From OpenAI's help page, checked 6 Oct 2026.
+

Who gets the watermark, and when?

+

Eligible ChatGPT and Codex users in the EU get it over the coming weeks, and API customers anywhere can opt in for select models. These scopes come from OpenAI’s announcement. Unite.AI dates it 5 October 2026:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
WhereWhat happensWhen
ChatGPT and Codex in the EUWatermark added to eligible text output, across all plansOver the coming weeks
ChatGPT and Codex outside the EUNot included. OpenAI says the rollout is EU only and not a global default at launchNo date given
OpenAI API, worldwideOpt in, for select models. Off by defaultFrom 5 Oct 2026
Text watermark detectorApplications open for approved researchers and expert organizationsFrom 5 Oct 2026
+
+

The EU rules behind this already apply. The European Commission’s Code of Practice page says the Article 50 transparency obligations apply from 2 August 2026. Signing the code is voluntary. The obligations are not.

+
Animation. Three hanging lamps. The OpenAI API lamp has a switch marked opt in, the lamp for ChatGPT and Codex in the EU runs on a timer marked over the coming weeks, and the lamp for ChatGPT and Codex outside the EU has no switch and is marked not included.
Who gets the watermark, from OpenAI's announcement. Flip the API switch. Checked 6 Oct 2026.
+

How does textGrain work?

+

It nudges the model’s random word choices with a secret key, and a detector with the same key looks for the pattern. A model writes one token at a time, a token being a word or a piece of one. OpenAI’s help page says the system builds several adjusted sets of likelihoods for the next token, and the key selects which set to use. Averaged together, the sets match the model’s original likelihoods.

+

The technical report, dated 5 October 2026, says the detector needs the generated text and the secret key. Our reading: without that key, a third party site cannot run this check, and OpenAI has not said it shares the key.

+

How reliable is the detection?

+

Stronger on long, loosely worded text, and much weaker on short, exact or edited text. All figures below are OpenAI’s own, from the announcement and the help page. The length rows and the language rows are separate tests, both at a 1% false positive rate.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TestDetection rate
200 token passages, content such as psychologyAbout 80%
400 token passages, content such as psychologyAbout 95%
Content such as mathematics“Substantially lower”, no figure given in the text
400 token passages, uneditedAbout 92%
Same passages, 10% of words replaced with synonyms66%
Same passages, 25% of words replaced17%
Spanish, the highest baseline of the 24 official EU languages69.0%
Romanian, the lowest baseline, before OpenAI raised the watermark strength42.2%
+
+

OpenAI says it raised the watermark strength for languages under 60%, and gives no figures for the result in the text.

+

So can you remove a ChatGPT watermark? OpenAI’s numbers say replacing one word in ten with a synonym already weakens it a lot, and the help page says substantial paraphrasing or translation can make it undetectable. OpenAI says these limits are part of why the detector is not public at launch.

+
Animation. A dial needle swings to about 80% for 200 token passages and about 95% for 400 token passages. In a second test it falls from about 92% unedited to 66% with 10% of words replaced and 17% with 25% replaced.
OpenAI's own detection figures. Drag the slider to replace words. Checked 6 Oct 2026.
+

What does a watermark not prove?

+

It does not prove who wrote a text, or that a person did not. OpenAI’s announcement lists five limits:

+
    +
  • Not human effort. It can indicate an OpenAI system generated or processed part of a passage, not how much editing or judgment a person added.
  • +
  • Not ownership. It says nothing about who owns the text or who is responsible for it.
  • +
  • Not identity. It does not link a person, account, prompt or conversation to the text.
  • +
  • Not accuracy. It does not tell you whether the passage is true.
  • +
  • No watermark is not proof of a human. The text may be too short, edited, translated, from an unsupported model, older than the watermark, or from another company’s tools.
  • +
+
Animation. A stamp marked proof slides along a rail and bounces off five tags without leaving a mark: not human effort, not ownership, not identity, not accuracy, and no watermark is not proof of a human.
The five limits OpenAI lists for its text watermark. Checked 6 Oct 2026.
+

Does it affect Codex and code?

+

Eligible Codex text output in the EU is in scope, and OpenAI does not spell out what happens to code. The announcement covers “eligible ChatGPT and Codex text output” and does not define eligible. The help page says code is harder to watermark, because there are fewer plausible choices for what comes next. It adds that the EU Code of Practice does not require watermarks in code snippets, or in outputs shorter than 200 tokens, about 150 words in English. That page names ChatGPT alone for the EU watermark, so we go by the announcement.

+

If Codex is new to you, start with what Codex is and how to install the Codex CLI.

+

How do you turn it on in the API?

+

You switch it on in the platform settings, for one project or for the whole organization. OpenAI’s help page gives two places:

+
    +
  1. Whole organization: Organization settings, then Data controls, then Text provenance.
  2. +
  3. One project: Project Settings, then Text provenance.
  4. +
+

Turn on Allow text watermarking, select your models, then select Save. Switching it on does not give you the detector.

+

What should you do now?

+

Very little, unless you build on the API.

+
    +
  • In the EU, on ChatGPT or Codex: nothing to set up. The pages we opened describe no user switch for it.
  • +
  • Building on the API: it stays off unless you choose it. OpenAI’s help page says it cannot advise on your legal obligations, so ask your own legal team.
  • +
  • Judging someone’s text: do not treat a missing watermark as proof of anything.
  • +
  • Choosing a coding agent: our view is that this should not decide it. Codex CLI vs Claude Code and our Codex Max plan tips cover what does.
  • +
+ + + + +
+

FAQ

+ +
+

Does ChatGPT watermark text outside the EU?

+

Not by default. OpenAI's 5 Oct 2026 announcement says the ChatGPT and Codex rollout is for the EU only, and that it is not making text watermarking a global default at launch. Outside the EU, text is marked when an API customer has switched watermarking on for a supported model.

+
+ +
+

Can you remove a ChatGPT watermark?

+

OpenAI does not claim the watermark is permanent. In its test of 400 token passages, replacing 10% of words with synonyms cut detection from about 92% to 66%, and replacing 25% cut it to 17%. OpenAI's help page adds that substantial paraphrasing or translation can make the watermark undetectable. Light edits and copy and paste are what it is designed to survive.

+
+ +
+

Is there a public OpenAI watermark detector for text?

+

No. OpenAI says the text detector is limited to approved researchers and expert organizations, reviewed case by case, because of the risk of missed watermarks and false positives. Its openai.com/verify tool checks images and audio, not text.

+
+ +
+

Does the ChatGPT watermark add hidden characters?

+

No. OpenAI's help page says textGrain does not add hidden characters, invisible spaces or unusual punctuation. The signal is in the pattern of word choices, so copying and pasting adds no hidden material. The watermark is part of the wording, so OpenAI expects it to remain when the wording is kept.

+
+ +
+

Does text watermarking make OpenAI models worse or slower?

+

OpenAI says no. Its published benchmark table for its Astra model shows moves in both directions, which OpenAI says fall within normal run to run noise, for example 72.80% without the watermark and 71.68% with it on DeepSWE v1.1. The help page calls the speed impact negligible. These are OpenAI's own tests, checked 6 Oct 2026.

+
+ +
+ + + +
+ + +
+ + + + +
+ + +
+ +
+ + + + + + diff --git a/docs/blog/openclaw-alternatives/index.html b/docs/blog/openclaw-alternatives/index.html index dc9dd156a..1cbf8cd14 100644 --- a/docs/blog/openclaw-alternatives/index.html +++ b/docs/blog/openclaw-alternatives/index.html @@ -174,17 +174,17 @@

OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

- 6 min read + 5 min read Comparisons
-
Title card. The words OpenClaw alternatives: 7 picks, with Michael and Jim from the office cast standing beside them
+
Title card on white. The words OpenClaw alternatives, 7 picks for chat, code and cloud, above six coloured name pills, with a Claude pill dropping from a hopper onto a yellow flap

The best OpenClaw alternative is Hermes Agent if you want the same job done, a personal assistant in your chat apps, and Munder Difflin if your real work is code. NanoClaw, ZeroClaw, nanobot, Goose and Claude cover tighter isolation, smaller installs and a hosted option. Checked 5 Oct 2026.

-
Animation. Michael, Jim, Dwight, Kevin and Oscar walk on one by one and each says one line about choosing between seven OpenClaw alternatives.
Seven alternatives. Six are free and open source, one is hosted.
+
Animation. Seven named pills drop out of a hopper one at a time and a tilting flap sorts them. Six land in a tray marked free and open source, and the last one, Claude, lands in a tray marked hosted.
Seven alternatives. Six are free and open source, one is hosted.

This guide sits in our Comparisons hub, next to the wider list of open source AI agents.

What is OpenClaw, and why would you leave it?

OpenClaw is an open source AI assistant that runs on your own computer and answers in the chat apps you already use. Its README names Discord, iMessage, Slack, Teams, Telegram and WhatsApp, “and 20+ more”. It is MIT licensed, run by the OpenClaw Foundation, and has “no paid tier, hosted service, or token”. GitHub showed 391,425 stars and release v2026.9.8 (3 Oct 2026) on 5 Oct 2026.

@@ -278,15 +278,15 @@

OpenCla

Licences come from each repo, checked on 5 Oct 2026. The Claude price is from claude.com/pricing on the same day.

What is the best OpenClaw alternative?

Hermes Agent, for most people who liked what OpenClaw does. It does the same job under the same licence, and it is the only pick here whose README documents an import from ~/.openclaw.

-
Animation. A table compares OpenClaw and Hermes Agent on licence, GitHub stars, latest release, chat apps and switching, then Oscar gives the verdict.
OpenClaw is the bigger project. Hermes Agent is the easiest move.
+
Animation. Four parcels marked settings, memories, skills and API keys hop from an OpenClaw crate to a Hermes Agent crate when the command hermes claw migrate is pressed. Both crates carry an MIT tag.
OpenClaw is the bigger project. Hermes Agent is the easiest move.

OpenClaw still wins on size and reach: more stars, more channels, and native apps for macOS, iOS, Android, Windows and Linux. If none of the three reasons above bothers you, stay. That is a fair outcome for a list like this.

Is there a free OpenClaw alternative?

Yes: six of the seven are free and open source, and so is OpenClaw. The software costs nothing. The model does, unless you run a local one or reuse a plan you already pay for. Goose, for example, can use an existing Claude, ChatGPT or Gemini subscription.

-
Animation. Six price steps appear in a row, four flat at free and two rising to 20 and 100 dollars a month, while Kevin points at them.
Prices to start per month, with Claude figures from claude.com/pricing on 5 Oct 2026.
+
Animation. Eight price tags hang on two lines and flip over one by one. OpenClaw and six alternatives say Free, and the Claude tag says Pro, 20 dollars a month.
Prices to start per month, with Claude figures from claude.com/pricing on 5 Oct 2026.

Claude is the one pick here where the agent work starts on a paid plan: on 5 Oct 2026, Pro is $20 a month billed monthly, and Max is listed “From $100”.

Who should pick what?

Pick by the job first, then by whose computer does the work.

-
Animation. Four boxes draw themselves in a row, asking what job, whose computer, how isolated and which model, while Pam points at each one.
Tap a box for the pick that goes with it.
+
Animation. Two switches flip, one for the job and one for whose computer, and a card pops out with the pick: Hermes Agent, then Munder Difflin, then Claude.
Tap the switches for the next pick.
  • Hermes Agent if you want OpenClaw’s job with a learning loop and an import command.
  • Munder Difflin if the work is code and you want several agents on it at once.
  • @@ -297,7 +297,7 @@

    Who should pick what? ChatGPT dots alternatives.

    - + @@ -364,7 +364,7 @@

    Related in Comparisons

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    diff --git a/docs/blog/orca-vs-munder-difflin/index.html b/docs/blog/orca-vs-munder-difflin/index.html index 23918fcd4..eb936b4df 100644 --- a/docs/blog/orca-vs-munder-difflin/index.html +++ b/docs/blog/orca-vs-munder-difflin/index.html @@ -351,7 +351,7 @@

    Related in Comparisons

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    @@ -367,7 +367,7 @@

    Claude Code Alternatives: 7 Coding
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/qm-vs-munder-difflin/index.html b/docs/blog/qm-vs-munder-difflin/index.html index bfa18ed89..831b332f7 100644 --- a/docs/blog/qm-vs-munder-difflin/index.html +++ b/docs/blog/qm-vs-munder-difflin/index.html @@ -362,7 +362,7 @@

    Related in Comparisons

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    @@ -378,7 +378,7 @@

    Claude Code Alternatives: 7 Coding
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/review-agent-work-where-it-happens/index.html b/docs/blog/review-agent-work-where-it-happens/index.html index bd608e3ac..ebec5db6a 100644 --- a/docs/blog/review-agent-work-where-it-happens/index.html +++ b/docs/blog/review-agent-work-where-it-happens/index.html @@ -282,50 +282,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/shareable-agent-roles/index.html b/docs/blog/shareable-agent-roles/index.html index 8bcadff6c..bc1b25b04 100644 --- a/docs/blog/shareable-agent-roles/index.html +++ b/docs/blog/shareable-agent-roles/index.html @@ -324,50 +324,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/tags/ai-agents/index.html b/docs/blog/tags/ai-agents/index.html index 2315cfd0f..848a91733 100644 --- a/docs/blog/tags/ai-agents/index.html +++ b/docs/blog/tags/ai-agents/index.html @@ -131,7 +131,7 @@

    ASD-STE100 Prompting: Karpathy's 4
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/tags/claude-code/index.html b/docs/blog/tags/claude-code/index.html index c02ff0648..17cb66596 100644 --- a/docs/blog/tags/claude-code/index.html +++ b/docs/blog/tags/claude-code/index.html @@ -105,11 +105,27 @@

    Tag

    #Claude Code

    -

    67 posts tagged Claude Code.

    +

    68 posts tagged Claude Code.

    -
    + +
    @@ -131,7 +147,7 @@

    ASD-STE100 Prompting: Karpathy's 4
    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    diff --git a/docs/blog/tags/cli-agents/index.html b/docs/blog/tags/cli-agents/index.html index 9246a9078..354b43946 100644 --- a/docs/blog/tags/cli-agents/index.html +++ b/docs/blog/tags/cli-agents/index.html @@ -115,7 +115,7 @@

    #CLI Agents

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    diff --git a/docs/blog/tags/codex/index.html b/docs/blog/tags/codex/index.html index df82e3bda..a6498cf0b 100644 --- a/docs/blog/tags/codex/index.html +++ b/docs/blog/tags/codex/index.html @@ -105,11 +105,43 @@

    Tag

    #Codex

    -

    7 posts tagged Codex.

    +

    9 posts tagged Codex.

    + +
    diff --git a/docs/blog/tags/comparisons/index.html b/docs/blog/tags/comparisons/index.html index f66efd239..8a55f0e99 100644 --- a/docs/blog/tags/comparisons/index.html +++ b/docs/blog/tags/comparisons/index.html @@ -115,7 +115,7 @@

    #Comparisons

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    @@ -131,7 +131,7 @@

    Claude Code Alternatives: 7 Coding
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/tags/concepts/index.html b/docs/blog/tags/concepts/index.html index c9c14e55a..6d27345f2 100644 --- a/docs/blog/tags/concepts/index.html +++ b/docs/blog/tags/concepts/index.html @@ -105,11 +105,27 @@

    Tag

    #Concepts

    -

    31 posts tagged Concepts.

    +

    32 posts tagged Concepts.

    +
    diff --git a/docs/blog/tags/cost/index.html b/docs/blog/tags/cost/index.html index 487f2d89d..027048fb9 100644 --- a/docs/blog/tags/cost/index.html +++ b/docs/blog/tags/cost/index.html @@ -115,7 +115,7 @@

    #Cost

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    diff --git a/docs/blog/tags/daily-brief/index.html b/docs/blog/tags/daily-brief/index.html index 25eb01958..85066111c 100644 --- a/docs/blog/tags/daily-brief/index.html +++ b/docs/blog/tags/daily-brief/index.html @@ -105,11 +105,27 @@

    Tag

    #Daily Brief

    -

    2 posts tagged Daily Brief.

    +

    3 posts tagged Daily Brief.

    +
    diff --git a/docs/blog/tags/local-first/index.html b/docs/blog/tags/local-first/index.html index 19b65d582..94875cee4 100644 --- a/docs/blog/tags/local-first/index.html +++ b/docs/blog/tags/local-first/index.html @@ -115,7 +115,7 @@

    #Local-First

    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/tags/open-source/index.html b/docs/blog/tags/open-source/index.html index 93d34b857..9a4202baf 100644 --- a/docs/blog/tags/open-source/index.html +++ b/docs/blog/tags/open-source/index.html @@ -105,17 +105,33 @@

    Tag

    #Open Source

    -

    46 posts tagged Open Source.

    +

    47 posts tagged Open Source.

    -
    + +
    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    @@ -131,7 +147,7 @@

    Claude Code Alternatives: 7 Coding
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/tags/openai/index.html b/docs/blog/tags/openai/index.html new file mode 100644 index 000000000..60787d9d7 --- /dev/null +++ b/docs/blog/tags/openai/index.html @@ -0,0 +1,184 @@ + + + + + + + + +OpenAI — Munder Difflin Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +

    Tag

    +

    #OpenAI

    +

    1 post tagged OpenAI.

    +
    +
    +
    + + +
    +
    + + +
    + + + + + + diff --git a/docs/blog/tags/skills/index.html b/docs/blog/tags/skills/index.html index 7582e23d3..96761e849 100644 --- a/docs/blog/tags/skills/index.html +++ b/docs/blog/tags/skills/index.html @@ -105,11 +105,27 @@

    Tag

    #Skills

    -

    4 posts tagged Skills.

    +

    5 posts tagged Skills.

    +
    diff --git a/docs/blog/the-clone-army-trap-mixed-swarm-vs-identical-agents/index.html b/docs/blog/the-clone-army-trap-mixed-swarm-vs-identical-agents/index.html index ce1dc0dbd..358b5ed30 100644 --- a/docs/blog/the-clone-army-trap-mixed-swarm-vs-identical-agents/index.html +++ b/docs/blog/the-clone-army-trap-mixed-swarm-vs-identical-agents/index.html @@ -358,50 +358,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html b/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html index 2decce96d..b505349f6 100644 --- a/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html +++ b/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html @@ -288,7 +288,7 @@

    Related in Comparisons

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    @@ -304,7 +304,7 @@

    Claude Code Alternatives: 7 Coding
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/topics/comparisons/index.html b/docs/blog/topics/comparisons/index.html index 44ef797e7..e4136a5a1 100644 --- a/docs/blog/topics/comparisons/index.html +++ b/docs/blog/topics/comparisons/index.html @@ -116,7 +116,7 @@

    comparisons

    Honest side-by-sides with the other ways to guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    comparisons

    Honest side-by-sides with the other ways to memory5 - news2 + news3

    @@ -191,7 +191,7 @@

    OmniAgent Alternatives: What to Use
    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    @@ -207,7 +207,7 @@

    Claude Code Alternatives: 7 Coding
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/topics/concepts/index.html b/docs/blog/topics/concepts/index.html index 99684503a..bec784aec 100644 --- a/docs/blog/topics/concepts/index.html +++ b/docs/blog/topics/concepts/index.html @@ -116,7 +116,7 @@

    concepts

    The ideas underneath the product — harnesses, guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    concepts

    The ideas underneath the product — harnesses, memory5 - news2 + news3

    @@ -138,6 +138,22 @@

    concepts

    The ideas underneath the product — harnesses,

    +
    diff --git a/docs/blog/topics/guides/index.html b/docs/blog/topics/guides/index.html index ca04a550b..dc32218b9 100644 --- a/docs/blog/topics/guides/index.html +++ b/docs/blog/topics/guides/index.html @@ -116,7 +116,7 @@

    guides

    Step-by-step walkthroughs you can follow today guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    guides

    Step-by-step walkthroughs you can follow today memory5 - news2 + news3

    diff --git a/docs/blog/topics/index.html b/docs/blog/topics/index.html index 979e84343..ec91c2ba4 100644 --- a/docs/blog/topics/index.html +++ b/docs/blog/topics/index.html @@ -126,7 +126,7 @@

    guides

    concepts

    -

    34 posts.

    +

    35 posts.

    View topic →
    @@ -189,7 +189,7 @@

    memory

    news

    -

    2 posts.

    +

    3 posts.

    View topic →
    @@ -202,15 +202,15 @@

    news

    Multi-Agent88 - Claude Code67 + Claude Code68 - Open Source46 + Open Source47 Guides35 Comparisons34 - Concepts31 + Concepts32 Internals26 @@ -244,12 +244,12 @@

    news

    Human-in-the-Loop10 + Codex9 + Workflow8 MCP7 - Codex7 - MemPalace6 Guardrails6 @@ -262,14 +262,14 @@

    news

    Electron5 + Skills5 + Autonomous5 Voice5 Git4 - Skills4 - Architecture4 Reliability4 @@ -292,6 +292,8 @@

    news

    Launch3 + Daily Brief3 + GOD2 Messaging2 @@ -332,8 +334,6 @@

    news

    Pricing2 - Daily Brief2 - Subagents1 Markdown1 @@ -476,6 +476,8 @@

    news

    Transcription1 + OpenAI1 +
    diff --git a/docs/blog/topics/internals/index.html b/docs/blog/topics/internals/index.html index f213ce12c..7ad94fba4 100644 --- a/docs/blog/topics/internals/index.html +++ b/docs/blog/topics/internals/index.html @@ -116,7 +116,7 @@

    internals

    How the machine actually works: postmortems, a guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    internals

    How the machine actually works: postmortems, a memory5 - news2 + news3

    diff --git a/docs/blog/topics/memory/index.html b/docs/blog/topics/memory/index.html index bb26429f1..2bb2413b0 100644 --- a/docs/blog/topics/memory/index.html +++ b/docs/blog/topics/memory/index.html @@ -116,7 +116,7 @@

    memory

    How agents remember across sessions — and how a guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    memory

    How agents remember across sessions — and how a memory5 - news2 + news3

    diff --git a/docs/blog/topics/news/index.html b/docs/blog/topics/news/index.html index 9fda38826..f3f22c8b8 100644 --- a/docs/blog/topics/news/index.html +++ b/docs/blog/topics/news/index.html @@ -116,7 +116,7 @@

    news

    Agent Tools Today: a daily brief of the top five la guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    news

    Agent Tools Today: a daily brief of the top five la memory5 - news2 + news3

    @@ -138,6 +138,22 @@

    news

    Agent Tools Today: a daily brief of the top five la

    +
    diff --git a/docs/blog/topics/orchestration/index.html b/docs/blog/topics/orchestration/index.html index cfb44d4f1..c8ca4f80f 100644 --- a/docs/blog/topics/orchestration/index.html +++ b/docs/blog/topics/orchestration/index.html @@ -116,7 +116,7 @@

    orchestration

    Patterns for making one boss agent run a f guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    orchestration

    Patterns for making one boss agent run a f memory5 - news2 + news3

    diff --git a/docs/blog/topics/story/index.html b/docs/blog/topics/story/index.html index 3388637e3..6d473e115 100644 --- a/docs/blog/topics/story/index.html +++ b/docs/blog/topics/story/index.html @@ -116,7 +116,7 @@

    story

    Launches, releases, and the occasional confession guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    story

    Launches, releases, and the occasional confession memory5 - news2 + news3

    diff --git a/docs/blog/topics/use-cases/index.html b/docs/blog/topics/use-cases/index.html index 4833e87ee..4655239d6 100644 --- a/docs/blog/topics/use-cases/index.html +++ b/docs/blog/topics/use-cases/index.html @@ -116,7 +116,7 @@

    use-cases

    Real workflows the hive actually runs — writ guides46 - concepts34 + concepts35 comparisons34 @@ -130,7 +130,7 @@

    use-cases

    Real workflows the hive actually runs — writ memory5 - news2 + news3

    diff --git a/docs/blog/vibe-kanban-alternative/index.html b/docs/blog/vibe-kanban-alternative/index.html index 81b71f35a..365fbe46a 100644 --- a/docs/blog/vibe-kanban-alternative/index.html +++ b/docs/blog/vibe-kanban-alternative/index.html @@ -278,7 +278,7 @@

    Related in Comparisons

    - 6 min + 5 min

    Claude Code Alternatives: 7 Coding Agents to Switch To

    Seven Claude Code alternatives checked on 5 Oct 2026: Codex CLI, OpenCode, Cursor, Gemini CLI, Cline, Pi and Aider, by licence, starting price and models.

    @@ -294,7 +294,7 @@

    Claude Code Alternatives: 7 Coding
    - 6 min + 5 min

    OpenClaw Alternatives: 7 Picks for Chat, Code and Cloud

    Seven OpenClaw alternatives sorted by job: Hermes Agent, Munder Difflin, NanoClaw, ZeroClaw, nanobot, Goose and Claude. Licences and prices checked 5 Oct 2026.

    diff --git a/docs/blog/voice-as-a-control-plane-for-agent-fleets/index.html b/docs/blog/voice-as-a-control-plane-for-agent-fleets/index.html index 9d72db07d..bd0d2897c 100644 --- a/docs/blog/voice-as-a-control-plane-for-agent-fleets/index.html +++ b/docs/blog/voice-as-a-control-plane-for-agent-fleets/index.html @@ -284,50 +284,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-are-claude-code-agents/index.html b/docs/blog/what-are-claude-code-agents/index.html index 1693a7b54..0778f262c 100644 --- a/docs/blog/what-are-claude-code-agents/index.html +++ b/docs/blog/what-are-claude-code-agents/index.html @@ -302,50 +302,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-a-multi-agent-harness/index.html b/docs/blog/what-is-a-multi-agent-harness/index.html index d3ce8c222..b797b94a3 100644 --- a/docs/blog/what-is-a-multi-agent-harness/index.html +++ b/docs/blog/what-is-a-multi-agent-harness/index.html @@ -373,50 +373,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-aeo-for-dev-tools/index.html b/docs/blog/what-is-aeo-for-dev-tools/index.html index 5407b7cf0..580c0b77b 100644 --- a/docs/blog/what-is-aeo-for-dev-tools/index.html +++ b/docs/blog/what-is-aeo-for-dev-tools/index.html @@ -424,50 +424,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-an-agent-development-environment/index.html b/docs/blog/what-is-an-agent-development-environment/index.html index b11ba17a1..cda58325d 100644 --- a/docs/blog/what-is-an-agent-development-environment/index.html +++ b/docs/blog/what-is-an-agent-development-environment/index.html @@ -287,50 +287,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-an-mcp-server/index.html b/docs/blog/what-is-an-mcp-server/index.html index 4e95ce6ff..660269063 100644 --- a/docs/blog/what-is-an-mcp-server/index.html +++ b/docs/blog/what-is-an-mcp-server/index.html @@ -292,50 +292,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-chatgpt-dots/index.html b/docs/blog/what-is-chatgpt-dots/index.html index cc5a9ba90..19ae7b434 100644 --- a/docs/blog/what-is-chatgpt-dots/index.html +++ b/docs/blog/what-is-chatgpt-dots/index.html @@ -335,50 +335,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-claude-cowork/index.html b/docs/blog/what-is-claude-cowork/index.html index e9b08f66e..9985b7682 100644 --- a/docs/blog/what-is-claude-cowork/index.html +++ b/docs/blog/what-is-claude-cowork/index.html @@ -392,50 +392,50 @@

    FAQ

    Related in Concepts

    - +
    - + 5 min
    -

    What Is Codex? OpenAI's Coding Agent in the CLI, App and Cloud

    -

    What is Codex? OpenAI's coding agent runs in your terminal, the ChatGPT desktop app, your IDE and the cloud. Which plans include it, checked 2 Oct 2026.

    +

    Hermes Agent: what it is, how to install it and what it costs

    +

    Hermes Agent is Nous Research's MIT licensed AI agent with memory, skills and chat channels. Install command, models, pricing and how it compares.

    Concepts - Read → + Read →
    diff --git a/docs/blog/what-is-codex/index.html b/docs/blog/what-is-codex/index.html index 28f2e3cc7..d3114d19b 100644 --- a/docs/blog/what-is-codex/index.html +++ b/docs/blog/what-is-codex/index.html @@ -352,50 +352,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-gemini-spark/index.html b/docs/blog/what-is-gemini-spark/index.html index ac7ce4fa7..f683a7636 100644 --- a/docs/blog/what-is-gemini-spark/index.html +++ b/docs/blog/what-is-gemini-spark/index.html @@ -376,50 +376,50 @@

    FAQ

    Related in Concepts

    - +
    - + 5 min
    -

    What Is Codex? OpenAI's Coding Agent in the CLI, App and Cloud

    -

    What is Codex? OpenAI's coding agent runs in your terminal, the ChatGPT desktop app, your IDE and the cloud. Which plans include it, checked 2 Oct 2026.

    +

    Hermes Agent: what it is, how to install it and what it costs

    +

    Hermes Agent is Nous Research's MIT licensed AI agent with memory, skills and chat channels. Install command, models, pricing and how it compares.

    Concepts - Read → + Read →
    diff --git a/docs/blog/what-is-harness-engineering/index.html b/docs/blog/what-is-harness-engineering/index.html index e3bf75ddd..0d8821678 100644 --- a/docs/blog/what-is-harness-engineering/index.html +++ b/docs/blog/what-is-harness-engineering/index.html @@ -288,50 +288,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-hermes-agent/index.html b/docs/blog/what-is-hermes-agent/index.html index d0041d826..e4a1eb68f 100644 --- a/docs/blog/what-is-hermes-agent/index.html +++ b/docs/blog/what-is-hermes-agent/index.html @@ -393,50 +393,50 @@

    FAQ

    Related in Concepts

    - +
    - + 5 min
    -

    What Is Codex? OpenAI's Coding Agent in the CLI, App and Cloud

    -

    What is Codex? OpenAI's coding agent runs in your terminal, the ChatGPT desktop app, your IDE and the cloud. Which plans include it, checked 2 Oct 2026.

    +

    Gemini Spark: what it is, what it costs and who can get it

    +

    Gemini Spark is Google's personal AI agent in the Gemini app. Plans, US prices, countries, limits and how it compares with Grok Bot, checked 3 Oct 2026.

    Concepts - Read → + Read →
    diff --git a/docs/blog/what-is-opencode/index.html b/docs/blog/what-is-opencode/index.html index 05fd5f59c..c4219f619 100644 --- a/docs/blog/what-is-opencode/index.html +++ b/docs/blog/what-is-opencode/index.html @@ -410,50 +410,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-pi-agent/index.html b/docs/blog/what-is-pi-agent/index.html index adf655039..612ea1a9e 100644 --- a/docs/blog/what-is-pi-agent/index.html +++ b/docs/blog/what-is-pi-agent/index.html @@ -364,50 +364,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/what-is-vibe-coding/index.html b/docs/blog/what-is-vibe-coding/index.html index 3dd02ea9e..f459a457e 100644 --- a/docs/blog/what-is-vibe-coding/index.html +++ b/docs/blog/what-is-vibe-coding/index.html @@ -277,50 +277,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/when-do-agents-need-an-agent-protocol/index.html b/docs/blog/when-do-agents-need-an-agent-protocol/index.html index f35c5b999..deae83d82 100644 --- a/docs/blog/when-do-agents-need-an-agent-protocol/index.html +++ b/docs/blog/when-do-agents-need-an-agent-protocol/index.html @@ -332,50 +332,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/blog/why-cli-agents-are-powerful/index.html b/docs/blog/why-cli-agents-are-powerful/index.html index a2122ebbe..a811eb950 100644 --- a/docs/blog/why-cli-agents-are-powerful/index.html +++ b/docs/blog/why-cli-agents-are-powerful/index.html @@ -370,50 +370,50 @@

    FAQ

    Related in Concepts

    diff --git a/docs/llms.txt b/docs/llms.txt index fbd673718..3d69363fc 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -43,7 +43,7 @@ ## Blog -177 posts, grouped by topic, newest first in each topic. Topic hubs: https://munderdiffl.in/blog/topics/ +179 posts, grouped by topic, newest first in each topic. Topic hubs: https://munderdiffl.in/blog/topics/ ### guides @@ -96,6 +96,7 @@ ### concepts +- [OpenAI text watermarking: textGrain, who gets it, limits](https://munderdiffl.in/blog/openai-text-watermarking/): OpenAI text watermarking (textGrain) is opt in for the API now and is coming to eligible ChatGPT and Codex text in the EU. Limits checked 6 Oct 2026. - [What Is Claude Cowork? Plans, Prices and How to Use It](https://munderdiffl.in/blog/what-is-claude-cowork/): Claude Cowork is Anthropic's agent for multi-step office work. Which plans include it, what it costs, where it runs and how to start, checked 3 Oct 2026. - [Gemini Spark: what it is, what it costs and who can get it](https://munderdiffl.in/blog/what-is-gemini-spark/): Gemini Spark is Google's personal AI agent in the Gemini app. Plans, US prices, countries, limits and how it compares with Grok Bot, checked 3 Oct 2026. - [Hermes Agent: what it is, how to install it and what it costs](https://munderdiffl.in/blog/what-is-hermes-agent/): Hermes Agent is Nous Research's MIT licensed AI agent with memory, skills and chat channels. Install command, models, pricing and how it compares. @@ -246,6 +247,7 @@ ### news +- [Agent Tools Today, 6 Oct 2026: OpenAI Text Watermarks, Reflection Beam, Cloudflare Web Search](https://munderdiffl.in/blog/agent-tools-today-2026-10-06/): The top launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 6 Oct 2026, each with its source. - [Agent Tools Today, 4 Oct 2026: You should Know, OpenClaw's Android Wait, Codex Wish List](https://munderdiffl.in/blog/agent-tools-today-2026-10-04/): The top 5 launches, skills and plugins, multi agent moves, rising repos and arguments in coding agents on 4 Oct 2026, each with its source. - [Agent Tools Today, 3 Oct 2026: Claude Code Mods, OpenDots, Conductor Mobile](https://munderdiffl.in/blog/agent-tools-today-2026-10-03/): The top 5 launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 3 Oct 2026, each with its source. diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 4780c641b..84b84de84 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -10,7 +10,7 @@ https://munderdiffl.in/ - 2026-10-05 + 2026-10-06 weekly 1.0 @@ -21,6 +21,18 @@ https://munderdiffl.in/blog/daily0.9 https://munderdiffl.in/blog/topics/weekly0.6 https://munderdiffl.in/blog/about/monthly0.4 + + https://munderdiffl.in/blog/agent-tools-today-2026-10-06/ + 2026-10-06 + monthly + 0.8 + + + https://munderdiffl.in/blog/openai-text-watermarking/ + 2026-10-06 + monthly + 0.8 + https://munderdiffl.in/blog/asd-ste100-prompting/ 2026-10-05