diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index 25ce526057..f74aaf01c5 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -5303,5 +5303,38 @@ "status": "ready" }, "inline": {} + }, + "pi-agent-alternatives": { + "title": "Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)", + "category": "comparisons", + "hero": { + "file": "assets/media/pi-agent-alternatives/hero.png", + "alt": "Title card on white. A pill reading Alternatives, the title Pi agent alternatives, the line 7 coding agents to try and a Checked 26 Oct 2026 line. Seven small tiles named OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush slide in under a grey tile labelled Pi.", + "prompt": "Title card: the hero scene in assets/media/pi-agent-alternatives/motion.js, shot at 1600 by 900.", + "status": "ready" + }, + "inline": {} + }, + "claude-md-best-practices": { + "title": "CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)", + "category": "guides", + "hero": { + "file": "assets/media/claude-md-best-practices/hero.png", + "alt": "Title card on white. A pill reading Guide, the title CLAUDE.md best practices, the line What to write, where it lives, how long and a Checked 26 Oct 2026 line, beside a white sheet with five short ruled lines and a yellow mark labelled Under 200 lines.", + "prompt": "Title card: the hero scene in assets/media/claude-md-best-practices/motion.js, shot at 1600 by 900.", + "status": "ready" + }, + "inline": {} + }, + "codex-computer-use": { + "title": "Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)", + "category": "guides", + "hero": { + "file": "assets/media/codex-computer-use/hero.png", + "alt": "Title card on white. A pill reading Guide, the title Codex computer use, the line Setup, plans and limits and a Checked 26 Oct 2026 line, beside a simple white desktop window where a blue pointer arrow clicks a mint toggle.", + "prompt": "Title card: the hero scene in assets/media/codex-computer-use/motion.js, shot at 1600 by 900.", + "status": "ready" + }, + "inline": {} } } diff --git a/blog/src/_data/site.js b/blog/src/_data/site.js index 582c72d3e4..fd1a91a695 100644 --- a/blog/src/_data/site.js +++ b/blog/src/_data/site.js @@ -9,7 +9,7 @@ export default { baseUrl: "https://munderdiffl.in/blog/", // Blog-index description (Kevin's SEO_METADATA.md §3.9). description: - "Guides, deep dives, and comparisons on running multi-agent Claude Code: orchestration, agent memory, automation, and the tooling landscape.", + "Guides, comparisons and pricing for AI agents and coding tools such as Claude Code, Codex and Cursor, each checked and dated. From the makers of Munder Difflin.", tagline: "Notes from the office floor.", lang: "en", locale: "en_US", diff --git a/blog/src/assets/media/claude-md-best-practices/gauge.png b/blog/src/assets/media/claude-md-best-practices/gauge.png new file mode 100644 index 0000000000..bffe839b05 Binary files /dev/null and b/blog/src/assets/media/claude-md-best-practices/gauge.png differ diff --git a/blog/src/assets/media/claude-md-best-practices/hero.png b/blog/src/assets/media/claude-md-best-practices/hero.png new file mode 100644 index 0000000000..2e586a857b Binary files /dev/null and b/blog/src/assets/media/claude-md-best-practices/hero.png differ diff --git a/blog/src/assets/media/claude-md-best-practices/junction.png b/blog/src/assets/media/claude-md-best-practices/junction.png new file mode 100644 index 0000000000..e954079c60 Binary files /dev/null and b/blog/src/assets/media/claude-md-best-practices/junction.png differ diff --git a/blog/src/assets/media/claude-md-best-practices/motion.css b/blog/src/assets/media/claude-md-best-practices/motion.css new file mode 100644 index 0000000000..e7b1df8a0b --- /dev/null +++ b/blog/src/assets/media/claude-md-best-practices/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "CLAUDE.md best practices". 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-md-best-practices/motion.js b/blog/src/assets/media/claude-md-best-practices/motion.js new file mode 100644 index 0000000000..643337700f --- /dev/null +++ b/blog/src/assets/media/claude-md-best-practices/motion.js @@ -0,0 +1,251 @@ +/* Bright coded scenes for "CLAUDE.md best practices". 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 CAP = "#6B5878"; + const hint = (svg, x, y, s) => txt(svg, x, y, s, { "text-anchor": "end", "font-size": 16, fill: MUTE, "font-weight": 500 }); + const pill = (svg, x, y, w, label, fn) => { + const g = el("g", {}, svg); + el("rect", { x, y, width: w, height: 52, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: x - 6, y: y - 6, width: w + 12, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + txt(g, x + w / 2, y + 34, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + const lift = (on, at, now) => (on ? oc(seg(now - at, 0, 0.35)) : 1 - oc(seg(now - at, 0, 0.35))); + + const SC = {}; + + /* ---- 1. four instruction files drop onto a pile, broadest first ---- */ + SC.stack = { + dur: 7.6, still: 6.6, + build(svg, api) { + hint(svg, 774, 50, "tap a sheet"); + const SH = [["Managed", LIL], ["User", SKY], ["Project", MINT], ["Local", YEL]]; + const X0 = 150, Y0 = 300, DX = 36, DY = -62, SW = 340, SHH = 230; + el("ellipse", { cx: X0 + SW / 2 + 54, cy: Y0 + SHH + 26, rx: 230, ry: 12, fill: SOFT }, svg); + const sheets = SH.map(([name, col], i) => { + const x = X0 + DX * i, y = Y0 + DY * i, g = el("g", {}, svg), body = el("g", {}, g); + el("rect", { x: x - 8, y: y - 8, width: SW + 16, height: SHH + 16, rx: 20, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "4 7", class: "mg-ring" }, body); + el("rect", { x: x + 6, y: y + 6, width: SW, height: SHH, rx: 14, fill: SOFT }, body); + el("rect", { x, y, width: SW, height: SHH, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, body); + [0, 1, 2, 3].forEach((r) => el("path", { d: `M${x + 28} ${y + 40 + r * 30} H${x + 28 + [250, 200, 270, 150][r]}`, stroke: SOFT, "stroke-width": 8, "stroke-linecap": "round" }, body)); + const cw = name.length * 12.5 + 34; + el("rect", { x: x + 22, y: y + SHH - 48, width: cw, height: 34, rx: 17, fill: col, stroke: INK, "stroke-width": 2.5 }, body); + txt(body, x + 22 + cw / 2, y + SHH - 24, name, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const s = { g, body, x, y, up: false, at: -99, cx: x + SW / 2, cy: y + SHH / 2 }; + button(g, `${name} sheet. Lift it`, () => { const now = api.now(); sheets.forEach((o) => { if (o !== s && o.up) { o.up = false; o.at = now; } }); s.up = !s.up; s.at = now; g.setAttribute("aria-pressed", s.up); api.poke(); }); + g.setAttribute("aria-pressed", "false"); + return s; + }); + const arrow = el("g", {}, svg); + const top = sheets[3], ay = top.y + 76; + el("path", { d: `M770 ${ay} H${top.x + SW + 22} M${top.x + SW + 36} ${ay - 12} L${top.x + SW + 22} ${ay} L${top.x + SW + 36} ${ay + 12}`, stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, arrow); + txt(arrow, (770 + top.x + SW + 22) / 2 + 6, ay - 18, "Read last", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + return (t, now) => { + const out = seg(t, 7.1, 7.6); + sheets.forEach((s, i) => { + const t0 = 0.4 + i * 1.0, k = ob(seg(t, t0, t0 + 0.75)), seen = seg(t, t0, t0 + 0.12); + const L = lift(s.up, s.at, now); + const dy = -340 * (1 - k) - 14 * L, dx = -130 * L, r = 4 * (1 - cl(k)) * (i % 2 ? 1 : -1) - 3 * L; + s.body.setAttribute("transform", `translate(${dx} ${dy}) rotate(${r} ${s.cx} ${s.cy})`); + s.g.setAttribute("opacity", seen * (1 - out)); + }); + const a = oc(seg(t, 4.5, 5.1)); + arrow.setAttribute("opacity", a * (1 - out)); arrow.setAttribute("transform", `translate(${30 * (1 - a)} 0)`); + }; + }, + }; + + /* ---- 2. a measuring tube fills to the 200 line mark, extras go to trays ---- */ + SC.gauge = { + dur: 11, still: 9.8, + build(svg, api) { + const T = { l: 230, r: 370, top: 70, bot: 512 }, BASE = 497, GAP = 16, NB = 16, NL = 6, MARK = 244; + el("rect", { x: T.l + 8, y: T.top + 8, width: T.r - T.l, height: T.bot - T.top, rx: 26, fill: SOFT }, svg); + el("path", { d: `M${T.l} ${T.top} V${T.bot - 26} Q${T.l} ${T.bot} ${T.l + 26} ${T.bot} H${T.r - 26} Q${T.r} ${T.bot} ${T.r} ${T.bot - 26} V${T.top}`, fill: "#fff", stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + el("path", { d: `M${T.l - 12} ${T.top} H${T.l + 10} M${T.r - 10} ${T.top} H${T.r + 12}`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + for (let y = T.bot - 34; y > T.top + 20; y -= 32) el("path", { d: `M${T.r} ${y} H${T.r + (((T.bot - 34 - y) / 32) % 2 ? 10 : 18)}`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, svg); + const W = [96, 72, 88, 60, 100, 80, 68, 92, 76, 98, 64, 86, 74, 94, 70, 84, 90, 66, 80, 96, 72, 62]; + const lines = Array.from({ length: NB + NL }, (_, i) => ({ n: el("rect", { x: 0, y: -4.5, width: W[i], height: 9, rx: 4.5, fill: i < NB ? BLUE : LIL, stroke: INK, "stroke-width": 1.5 }, svg), d: i < NB ? 0.4 + i * 0.17 : 3.4 + (i - NB) * 0.22 })); + const mark = el("g", {}, svg); + el("path", { d: `M200 ${MARK} H${T.r + 22}`, stroke: INK, "stroke-width": 12, "stroke-linecap": "round" }, mark); + el("path", { d: `M200 ${MARK} H${T.r + 22}`, stroke: YEL, "stroke-width": 7, "stroke-linecap": "round" }, mark); + el("rect", { x: 62, y: MARK - 22, width: 140, height: 44, rx: 22, fill: YEL, stroke: INK, "stroke-width": 3 }, mark); + txt(mark, 132, MARK + 8, "200 lines", { "text-anchor": "middle", "font-size": 21, "font-weight": 700 }); + const TR = [["Rules", 112], ["Skills", 244], ["Imports", 376]].map(([name, y]) => { + el("rect", { x: 528, y: y + 6, width: 170, height: 52, rx: 10, fill: SOFT }, svg); + el("path", { d: `M520 ${y} V${y + 40} Q520 ${y + 52} 532 ${y + 52} H678 Q690 ${y + 52} 690 ${y + 40} V${y}`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + txt(svg, 605, y + 88, name, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + return y; + }); + pill(svg, 535, 510, 140, "Tidy", () => api.seek(0)); + return (t) => { + const settle = io(seg(t, 7.9, 8.7)), gap = GAP - 2 * settle, out = seg(t, 10.4, 11); + lines.forEach((L, i) => { + const k = oc(seg(t, L.d, L.d + 0.4)), bounce = Math.sin(seg(t, L.d + 0.4, L.d + 0.65) * Math.PI) * -4; + let x = T.l + 20, y = i < NB ? BASE - i * gap : BASE - i * GAP - 12, s = 1; + y = T.top - 10 + (y - T.top + 10) * k + bounce; + if (i >= NB) { + const j = i - NB, f0 = 5.4 + j * 0.3, f = io(seg(t, f0, f0 + 0.8)); + const tx = 545 + (j % 2) * 12, ty = TR[Math.floor(j / 2)] + 40 - (j % 2) * 13; + x = x + (tx - x) * f; y = y + (ty - y) * f - Math.sin(f * Math.PI) * 70; s = 1 - 0.18 * f; + } + L.n.setAttribute("transform", `translate(${x} ${y}) scale(${s})`); + L.n.setAttribute("opacity", seg(t, L.d, L.d + 0.08) * (1 - out)); + }); + const wob = Math.sin(seg(t, 4.8, 5.3) * Math.PI * 3) * 3 * (1 - seg(t, 4.8, 5.3)); + mark.setAttribute("transform", `translate(${wob} 0)`); + }; + }, + }; + + /* ---- 3. a cart takes the agents track when there is no Claude file ---- */ + SC.junction = { + dur: 10.5, still: 9.6, + build(svg, api) { + hint(svg, 774, 50, "tap the switch"); + const J = [330, 330], END = 556; + const branch = (dy) => `M50 330 H330 C390 ${330 + dy * 40} 410 ${330 + dy * 160} 480 ${330 + dy * 160} H${END}`; + const rail = (d, g) => [el("path", { d, stroke: INK, "stroke-width": 17, fill: "none", "stroke-linecap": "round" }, g), el("path", { d, stroke: "#fff", "stroke-width": 10, fill: "none", "stroke-linecap": "round" }, g)]; + const mid = el("g", {}, svg), midRail = rail(`M330 330 H${END}`, mid), midLen = END - 330; + rail(branch(-1), svg); rail(branch(1), svg); + for (let x = 70; x < 320; x += 30) el("path", { d: `M${x} 341 V346`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const up = el("path", { d: branch(-1), fill: "none" }, svg), dn = el("path", { d: branch(1), fill: "none" }, svg); + const L = up.getTotalLength(), JD = 280 - 46, STOP = L - 58; + const card = (y, h, label, col) => { + const g = el("g", {}, svg); + el("rect", { x: 566, y: y - h / 2 + 6, width: 200, height: h, rx: 18, fill: SOFT }, g); + el("rect", { x: 560, y: y - h / 2, width: 200, height: h, rx: 18, fill: col, stroke: INK, "stroke-width": 3 }, g); + txt(g, 660, y + 9, label, { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + const tick = el("g", {}, g); + el("circle", { cx: 756, cy: y - h / 2 + 4, r: 18, fill: MINT, stroke: INK, "stroke-width": 2.5 }, tick); + el("path", { d: `M747 ${y - h / 2 + 4} l6 6 l11 -12`, stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, tick); + return { g, tick }; + }; + const cC = card(170, 84, "Claude file", LIL), cA = card(490, 84, "Agents file", SKY), cB = card(330, 60, "Both", MINT); + cB.tick.remove(); + const sw = el("g", {}, svg), blade = el("g", {}, sw); + el("circle", { cx: J[0], cy: J[1], r: 46, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, sw); + el("rect", { x: J[0] - 6, y: J[1] - 7, width: 58, height: 14, rx: 7, fill: YEL, stroke: INK, "stroke-width": 2.5 }, blade); + el("circle", { cx: J[0], cy: J[1], r: 13, fill: YEL, stroke: INK, "stroke-width": 3 }, sw); + const cart = el("g", {}, svg), body = el("g", {}, cart); + el("rect", { x: -34, y: -50, width: 68, height: 32, rx: 9, fill: BLUE, stroke: INK, "stroke-width": 3 }, body); + el("path", { d: "M-20 -38 H20", stroke: "#fff", "stroke-width": 4, "stroke-linecap": "round" }, body); + [-18, 18].forEach((x) => el("circle", { cx: x, cy: -15, r: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, body)); + let user = null, lastS = 0, lastD = 0, lastOp = 1, prevT = 0; + button(sw, "Track switch. Flip it", () => { user = { to: lastS > 0.5 ? 0 : 1, s: lastS, d: lastD, op: lastOp, at: api.now() }; api.poke(); }); + const pt = (d, s) => { const a = up.getPointAtLength(d), b = dn.getPointAtLength(d); return [a.x + (b.x - a.x) * s, a.y + (b.y - a.y) * s]; }; + return (t, now) => { + if (t < prevT - 1) user = null; prevT = t; + const out = seg(t, 10, 10.5); + let s, d, opC; + if (user) { + const u = now - user.at, back = io(seg(u, 0, 0.8)), flip = io(seg(u, 0.8, 1.1)), go = io(seg(u, 1.2, 3)); + const from = Math.min(user.d, JD); + s = user.s + (user.to - user.s) * flip; + d = u < 1.2 ? user.d + (from - user.d) * back : from + (STOP - from) * go; + opC = user.op + (1 - 0.7 * user.to - user.op) * flip; + } else { + s = io(seg(t, 3.3, 3.8)); + d = JD * oc(seg(t, 0.5, 2.3)) + (STOP - JD) * io(seg(t, 3.9, 5.8)); + opC = 1 - 0.7 * seg(t, 2.5, 3.3); + } + lastS = s; lastD = d; lastOp = opC; + const [x, y] = pt(d, s), [x2, y2] = pt(Math.min(d + 2, L), s), ang = (Math.atan2(y2 - y, x2 - x) * 180) / Math.PI; + cart.setAttribute("transform", `translate(${x} ${y}) rotate(${ang})`); + cart.setAttribute("opacity", seg(t, 0, 0.3) * (1 - out)); + blade.setAttribute("transform", `rotate(${-34 + 68 * s} ${J[0]} ${J[1]})`); + sw.setAttribute("aria-pressed", s > 0.5); + cC.g.setAttribute("opacity", opC + (1 - opC) * out); + const arr = seg(d, STOP - 12, STOP); + cC.tick.setAttribute("opacity", arr * (1 - s) * (1 - out)); cA.tick.setAttribute("opacity", arr * s * (1 - out)); + const b = io(seg(t, 6.4, 7.4)), bc = ob(seg(t, 7.2, 7.7)); + midRail.forEach((n) => n.setAttribute("stroke-dasharray", `${midLen * b + 0.01} ${midLen + 40}`)); + mid.setAttribute("opacity", (b > 0 ? 1 : 0) * (1 - out)); + cB.g.setAttribute("opacity", cl(bc) * (1 - out)); cB.g.setAttribute("transform", `translate(${30 * (1 - cl(bc))} 0)`); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 8, still: 8, + build(svg) { + el("rect", { x: 64, y: 88, width: 110, height: 42, rx: 21, fill: SKY, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 119, 116, "Guide", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 60, 226, "CLAUDE.md", { "font-family": MONO, "font-size": 70, "font-weight": 700 }); + txt(svg, 60, 306, "best practices", { "font-size": 70, "font-weight": 700 }); + txt(svg, 64, 366, "What to write, where it lives, how long", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 408, "Checked 26 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const S = { x: 606, y: 140, w: 170, h: 260 }, sheet = el("g", {}, svg); + el("rect", { x: S.x + 9, y: S.y + 9, width: S.w, height: S.h, rx: 14, fill: SOFT }, sheet); + el("rect", { x: S.x, y: S.y, width: S.w, height: S.h, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, sheet); + const LW = [118, 92, 126, 78, 104]; + const rules = LW.map((w, i) => el("path", { d: `M${S.x + 24} ${S.y + 44 + i * 36} H${S.x + 24 + w}`, stroke: BLUE, "stroke-width": 8, "stroke-linecap": "round" }, sheet)); + const mk = el("g", {}, svg), y1 = S.y + 44, y2 = S.y + 44 + 4 * 36, bx = S.x + S.w + 26; + el("path", { d: `M${bx - 8} ${y1} H${bx} V${y2} H${bx - 8}`, stroke: INK, "stroke-width": 11, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, mk); + el("path", { d: `M${bx - 8} ${y1} H${bx} V${y2} H${bx - 8}`, stroke: YEL, "stroke-width": 6, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, mk); + txt(mk, bx + 16, (y1 + y2) / 2 - 4, "Under", { "font-size": 22, "font-weight": 700 }); + txt(mk, bx + 16, (y1 + y2) / 2 + 24, "200 lines", { "font-size": 22, "font-weight": 700 }); + return (t) => { + const k = ob(seg(t, 0.2, 0.9)); + sheet.setAttribute("opacity", seg(t, 0.2, 0.35)); sheet.setAttribute("transform", `translate(0 ${-40 * (1 - k)})`); + rules.forEach((r, i) => { const q = oc(seg(t, 0.8 + i * 0.18, 1.2 + i * 0.18)); r.setAttribute("stroke-dasharray", `${LW[i] * q + 0.01} 400`); r.setAttribute("opacity", q > 0 ? 1 : 0); }); + const m = oc(seg(t, 1.9, 2.4)); mk.setAttribute("opacity", m); mk.setAttribute("transform", `translate(${16 * (1 - m)} 0)`); + }; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/claude-md-best-practices/stack.png b/blog/src/assets/media/claude-md-best-practices/stack.png new file mode 100644 index 0000000000..4cfe07aa0d Binary files /dev/null and b/blog/src/assets/media/claude-md-best-practices/stack.png differ diff --git a/blog/src/assets/media/codex-computer-use/approval.png b/blog/src/assets/media/codex-computer-use/approval.png new file mode 100644 index 0000000000..77bc0fb3e4 Binary files /dev/null and b/blog/src/assets/media/codex-computer-use/approval.png differ diff --git a/blog/src/assets/media/codex-computer-use/hero.png b/blog/src/assets/media/codex-computer-use/hero.png new file mode 100644 index 0000000000..64689644eb Binary files /dev/null and b/blog/src/assets/media/codex-computer-use/hero.png differ diff --git a/blog/src/assets/media/codex-computer-use/motion.css b/blog/src/assets/media/codex-computer-use/motion.css new file mode 100644 index 0000000000..dfe632ff0c --- /dev/null +++ b/blog/src/assets/media/codex-computer-use/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Codex computer use". 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/codex-computer-use/motion.js b/blog/src/assets/media/codex-computer-use/motion.js new file mode 100644 index 0000000000..9db72536e5 --- /dev/null +++ b/blog/src/assets/media/codex-computer-use/motion.js @@ -0,0 +1,281 @@ +/* Bright coded scenes for "Codex computer use". 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 CAP = "#6B5878"; + const pill = (svg, x, y, w, label, fn) => { + const g = el("g", {}, svg); + el("rect", { x, y, width: w, height: 52, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: x - 6, y: y - 6, width: w + 12, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + txt(g, x + w / 2, y + 34, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + /* a plain arrow cursor, tip at 0,0 */ + const pointer = (p, s = 1.3) => { + const g = el("g", {}, p), ring = el("circle", { cx: 0, cy: 0, r: 10, fill: "none", stroke: BLUE, "stroke-width": 3 }, g); + const arrow = el("g", {}, g); + el("path", { d: "M0 0 V30 L7.5 23 L13 35.5 L19.5 32.5 L14 20.5 H24.5 Z", fill: BLUE, stroke: INK, "stroke-width": 2.4, "stroke-linejoin": "round", transform: `scale(${s})` }, arrow); + return { g, arrow, ring }; + }; + const press = (P, k) => { // k from 0 to 1 over one click + const dn = Math.sin(cl(k) * Math.PI); + P.arrow.setAttribute("transform", `scale(${1 - 0.1 * dn})`); + P.ring.setAttribute("r", 8 + 22 * k); P.ring.setAttribute("opacity", k > 0 && k < 1 ? 1 - k : 0); + }; + /* a switch: soft track, mint when on */ + const toggle = (p, x, y, w = 68, h = 36) => { + const g = el("g", {}, p); + el("rect", { x: x - 7, y: y - 7, width: w + 14, height: h + 14, rx: (h + 14) / 2, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, g); + el("rect", { x, y, width: w, height: h, rx: h / 2, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); + const on = el("rect", { x, y, width: w, height: h, rx: h / 2, fill: MINT, stroke: INK, "stroke-width": 3 }, g); + const r = h / 2 - 6, knob = el("circle", { cx: x + h / 2, cy: y + h / 2, r, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + return { g, set: (v) => { on.setAttribute("opacity", cl(v)); knob.setAttribute("cx", x + h / 2 + (w - h) * cl(v)); } }; + }; + const tick = (p, cx, cy, r = 17) => { + const g = el("g", {}, p); + el("circle", { cx, cy, r, fill: MINT, stroke: INK, "stroke-width": 2.5 }, g); + el("path", { d: `M${cx - r * 0.5} ${cy} l${r * 0.35} ${r * 0.35} l${r * 0.65} ${-r * 0.7}`, stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); + return g; + }; + const windowFrame = (p, x, y, w, h, title) => { + el("rect", { x: x + 10, y: y + 10, width: w, height: h, rx: 18, fill: SOFT }, p); + el("rect", { x, y, width: w, height: h, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 3 }, p); + el("path", { d: `M${x} ${y + 54} H${x + w}`, stroke: INK, "stroke-width": 3 }, p); + [0, 1, 2].forEach((i) => el("circle", { cx: x + 28 + i * 24, cy: y + 27, r: 7, fill: [SOFT, YEL, MINT][i], stroke: INK, "stroke-width": 2.2 }, p)); + if (title) txt(p, x + w / 2, y + 35, title, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + }; + const lerp = (a, b, k) => a + (b - a) * k; + + const SC = {}; + + /* ---- 1. a pointer flips one switch in a Settings window and clicks Save ---- */ + SC.window = { + dur: 10, still: 8.6, + build(svg, api) { + const root = el("g", {}, svg); + const X = 50, Y = 110, WW = 460, WH = 390; + windowFrame(root, X, Y, WW, WH, "Settings"); + const ROWS = [204, 274, 344], BAR = [190, 150, 210]; + const sw = ROWS.map((y, i) => { + el("rect", { x: X + 40, y: y - 6, width: BAR[i], height: 12, rx: 6, fill: SOFT }, root); + const s = toggle(root, X + WW - 108, y - 18); + s.g.setAttribute("aria-pressed", "false"); + return s; + }); + const SV = { x: X + WW - 176, y: Y + WH - 76, w: 136, h: 50 }; + const save = el("g", {}, root); + el("rect", { x: SV.x, y: SV.y + 5, width: SV.w, height: SV.h, rx: 25, fill: INK }, save); + const saveTop = el("g", {}, save); + el("rect", { x: SV.x, y: SV.y, width: SV.w, height: SV.h, rx: 25, fill: YEL, stroke: INK, "stroke-width": 3 }, saveTop); + txt(saveTop, SV.x + SV.w / 2, SV.y + 33, "Save", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + const card = el("g", {}, root), CX = 548, CY = 252, CW = 232, CH = 96; + el("rect", { x: CX + 6, y: CY + 6, width: CW, height: CH, rx: 18, fill: SOFT }, card); + el("rect", { x: CX, y: CY, width: CW, height: CH, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 3 }, card); + txt(card, CX + CW / 2, CY + CH / 2 + 8, "Done in the app", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + tick(card, CX + 4, CY + 4); + const P = pointer(root); + const T2 = [X + WW - 108 + 50, ROWS[1] + 4], SVP = [SV.x + SV.w - 30, SV.y + SV.h / 2 + 4], START = [760, 590]; + const user = [null, null, null], last = [0, 0, 0]; + let prevT = 0; + sw.forEach((s, i) => button(s.g, `Switch ${i + 1}. Flip it`, () => { user[i] = { from: last[i], to: last[i] > 0.5 ? 0 : 1, at: api.now() }; api.poke(); })); + return (t, now) => { + if (t < prevT - 1) user.fill(null); prevT = t; + root.setAttribute("opacity", seg(t, 0, 0.4) * (1 - seg(t, 9.4, 10))); + sw.forEach((s, i) => { + const u = user[i], auto = i === 1 ? io(seg(t, 3.0, 3.3)) : 0; + const v = u ? lerp(u.from, u.to, oc(seg(now - u.at, 0, 0.25))) : auto; + last[i] = v; s.set(v); s.g.setAttribute("aria-pressed", v > 0.5); + }); + const a = io(seg(t, 1.0, 2.6)), b = io(seg(t, 3.7, 4.9)); + let x = lerp(START[0], T2[0], a) + Math.sin(a * Math.PI) * 30, y = lerp(START[1], T2[1], a); + x = lerp(x, SVP[0], b); y = lerp(y, SVP[1], b) - Math.sin(b * Math.PI) * 30; + P.g.setAttribute("transform", `translate(${x} ${y})`); + P.g.setAttribute("opacity", seg(t, 0.9, 1.2)); + press(P, t < 3.5 ? seg(t, 2.75, 3.3) : seg(t, 5.0, 5.55)); + saveTop.setAttribute("transform", `translate(0 ${4 * Math.sin(seg(t, 5.0, 5.3) * Math.PI)})`); + const c = ob(seg(t, 5.5, 6.1)); + card.setAttribute("opacity", seg(t, 5.5, 5.65)); card.setAttribute("transform", `translate(${24 * (1 - c)} 0)`); + }; + }, + }; + + /* ---- 2. an approval card; Always allow sends the app tile to a shelf ---- */ + SC.approval = { + dur: 10, still: 8.6, + build(svg, api) { + const root = el("g", {}, svg); + const C = { x: 160, y: 56, w: 480, h: 262 }; + el("rect", { x: C.x + 10, y: C.y + 10, width: C.w, height: C.h, rx: 22, fill: SOFT }, root); + el("rect", { x: C.x, y: C.y, width: C.w, height: C.h, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, root); + el("rect", { x: 196, y: 92, width: 90, height: 90, rx: 18, fill: "none", stroke: MUTE, "stroke-width": 2, "stroke-dasharray": "5 7" }, root); + txt(root, 312, 150, "Use Calculator?", { "font-size": 30, "font-weight": 700 }); + const B = [["Allow once", 196], ["Always allow", 420]].map(([label, x]) => { + const g = el("g", {}, root), y = 230, w = 204, h = 54; + el("rect", { x: x - 7, y: y - 7, width: w + 14, height: h + 14, rx: 34, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, g); + el("rect", { x, y, width: w, height: h, rx: 27, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const lit = el("rect", { x, y, width: w, height: h, rx: 27, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + txt(g, x + w / 2, y + 35, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + g.setAttribute("aria-pressed", "false"); + return { g, lit, label }; + }); + const S = { x: 130, y: 400, w: 540, h: 172 }; + el("rect", { x: S.x + 8, y: S.y + 8, width: S.w, height: S.h, rx: 22, fill: SOFT }, root); + el("rect", { x: S.x, y: S.y, width: S.w, height: S.h, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, root); + txt(root, S.x + 32, S.y + 146, "Always allowed apps", { "font-size": 22, "font-weight": 700 }); + [0, 1, 2, 3].forEach((i) => el("rect", { x: S.x + 32 + i * 96, y: S.y + 24, width: 76, height: 76, rx: 16, fill: "none", stroke: MUTE, "stroke-width": 2, "stroke-dasharray": "5 7" }, root)); + const tile = el("g", {}, root); + el("rect", { x: 0, y: 0, width: 90, height: 90, rx: 18, fill: SKY, stroke: INK, "stroke-width": 3 }, tile); + el("rect", { x: 14, y: 13, width: 62, height: 20, rx: 5, fill: "#fff", stroke: INK, "stroke-width": 2.2 }, tile); + [0, 1].forEach((r) => [0, 1, 2].forEach((c) => el("rect", { x: 14 + c * 23, y: 42 + r * 21, width: 16, height: 15, rx: 4, fill: r === 1 && c === 2 ? YEL : "#fff", stroke: INK, "stroke-width": 2 }, tile))); + const FROM = [196, 92, 1], TO = [S.x + 32 + 1.5, S.y + 24 + 1.5, 73 / 90]; + let user = null, prevT = 0, lA = 0, lO = 0, lT = 0; + const pick = (which) => { user = { which, at: api.now(), A: lA, O: lO, T: lT }; api.poke(); }; + button(B[0].g, "Allow once", () => pick(0)); button(B[1].g, "Always allow", () => pick(1)); + return (t, now) => { + if (t < prevT - 1) user = null; prevT = t; + root.setAttribute("opacity", seg(t, 0, 0.4) * (1 - seg(t, 9.4, 10))); + let A, O, T; + if (user) { + const u = now - user.at, k = oc(seg(u, 0, 0.3)), m = io(seg(u, 0.4, 1.6)), al = user.which === 1; + A = lerp(user.A, al ? 1 : 0, k); O = lerp(user.O, al ? 0 : 1, k); T = lerp(user.T, al ? 1 : 0, m); + } else { A = oc(seg(t, 3.0, 3.3)); O = 0; T = io(seg(t, 4.0, 5.4)); } + lA = A; lO = O; lT = T; + B[0].lit.setAttribute("opacity", O); B[1].lit.setAttribute("opacity", A); + B[0].g.setAttribute("aria-pressed", O > 0.5); B[1].g.setAttribute("aria-pressed", A > 0.5); + const x = lerp(FROM[0], TO[0], T), y = lerp(FROM[1], TO[1], T) - Math.sin(T * Math.PI) * 50, s = lerp(FROM[2], TO[2], T); + tile.setAttribute("transform", `translate(${x} ${y}) rotate(${Math.sin(T * Math.PI) * -6} 45 45) scale(${s})`); + }; + }, + }; + + /* ---- 3. a padlock opens under a lilac cover for an active task, then relocks on a key press ---- */ + SC.padlock = { + dur: 10, still: 9, + build(svg, api) { + const root = el("g", {}, svg); + el("rect", { x: 128, y: 48, width: 480, height: 350, rx: 20, fill: SOFT }, root); + el("rect", { x: 120, y: 40, width: 480, height: 350, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 4 }, root); + el("rect", { x: 140, y: 60, width: 440, height: 312, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 2 }, root); + el("path", { d: "M70 390 H650 L624 428 H96 Z", fill: "#fff", stroke: INK, "stroke-width": 3.5, "stroke-linejoin": "round" }, root); + el("path", { d: "M318 390 Q318 402 330 402 H390 Q402 402 402 390", fill: SOFT, stroke: INK, "stroke-width": 2.5 }, root); + const clip = el("clipPath", { id: "mg-pl-clip" }, svg); + el("rect", { x: 141, y: 61, width: 438, height: 310, rx: 9 }, clip); + const cover = el("circle", { cx: 360, cy: 294, r: 0, fill: LIL, "clip-path": "url(#mg-pl-clip)" }, root); + const tag = el("g", {}, root); + el("rect", { x: 412, y: 82, width: 148, height: 40, rx: 20, fill: BLUE, stroke: INK, "stroke-width": 2.5 }, tag); + txt(tag, 486, 109, "Active task", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const lock = el("g", {}, root), shackle = el("g", {}, lock), body = el("g", {}, lock); + const SH = "M318 244 V200 A42 42 0 0 1 402 200 V244"; + el("path", { d: SH, stroke: INK, "stroke-width": 26, fill: "none", "stroke-linecap": "round" }, shackle); + el("path", { d: SH, stroke: YEL, "stroke-width": 14, fill: "none", "stroke-linecap": "round" }, shackle); + el("rect", { x: 288, y: 236, width: 144, height: 118, rx: 22, fill: YEL, stroke: INK, "stroke-width": 4 }, body); + el("circle", { cx: 360, cy: 284, r: 12, fill: INK }, body); + el("rect", { x: 354, y: 284, width: 12, height: 36, rx: 5, fill: INK }, body); + const key = el("g", {}, root), K = { x: 662, y: 300, s: 80 }; + el("rect", { x: K.x, y: K.y + 8, width: K.s, height: K.s - 6, rx: 16, fill: INK }, key); + const cap = el("g", {}, key); + el("rect", { x: K.x, y: K.y, width: K.s, height: K.s - 6, rx: 16, fill: SKY, stroke: INK, "stroke-width": 3 }, cap); + el("path", { d: `M${K.x + 40} ${K.y + 18} V${K.y + 52} M${K.x + 28} ${K.y + 40} L${K.x + 40} ${K.y + 52} L${K.x + 52} ${K.y + 40}`, stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, cap); + const burst = el("path", { d: `M${K.x + 40} ${K.y - 14} V${K.y - 30} M${K.x + 8} ${K.y - 8} L${K.x - 4} ${K.y - 20} M${K.x + 72} ${K.y - 8} L${K.x + 84} ${K.y - 20}`, stroke: INK, "stroke-width": 3.5, "stroke-linecap": "round" }, key); + pill(svg, 620, 512, 140, "Lock", () => api.seek(0)); + return (t) => { + root.setAttribute("opacity", seg(t, 0, 0.4) * (1 - seg(t, 9.4, 10))); + const pop = ob(seg(t, 0.4, 1.0)); + lock.setAttribute("opacity", seg(t, 0.4, 0.55)); + const shut = ob(seg(t, 6.05, 6.3)), open = io(seg(t, 2.3, 2.75)) * (1 - shut); + const jolt = Math.sin(seg(t, 6.2, 6.5) * Math.PI) * 5; + lock.setAttribute("transform", `translate(360 ${295 + jolt}) scale(${0.6 + 0.4 * pop}) translate(-360 -295)`); + shackle.setAttribute("transform", `translate(0 ${-26 * open}) rotate(${-28 * open} 318 244)`); + const tg = oc(seg(t, 1.4, 1.9)); + tag.setAttribute("opacity", seg(t, 1.4, 1.6)); tag.setAttribute("transform", `translate(0 ${-14 * (1 - tg)})`); + cover.setAttribute("r", 470 * io(seg(t, 2.6, 4.0)) * (1 - io(seg(t, 6.5, 7.5)))); + const kp = ob(seg(t, 5.3, 5.7)), down = Math.sin(seg(t, 5.85, 6.15) * Math.PI); + key.setAttribute("opacity", seg(t, 5.3, 5.45)); + key.setAttribute("transform", `translate(${K.x + 40} ${K.y + 37}) scale(${0.6 + 0.4 * kp}) translate(${-K.x - 40} ${-K.y - 37})`); + cap.setAttribute("transform", `translate(0 ${7 * down})`); + burst.setAttribute("opacity", seg(t, 5.9, 6.05)); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 8, still: 8, + build(svg) { + el("rect", { x: 64, y: 88, width: 110, height: 42, rx: 21, fill: SKY, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 119, 116, "Guide", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 60, 230, "Codex", { "font-family": MONO, "font-size": 76, "font-weight": 700 }); + txt(svg, 60, 310, "computer use", { "font-size": 70, "font-weight": 700 }); + txt(svg, 64, 370, "Setup, plans and limits", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 412, "Checked 26 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const win = el("g", {}, svg), X = 600, Y = 130, WW = 300, WH = 270; + windowFrame(win, X, Y, WW, WH); + [[X + 34, Y + 92, 170], [X + 34, Y + 122, 120]].forEach(([x, y, w]) => el("rect", { x, y: y - 6, width: w, height: 12, rx: 6, fill: SOFT }, win)); + const sw = toggle(win, X + 80, Y + 170, 140, 66); + const P = pointer(svg, 1.5); + return (t) => { + const k = ob(seg(t, 0.2, 0.9)); + win.setAttribute("opacity", seg(t, 0.2, 0.35)); win.setAttribute("transform", `translate(0 ${-30 * (1 - k)})`); + const a = io(seg(t, 1.0, 2.0)); + P.g.setAttribute("transform", `translate(${lerp(940, X + 200, a)} ${lerp(530, Y + 212, a) - Math.sin(a * Math.PI) * 20})`); + P.g.setAttribute("opacity", seg(t, 1.0, 1.2)); + press(P, seg(t, 2.1, 2.6)); + sw.set(io(seg(t, 2.25, 2.55))); + }; + }, + }; + + /* ---- 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/codex-computer-use/padlock.png b/blog/src/assets/media/codex-computer-use/padlock.png new file mode 100644 index 0000000000..339783b7a8 Binary files /dev/null and b/blog/src/assets/media/codex-computer-use/padlock.png differ diff --git a/blog/src/assets/media/codex-computer-use/window.png b/blog/src/assets/media/codex-computer-use/window.png new file mode 100644 index 0000000000..e51608c10e Binary files /dev/null and b/blog/src/assets/media/codex-computer-use/window.png differ diff --git a/blog/src/assets/media/cursor-alternatives/bookends.png b/blog/src/assets/media/cursor-alternatives/bookends.png index a942ba9a62..cf8fd562b9 100644 Binary files a/blog/src/assets/media/cursor-alternatives/bookends.png and b/blog/src/assets/media/cursor-alternatives/bookends.png differ diff --git a/blog/src/assets/media/cursor-alternatives/motion.js b/blog/src/assets/media/cursor-alternatives/motion.js index a01e35b058..a362a8bac0 100644 --- a/blog/src/assets/media/cursor-alternatives/motion.js +++ b/blog/src/assets/media/cursor-alternatives/motion.js @@ -39,7 +39,7 @@ el("path", { d: `M${PX} ${BASE - 46} L${PX - 78} ${BASE} M${PX} ${BASE - 46} L${PX + 78} ${BASE}`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round", fill: "none" }, rack); el("rect", { x: 104, y: RAIL - 11, width: 592, height: 22, rx: 11, fill: YEL, stroke: INK, "stroke-width": 3 }, rack); HX.forEach((x) => el("path", { d: `M${x} ${RAIL + 11} V${RAIL + 30}`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, rack)); - const LINES = ["Price: Individual $20, Pro Plus $60, Ultra $200 a month", "Licence: its public GitHub repo carried no licence", "Plan limits: Hobby is free with “Limited Agent requests”"]; + const LINES = ["Price: Plus $8, Premium $30, Ultra $200 a month", "Licence: its public GitHub repo carried no licence", "Plan limits: Plus runs Grok models only"]; const NAME = ["price", "licence", "plan limits"], COL = [YEL, LIL, SKY]; let pinned = -1; const coats = HX.map((x, i) => { @@ -66,7 +66,7 @@ c.g.setAttribute("transform", `translate(${c.x + 130 * (1 - oc(q))} ${RAIL + 22 - 30 * (1 - q * q)}) rotate(${-lean + sw})`); c.g.setAttribute("opacity", seg(tt, 0, 0.15) * (1 - out)); }); - cap.textContent = pinned >= 0 ? LINES[pinned] : t > 7.6 && t < 10.6 ? "Hobby free; Individual $20, Pro Plus $60, Ultra $200 a month" : last < 0 || t >= 10.6 ? "Three reasons: price, licence and plan limits" : LINES[last]; + cap.textContent = pinned >= 0 ? LINES[pinned] : t > 7.6 && t < 10.6 ? "Free $0; Plus $8, Premium $30, Ultra $200 a month" : last < 0 || t >= 10.6 ? "Three reasons: price, licence and plan limits" : LINES[last]; }; }, }; @@ -83,7 +83,7 @@ el("rect", { x: -76, y: -21, width: 152, height: 42, rx: 21, fill: MINT, stroke: INK, "stroke-width": 3 }, chip); txt(chip, 0, 7, "same height", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); el("rect", { x: 50, y: SH, width: 700, height: 16, rx: 8, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); - const book = (name, col, plan, right) => { + const book = (name, col, plan, right, price) => { const g = el("g", {}, svg), s = right ? -1 : 1, cx = (s * BW) / 2; el("rect", { x: right ? -BW : 0, y: -BH, width: BW, height: BH, rx: 10, fill: col, stroke: INK, "stroke-width": 3 }, g); el("path", { d: `M${cx - BW / 2} ${-BH + 26} h${BW} M${cx - BW / 2} ${-BH + 38} h${BW}`, stroke: INK, "stroke-width": 3 }, g); @@ -91,11 +91,11 @@ const p = el("g", {}, g); el("rect", { x: -58, y: -52, width: 116, height: 104, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, p); txt(p, 0, -24, plan, { "text-anchor": "middle", "font-size": 19, "font-weight": 500 }); - txt(p, 0, 12, "$20", { "text-anchor": "middle", "font-size": 34, "font-weight": 700 }); + txt(p, 0, 12, price, { "text-anchor": "middle", "font-size": 34, "font-weight": 700 }); txt(p, 0, 38, "a month", { "text-anchor": "middle", "font-size": 19, "font-weight": 500 }); return { g, p, cx }; }; - const A = book(["Cursor"], SKY, "Individual", false), B = book(["Devin", "Desktop"], LIL, "Pro", true); + const A = book(["Cursor"], SKY, "Premium", false, "$30"), B = book(["Devin", "Desktop"], LIL, "Pro", true, "$20"); const end = (right) => { const g = el("g", {}, svg); el("path", { d: `M0 0 V${-PL} a8 8 0 0 0 -16 0 V-16 H-112 a8 8 0 0 0 0 16 Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round", transform: right ? "scale(-1 1)" : "" }, g); return g; }; const EA = end(false), EB = end(true); const LBX = RX - BW / 2, LBY = 470; @@ -124,7 +124,7 @@ face.setAttribute("transform", `scale(1 ${Math.max(0.04, Math.min(Math.abs(Math.cos(Math.PI * u)), Math.abs(Math.cos(Math.PI * w))))})`); ft.textContent = side ? "Devin Desktop" : "Windsurf"; fr.setAttribute("fill", side ? YEL : "#fff"); note.textContent = side ? "new name" : "old name"; - cap.textContent = t < 3 || t >= 10.4 ? "Both are editors with agents built in" : t < 4.5 ? "Same shape" : t < 6.6 ? "Both list a plan at $20 a month: Cursor Individual, Devin Pro" : t < 8.4 ? "“Devin Desktop is the new name for Windsurf.”" : "We ran no test, so this is about shape and price"; + cap.textContent = t < 3 || t >= 10.4 ? "Both are editors with agents built in" : t < 4.5 ? "Same shape" : t < 6.6 ? "Several labs’ models: Cursor Premium $30, Devin Pro $20" : t < 8.4 ? "“Devin Desktop is the new name for Windsurf.”" : "This is about shape, not quality"; }; }, }; diff --git a/blog/src/assets/media/cursor-alternatives/rack.png b/blog/src/assets/media/cursor-alternatives/rack.png index b21e48ea62..2ba59e14cc 100644 Binary files a/blog/src/assets/media/cursor-alternatives/rack.png and b/blog/src/assets/media/cursor-alternatives/rack.png differ diff --git a/blog/src/assets/media/pi-agent-alternatives/dial.png b/blog/src/assets/media/pi-agent-alternatives/dial.png new file mode 100644 index 0000000000..7bb9a7cbb9 Binary files /dev/null and b/blog/src/assets/media/pi-agent-alternatives/dial.png differ diff --git a/blog/src/assets/media/pi-agent-alternatives/hero.png b/blog/src/assets/media/pi-agent-alternatives/hero.png new file mode 100644 index 0000000000..c1975dc84a Binary files /dev/null and b/blog/src/assets/media/pi-agent-alternatives/hero.png differ diff --git a/blog/src/assets/media/pi-agent-alternatives/keyring.png b/blog/src/assets/media/pi-agent-alternatives/keyring.png new file mode 100644 index 0000000000..e5d8be7736 Binary files /dev/null and b/blog/src/assets/media/pi-agent-alternatives/keyring.png differ diff --git a/blog/src/assets/media/pi-agent-alternatives/kit.png b/blog/src/assets/media/pi-agent-alternatives/kit.png new file mode 100644 index 0000000000..561bda481f Binary files /dev/null and b/blog/src/assets/media/pi-agent-alternatives/kit.png differ diff --git a/blog/src/assets/media/pi-agent-alternatives/motion.css b/blog/src/assets/media/pi-agent-alternatives/motion.css new file mode 100644 index 0000000000..5725a47703 --- /dev/null +++ b/blog/src/assets/media/pi-agent-alternatives/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Pi agent 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/pi-agent-alternatives/motion.js b/blog/src/assets/media/pi-agent-alternatives/motion.js new file mode 100644 index 0000000000..bf47be2ed9 --- /dev/null +++ b/blog/src/assets/media/pi-agent-alternatives/motion.js @@ -0,0 +1,248 @@ +/* Bright coded scenes for "Pi agent 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 GREY = "#D9D4DF", CAP = "#6B5878"; + const hint = (svg, x, y, s, anchor = "end") => txt(svg, x, y, s, { "text-anchor": anchor, "font-size": 16, fill: MUTE, "font-weight": 500 }); + const pill = (svg, x, y, w, label, fn) => { + const g = el("g", {}, svg); + el("rect", { x, y, width: w, height: 52, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: x - 6, y: y - 6, width: w + 12, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + txt(g, x + w / 2, y + 34, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + /* a name in the mono font, split onto two centred lines when it has a space */ + const name2 = (g, x, y, name, size, a = {}) => { + const parts = name.split(" "), gap = size + 3; + parts.forEach((p, j) => txt(g, x, y + (j - (parts.length - 1) / 2) * gap + size * 0.36, p, Object.assign({ "text-anchor": "middle", "font-family": MONO, "font-size": size, "font-weight": 700 }, a))); + }; + const PICKS = ["OpenCode", "Munder Difflin", "Claude Code", "Codex CLI", "Hermes Agent", "Goose", "Crush"]; + const BEST = ["More in the box, any model", "A team of agents, Pi included", "Permission modes, Claude models", "You already pay for ChatGPT", "Chat apps and schedules", "Code and workflows, desktop app", "Swap models mid session"]; + + const SC = {}; + + /* ---- 1. seven keys slide onto a ring that already holds the Pi key ---- */ + SC.keyring = { + dur: 11.4, still: 9.5, + build(svg, api) { + hint(svg, 774, 50, "tap a key"); + const C = [400, 300], R = 150, E = 240; + const at = (deg, r = R) => { const a = (deg * Math.PI) / 180; return [C[0] + Math.cos(a) * r, C[1] + Math.sin(a) * r]; }; + el("ellipse", { cx: 400, cy: 572, rx: 170, ry: 12, fill: SOFT }, svg); + el("rect", { x: 348, y: 20, width: 104, height: 22, rx: 9, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + [370, 430].forEach((x) => el("circle", { cx: x, cy: 31, r: 4, fill: "#fff", stroke: INK, "stroke-width": 2 }, svg)); + el("path", { d: "M400 42 V150", stroke: INK, "stroke-width": 8, "stroke-linecap": "round", fill: "none" }, svg); + el("circle", { cx: C[0], cy: C[1], r: R, fill: "none", stroke: INK, "stroke-width": 13 }, svg); + el("circle", { cx: C[0], cy: C[1], r: R, fill: "none", stroke: SOFT, "stroke-width": 6 }, svg); + const forced = PICKS.map(() => null).concat([null]); + const key = (fill, name, br, size, i) => { + const g = el("g", {}, svg), blade = el("g", {}, g), bow = el("g", {}, g); + el("path", { d: `M${br - 8} -8 H${br + 50} L${br + 58} -1 V3 H${br + 48} V11 H${br + 40} V3 H${br + 31} V12 H${br + 23} V3 H${br - 8} Z`, fill, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, blade); + el("circle", { r: br + 7, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, bow); + el("circle", { r: br, fill, stroke: INK, "stroke-width": 3 }, bow); + name2(bow, 0, 0, name, size); + button(g, `Lift the ${name} key`, () => { forced[i] = api.now(); api.poke(); }); + return { g, blade }; + }; + const place = (k, deg, lift, s, out) => { + const [x, y] = at(deg, R + lift); + k.g.setAttribute("transform", `translate(${x} ${y}) scale(${s})`); + k.blade.setAttribute("transform", `rotate(${deg + (out ? 0 : 180)})`); + }; + const pi = key(GREY, "Pi", 34, 24, 7); + const keys = PICKS.map((n, i) => key(i % 2 ? LIL : BLUE, n, 44, 15, i)); + const lifted = (i, now) => { if (forced[i] == null) return 0; const u = now - forced[i]; return oc(seg(u, 0, 0.25)) * (1 - io(seg(u, 1.3, 1.7))); }; + return (t, now) => { + const lp = lifted(7, now); + place(pi, -90, -22 * lp, 1 + 0.08 * lp, false); + keys.forEach((k, i) => { + const a = 0.6 + i * 0.95, m = io(seg(t, a, a + 0.85)), slot = -45 + 45 * i; + const ap = oc(seg(t, a - 0.2, a + 0.1)), gone = io(seg(t, 10.3 + (6 - i) * 0.05, 10.8 + (6 - i) * 0.05)), lv = lifted(i, now); + place(k, E + (slot - E) * m, 26 * lv + 40 * gone, (0.6 + 0.4 * ap) * (1 + 0.1 * lv), true); + k.g.setAttribute("opacity", ap * (1 - gone)); + }); + }; + }, + }; + + /* ---- 2. the Pi kit box opens on three empty slots ---- */ + SC.kit = { + dur: 10.5, still: 8.4, + build(svg, api) { + pill(svg, 646, 34, 112, "Open", () => api.seek(0)); + hint(svg, 758, 114, "tap Open"); + const X = 110, Wd = 420, HY = 210, TH = 130; + el("ellipse", { cx: X + Wd / 2, cy: 512, rx: 240, ry: 14, fill: SOFT }, svg); + const back = el("g", {}, svg); + el("rect", { x: X, y: HY, width: Wd, height: TH, rx: 6, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const labels = ["Subagents", "Plan mode", "Permissions"].map((s, i) => { + const x = X + 12 + i * 136; + el("rect", { x, y: HY + 17, width: 124, height: 96, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 2.5, "stroke-dasharray": "8 7" }, svg); + return txt(svg, x + 62, HY + 71, s, { "text-anchor": "middle", "font-size": 17, "font-weight": 700 }); + }); + el("rect", { x: X, y: HY + TH, width: Wd, height: 160, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + txt(svg, X + Wd / 2, HY + TH + 112, "Pi", { "text-anchor": "middle", "font-family": MONO, "font-size": 76, "font-weight": 700 }); + el("rect", { x: X + Wd / 2 - 22, y: HY + TH - 6, width: 44, height: 22, rx: 6, fill: YEL, stroke: INK, "stroke-width": 2.5 }, svg); + const front = el("g", {}, svg); + const lid = el("g", {}, front); + const face = el("rect", { x: X - 4, y: HY, width: Wd + 8, height: TH, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, lid); + const top = el("rect", { x: X + 24, width: Wd - 48, rx: 10, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "2 8", "stroke-linecap": "round" }, lid); + const tag = el("g", {}, svg), tagIn = el("g", {}, tag); + el("path", { d: "M20 43 C4 43 -10 32 -16 14", fill: "none", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, tagIn); + el("path", { d: "M28 0 H164 Q170 0 170 6 V80 Q170 86 164 86 H28 L0 58 V28 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, tagIn); + el("circle", { cx: 20, cy: 43, r: 7, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, tagIn); + txt(tagIn, 99, 38, "Add with", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(tagIn, 99, 64, "extensions", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + return (t) => { + const th = 115 * ob(seg(t, 0.6, 1.6)) * (1 - io(seg(t, 9.3, 10.1))), c = Math.cos((th * Math.PI) / 180); + const hh = Math.max(Math.abs(c) * TH, 6); + face.setAttribute("y", c >= 0 ? HY : HY - hh); face.setAttribute("height", hh); face.setAttribute("rx", Math.min(8, hh / 2)); + top.setAttribute("y", HY + 22 * c); top.setAttribute("height", Math.max(TH - 44, 0) * Math.max(c, 0.01)); + (c >= 0 ? front : back).appendChild(lid); + face.setAttribute("fill", c >= 0 ? "#fff" : "#E9E3F2"); top.setAttribute("opacity", c > 0.3 ? 1 : 0); + labels.forEach((n, i) => { const k = ob(seg(t, 1.7 + i * 0.45, 2.1 + i * 0.45)); n.setAttribute("opacity", cl(k * 2) * (1 - seg(t, 9.1, 9.4))); n.setAttribute("transform", `translate(0 ${10 * (1 - k)})`); }); + const d = seg(t, 3.4, 4.1), y = -160 + (232 + 160) * ob(d) - 34 * seg(t, 8.9, 9.3); + const k = seg(t, 4.0, 5.6), ang = 18 * (1 - k) * Math.exp(-2.5 * k) * Math.cos(9 * k) * (d >= 1 ? 1 : 0); + tag.setAttribute("transform", `translate(588 ${y})`); + tagIn.setAttribute("transform", `rotate(${ang} 20 43)`); + tag.setAttribute("opacity", d <= 0 ? 0 : 1 - seg(t, 8.9, 9.3)); + }; + }, + }; + + /* ---- 3. a selector dial steps through the seven stops ---- */ + SC.dial = { + dur: 11.6, still: 10, + build(svg, api) { + el("rect", { x: 30, y: 24, width: 740, height: 552, rx: 28, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + hint(svg, 744, 64, "tap the dial"); + const C = [400, 252], ST = [-210, -170, -130, -90, -50, -10, 30]; + const at = (deg, r) => { const a = (deg * Math.PI) / 180; return [C[0] + Math.cos(a) * r, C[1] + Math.sin(a) * r]; }; + const dots = ST.map((d, i) => { + const [x, y] = at(d, 134), [lx, ly] = at(d, 160), cx = Math.cos((d * Math.PI) / 180); + const anchor = Math.abs(cx) < 0.2 ? "middle" : cx < 0 ? "end" : "start"; + txt(svg, lx, ly + 7, PICKS[i], { "text-anchor": anchor, "font-family": MONO, "font-size": 18, "font-weight": 700 }); + return el("circle", { cx: x, cy: y, r: 9, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + }); + const g = el("g", {}, svg), knob = el("g", {}, g); + el("circle", { cx: C[0], cy: C[1], r: 118, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, g); + el("circle", { r: 108, fill: YEL, stroke: INK, "stroke-width": 3 }, knob); + for (let i = 0; i < 30; i++) { const a = (i / 30) * Math.PI * 2; el("path", { d: `M${Math.cos(a) * 96} ${Math.sin(a) * 96} L${Math.cos(a) * 106} ${Math.sin(a) * 106}`, stroke: INK, "stroke-width": 2, "stroke-linecap": "round" }, knob); } + el("circle", { r: 74, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, knob); + el("path", { d: "M-18 0 H62", stroke: INK, "stroke-width": 9, "stroke-linecap": "round" }, knob); + el("circle", { r: 13, fill: SKY, stroke: INK, "stroke-width": 2.5 }, knob); + el("path", { d: "M122 0 L104 -10 L104 10 Z", fill: INK, "stroke-linejoin": "round", stroke: INK, "stroke-width": 2 }, knob); + el("rect", { x: 130, y: 418, width: 540, height: 118, rx: 20, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const body = el("g", {}, svg); + const nm = txt(body, 400, 466, "", { "text-anchor": "middle", "font-family": MONO, "font-size": 24, "font-weight": 700 }); + const ln = txt(body, 400, 508, "", { "text-anchor": "middle", "font-size": 21, "font-weight": 500 }); + el("tspan", { "font-weight": 700 }, ln).textContent = "Best for: "; + const lnT = el("tspan", {}, ln); + const auto = (t) => { let a = ST[0]; for (let i = 0; i < 6; i++) { const T = 1.0 + i * 1.35; a += 40 * ob(seg(t, T, T + 0.5)); } return a - 240 * io(seg(t, 10.4, 11.2)); }; + let man = null, shown = -1, changed = -99, lastA = ST[0]; + const idx = (a) => cl(Math.round((a - ST[0]) / 40), 0, 6); + button(g, "Dial on OpenCode. Step to the next stop", () => { + const from = lastA, nx = (idx(from) + 1) % 7; + man = { from, to: ST[nx], at: api.now(), wrap: nx === 0 }; api.poke(); + }); + return (t, now) => { + let a = auto(t); + if (man) { const u = seg(now - man.at, 0, man.wrap ? 0.8 : 0.5); a = man.from + (man.to - man.from) * (man.wrap ? io(u) : ob(u)); } + lastA = a; + knob.setAttribute("transform", `translate(${C[0]} ${C[1]}) rotate(${a})`); + const s = idx(a); + if (s !== shown) { + if (shown !== -1) changed = now; + shown = s; nm.textContent = PICKS[s]; lnT.textContent = BEST[s]; + g.setAttribute("aria-label", `Dial on ${PICKS[s]}, best for ${BEST[s]}. Step to the next stop`); + } + dots.forEach((d, i) => { d.setAttribute("fill", i === s ? MINT : "#fff"); d.setAttribute("r", i === s ? 11 : 8); }); + const f = 0.3 + 0.7 * oc(seg(now - changed, 0, 0.3)); + body.setAttribute("opacity", f); body.setAttribute("transform", `translate(0 ${8 * (1 - f)})`); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 4, still: 4, + build(svg) { + el("rect", { x: 64, y: 48, width: 168, height: 42, rx: 21, fill: LIL, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 148, 76, "Alternatives", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 62, 172, "Pi agent alternatives", { "font-size": 72, "font-weight": 700 }); + txt(svg, 64, 224, "7 coding agents to try", { "font-size": 30, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 262, "Checked 26 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const TW = 116, gap = 11, x0 = (960 - (7 * TW + 6 * gap)) / 2, cx = (i) => x0 + i * (TW + gap) + TW / 2; + const lines = el("path", { d: `M480 366 V392 M${cx(0)} 392 H${cx(6)} ` + PICKS.map((_, i) => `M${cx(i)} 392 V418`).join(" "), fill: "none", stroke: INK, "stroke-width": 2, "stroke-linecap": "round", "stroke-dasharray": "2 8" }, svg); + const pi = el("g", {}, svg); + el("rect", { x: 425, y: 308, width: 110, height: 58, rx: 14, fill: GREY, stroke: INK, "stroke-width": 3 }, pi); + txt(pi, 480, 347, "Pi", { "text-anchor": "middle", "font-family": MONO, "font-size": 28, "font-weight": 700 }); + const FILL = [YEL, BLUE, LIL, SKY, MINT, YEL, BLUE]; + const tiles = PICKS.map((name, i) => { + const g = el("g", {}, svg), x = x0 + i * (TW + gap); + el("rect", { x, y: 420, width: TW, height: 72, rx: 14, fill: FILL[i], stroke: INK, "stroke-width": 2.5 }, g); + name2(g, x + TW / 2, 456, name, 17); + return g; + }); + return (t) => { + const c0 = oc(seg(t, 0.1, 0.6)); + pi.setAttribute("opacity", c0); pi.setAttribute("transform", `translate(0 ${-16 * (1 - c0)})`); + lines.setAttribute("opacity", seg(t, 0.5, 0.9)); + tiles.forEach((g, i) => { const k = ob(seg(t, 0.7 + i * 0.22, 1.3 + i * 0.22)); g.setAttribute("opacity", cl(k * 2)); g.setAttribute("transform", `translate(${90 * (1 - k)} 0)`); }); + }; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/index.njk b/blog/src/index.njk index 813d337063..650a11db12 100644 --- a/blog/src/index.njk +++ b/blog/src/index.njk @@ -1,8 +1,8 @@ --- layout: base.njk title: The Munder Difflin Blog -description: "Guides, deep dives, and comparisons on running multi-agent Claude Code: orchestration, agent memory, automation, and the tooling landscape." -seoTitle: "Multi-agent & Claude Code blog — Munder Difflin" +description: "Guides, comparisons and pricing for AI agents and coding tools such as Claude Code, Codex and Cursor, each checked and dated. From the makers of Munder Difflin." +seoTitle: "Munder Difflin blog: AI agents and coding tools" permalink: /index.html --- {% block head %} diff --git a/blog/src/posts/claude-code-best-practices.md b/blog/src/posts/claude-code-best-practices.md index 500914ac74..1583fd8deb 100644 --- a/blog/src/posts/claude-code-best-practices.md +++ b/blog/src/posts/claude-code-best-practices.md @@ -65,7 +65,7 @@ cat error.log | claude -p "explain this error" ## 4. What should go in `CLAUDE.md`? -Only what Claude can't infer from code and needs in every session: Bash commands, code style that differs from defaults, testing instructions, repo etiquette. Run `/init` for a starter file and `/context` to confirm Claude loaded it. For each line, Anthropic's best practices page asks "Would removing this cause Claude to make mistakes?" If not, cut it. Anthropic's [memory docs](https://code.claude.com/docs/en/memory) say to target under 200 lines per file. Our view: a `CLAUDE.md` that reads like a policy manual gets read like one. See [give Claude Code long term memory](/blog/give-claude-code-long-term-memory/). +Only what Claude can't infer from code and needs in every session: Bash commands, code style that differs from defaults, testing instructions, repo etiquette. Run `/init` for a starter file and `/context` to confirm Claude loaded it. For each line, Anthropic's best practices page asks "Would removing this cause Claude to make mistakes?" If not, cut it. Anthropic's [memory docs](https://code.claude.com/docs/en/memory) say to target under 200 lines per file. Our view: a `CLAUDE.md` that reads like a policy manual gets read like one. See [give Claude Code long term memory](/blog/give-claude-code-long-term-memory/). Our [CLAUDE.md best practices](/blog/claude-md-best-practices/) page covers where each file lives and how `AGENTS.md` loads.
Animation. A clipboard holds a short checklist of four lines. Each box ticks itself in turn, and a fifth, longer line fades out and is struck through before it can be ticked.
Short beats long: Anthropic's memory docs say to target under 200 lines per file.
diff --git a/blog/src/posts/claude-md-best-practices.md b/blog/src/posts/claude-md-best-practices.md new file mode 100644 index 0000000000..21bf91eee6 --- /dev/null +++ b/blog/src/posts/claude-md-best-practices.md @@ -0,0 +1,113 @@ +--- +title: "CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)" +seoTitle: "CLAUDE.md Best Practices (Oct 2026): 8 Rules From Anthropic's Docs" +description: "CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads." +date: 2026-10-26 +category: guides +categoryLabel: Guides +type: Technical +primaryKeyword: "claude md best practices" +secondaryKeywords: ["claude.md best practices", "what to put in claude.md", "claude.md example", "claude.md vs agents.md", "how long should claude.md be"] +tags: ["Guides", "Claude Code", "Memory", "Context"] +faq: + - q: "How long should a `CLAUDE.md` file be?" + a: "Under 200 lines per file. Anthropic's memory docs say longer files use more context and reduce adherence, and that imports help you organise a long file but do not cut its context cost. Checked 26 Oct 2026." + - q: "Does Claude Code read `AGENTS.md`?" + a: "Yes, from Claude Code v2.1.277. By default it reads `AGENTS.md` only when there is no `CLAUDE.md` or `CLAUDE.local.md` in your working directory or above it. The Project instructions setting in /config can make it read both." + - q: "Should `CLAUDE.md` be committed to git?" + a: "The project `CLAUDE.md`, yes: Anthropic's best practices page says to check it into git so your team can contribute. Keep personal notes in `CLAUDE.local.md` and add that file to `.gitignore`." + - q: "Why does Claude ignore my `CLAUDE.md`?" + a: "Usually the file is too long or two lines disagree. Anthropic's docs say a rule gets lost in a long file and that Claude may pick one of two conflicting instructions arbitrarily. For a rule with no exceptions, use a hook." +--- + +The best `CLAUDE.md` is short, concrete and checked often: under 200 lines, only what Claude needs in every session, and hooks for anything that must never be skipped. These eight rules come from Anthropic's [memory docs](https://code.claude.com/docs/en/memory) and [best practices page](https://code.claude.com/docs/en/best-practices), read on 26 Oct 2026. + +Running several Claude Code agents? [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of AI agents for all kinds of knowledge work, coding included, on your own computer. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup. For habits beyond this file, see [Claude Code best practices](/blog/claude-code-best-practices/). + +## What is a `CLAUDE.md` file? + +A plain markdown file of instructions that Claude Code loads at the start of every session. The memory docs say each session "begins with a fresh context window", and `CLAUDE.md` is how your build commands, conventions and rules carry over. Claude writes a second kind of memory itself, auto memory, from your corrections. + +One line matters more than the rest: Claude treats `CLAUDE.md` "as context, not enforced configuration". It is advice Claude reads, not a lock. + +## Where should `CLAUDE.md` files live? + +Four places, loaded from broadest to most specific, per the memory docs on 26 Oct 2026: + +| Scope | Where | Shared with | +| --- | --- | --- | +| Managed policy | `/Library/Application Support/ClaudeCode/CLAUDE.md` on macOS | Everyone in the organisation | +| User | `~/.claude/CLAUDE.md` | Only you, every project | +| Project | `./CLAUDE.md` or `./.claude/CLAUDE.md` | Your team, through git | +| Local | `./CLAUDE.local.md`, added to `.gitignore` | Only you, this project | + +Files above your working folder load at launch, and files in subfolders load when Claude touches a file there. They add up rather than replace each other, so the line closest to where you started Claude is read last. + +
Animation. On a white background, four sheets of paper drop onto a pile one at a time, labelled Managed, User, Project and Local. Each sheet lands slightly offset so every label stays visible, and a small arrow beside the pile reads Read last at the top sheet.
Instruction files load from broadest to most specific and add up, per Anthropic's memory docs on 26 Oct 2026.
+ +## What should go in `CLAUDE.md`? + +Facts Claude should hold in every session and cannot work out from the code. The memory docs name build commands, conventions, project layout and "always do X" rules, and say to add a line when: + +- Claude makes the same mistake a second time. +- A code review catches something Claude should have known. +- You type the same correction you typed last session. +- A new teammate would need the same context. + +Write lines you could check. The docs give examples: "Use 2-space indentation" instead of "Format code properly", and "Run `npm test` before committing" instead of "Test your changes". Here is a short project file in that style: + +```markdown +# Build +- Run `npm run build` before you say a task is done. +- Run `npm test -- path/to/file` for one test file, not the whole suite. + +# Style +- TypeScript strict mode; no `any`. +- API handlers live in `src/api/handlers/`. + +# Rules +- Never edit files in `generated/`. +``` + +A procedure with many steps, or a rule for one corner of the repo, belongs in a [skill](/blog/claude-code-skills/) or a path scoped rule. + +## How long should `CLAUDE.md` be? + +Under 200 lines per file. The docs say longer files "consume more context and reduce adherence". Three tools keep it short: + +- **Path scoped rules.** Files in `.claude/rules/` with a `paths` field load only when Claude works on matching files. +- **Imports.** `@path/to/file` pulls in another file, up to four hops deep. This organises a long file but does not cut its context cost. +- **HTML comments.** Block comments are stripped before Claude sees the file, so notes for people cost no tokens. + +The best practices page asks of every line: "Would removing this cause Claude to make mistakes?" If not, cut it. + +
Animation. A tall white measuring tube fills with blue lines of text up to a yellow mark labelled 200 lines. Extra lilac lines float off to the side into three small trays labelled Rules, Skills and Imports, and the tube level settles below the mark.
Target under 200 lines per file; move the rest into rules or skills.
+ +## Does Claude Code read `AGENTS.md`? + +Yes, from v2.1.277, but not next to a `CLAUDE.md` by default. The memory docs say Claude reads `AGENTS.md` when you have no `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` in your working folder or above it. Add any one of those and `AGENTS.md` stops loading. + +To keep one file for every agent, import `AGENTS.md` from `CLAUDE.md`, or set **Project instructions** in `/config` to `claude-md-and-agents-md` so Claude reads both. Codex, OpenCode and Pi all read `AGENTS.md`, so one shared file keeps a mixed team in step. + +
Animation. A small blue cart rolls along a white rail toward a junction with two tracks, one to a card labelled Claude file and one to a card labelled Agents file. A yellow switch flips the cart toward the Agents file when the Claude file card fades out, then a third track appears labelled Both.
By default Claude Code reads the shared agents file only when it finds no Claude file of its own, per Anthropic's memory docs on 26 Oct 2026.
+ +## How do you keep `CLAUDE.md` up to date? + +Treat it like code. The best practices page says to check it into git and review it when things go wrong. Run `/init` for a starter file, `/context` to confirm it loaded and `/memory` to list and open your memory files. From v2.1.283, `/doctor prompt-audit` looks for stale instructions, missing files and contradictions, and changes nothing until you say so. + +If one rule keeps slipping, the page suggests adding "IMPORTANT" to that line alone. If a rule must hold every time, the memory docs point to a PreToolUse hook instead; see [Claude Code hooks explained](/blog/claude-code-hooks-explained/). + +## `CLAUDE.md` best practices at a glance, checked 26 Oct 2026 + +| Rule | From | +| --- | --- | +| Under 200 lines per file | Memory docs | +| Only what applies in every session | Best practices page | +| Concrete lines you could verify | Memory docs | +| Path scoped rules and skills for the rest | Memory docs | +| No two lines that disagree | Memory docs | +| Commit the project file, keep `CLAUDE.local.md` private | Both pages | +| Emphasis on the one line that slips, not many | Best practices page | +| Hooks for rules with no exceptions | Both pages | + + diff --git a/blog/src/posts/codex-computer-use.md b/blog/src/posts/codex-computer-use.md new file mode 100644 index 0000000000..ff92a9b6e1 --- /dev/null +++ b/blog/src/posts/codex-computer-use.md @@ -0,0 +1,96 @@ +--- +title: "Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)" +seoTitle: "Codex Computer Use (Oct 2026): Setup, Plans, Safety, vs Claude" +description: "Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares." +date: 2026-10-26 +category: guides +categoryLabel: Guides +type: Technical +primaryKeyword: "codex computer use" +secondaryKeywords: ["codex computer use mac", "codex computer use windows", "chatgpt computer use", "codex vs claude computer use", "how to enable computer use in codex"] +tags: ["Guides", "Codex", "Computer Use", "Desktop Apps"] +faq: + - q: "Is Codex computer use free?" + a: "Not on OpenAI's list. Its Codex pricing page lists Computer Use for Plus, Pro, Business, Enterprise and API keys, limited to some regions; Free and Go are not in that table, as of 26 Oct 2026. Plus is $20 a month." + - q: "Does Codex computer use work on Windows?" + a: "Yes, in the foreground. OpenAI's docs say it runs on the active desktop on Windows and cannot work in the background while you use the same session. Run the app in a Windows virtual machine if you want your own desktop free." + - q: "Can Codex use my computer while my Mac is locked?" + a: "Only with locked use turned on in Settings, on macOS. It installs an Apple authorization plugin and unlocks the Mac only for an active, trusted Computer Use turn, covering every display and relocking if it sees local input." + - q: "How is Claude Code computer use different?" + a: "Claude Code's computer use is a research preview on macOS for Pro and Max plans, not Team or Enterprise, per Anthropic's docs on 26 Oct 2026. You turn on a built in MCP server called computer-use from /mcp, and it needs an interactive session." +--- + +Codex computer use lets the ChatGPT desktop app see and operate other apps on macOS and Windows, through a plugin you install once. OpenAI's [Codex pricing page](https://learn.chatgpt.com/docs/pricing) lists it for Plus, Pro, Business, Enterprise and API keys in supported regions; Free and Go are not on that list. Checked 26 Oct 2026. + +Codex is also one of the engines in [Munder Difflin](https://harnessmd.com/download), free and open source: a desktop app that runs a team of AI agents for all kinds of knowledge work, coding included, on your own computer. The [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup. New to Codex? Start with [what is Codex](/blog/what-is-codex/). + +## What is Codex computer use? + +A way for Codex to work in apps that have no command line or plugin. OpenAI's [Computer Use docs](https://learn.chatgpt.com/docs/computer-use), read on 26 Oct 2026, say ChatGPT "can see and operate graphical user interfaces on macOS or Windows". The good fits they list: + +- Testing a macOS app, Windows app or iOS simulator flow that Codex is building. +- A task that needs your web browser. +- A bug that only shows up in the interface. +- Settings you can only change by clicking through an app. +- Work that spans more than one app. + +For a web app you are building locally, the docs say to try the built in browser first, and to prefer a plugin or MCP server when the app has one. Computer use is for what those cannot reach. + +
Animation. On a white background, a simple desktop window labelled Settings shows three toggles and a Save button. A blue pointer arrow glides in, flips the second toggle to mint, then clicks Save, and a small card beside the window reads Done in the app.
Computer use lets Codex see and operate an app's interface, per OpenAI's docs on 26 Oct 2026.
+ +## How do you turn on computer use in Codex? + +Four steps in the ChatGPT desktop app, from the docs: + +1. Select Codex, or select ChatGPT and switch to Work. +2. Open **Plugins > Computer Use** and select **Install plugin**, or **Enable** if shown. +3. Turn on the Computer Use server and skill toggles, then select **Try now**. +4. On macOS, grant **Screen Recording** so it can see apps and **Accessibility** so it can click, type and navigate. + +Then mention `@Computer` or an app, as in `@Chrome`, in your prompt and name the exact window or flow. The docs suggest GPT-6 Astra, where your model picker has it, for hard tasks that depend on screenshots. + +## Which plans include Codex computer use? + +Plus, Pro, Business, Enterprise and API keys, each marked limited because the feature is "currently limited to only specific regions", per the [pricing page](https://learn.chatgpt.com/docs/pricing) on 26 Oct 2026. Free and Go are not on its list. + +| Plan | Price as of 26 Oct 2026 | Computer use | +| --- | --- | --- | +| Free | $0 | Not listed | +| Go | $8 a month | Not listed | +| Plus | $20 a month | Yes, some regions | +| Pro | From $100 a month | Yes, some regions | +| Business, Enterprise, API key | Varies | Yes, some regions | + +The [Codex pricing](/blog/codex-pricing/) guide has the full plan table. + +## Is Codex computer use safe? + +It acts only in apps you allow, and it asks first. The docs say it requests permission before it uses an app, and **Always allow** saves that choice; you can remove apps later in **Settings > Computer Use**. It may also ask before "sensitive or disruptive actions". File edits and shell commands still follow the task's sandbox and approval settings. + +Admins can restrict which apps it reaches, block saved approvals, or switch it off with `[features].computer_use = false` in `requirements.toml`. On Windows, saved app choices live in `$CODEX_HOME/config.toml`. + +
Animation. A white approval card asks Use Calculator? with two buttons, Allow once and Always allow. Always allow lights up yellow, and the Calculator tile slides down into a shelf labelled Always allowed apps.
Codex asks before it uses an app; Always allow saves the choice, per OpenAI's docs on 26 Oct 2026.
+ +## Does it work on Windows and when the Mac is locked? + +On Windows, yes, in the foreground. The docs say it "can't operate in the background while you keep using the same Windows session", so expect it to move the pointer and type. Keep the target app visible, or run the desktop app in a Windows virtual machine. + +On macOS, it can work in the background, and **locked use** lets it use apps after the Mac locks, for a task you start from a connected device. You turn it on in Settings, and it installs an Apple authorization plugin. The docs say it unlocks only for an active, trusted Computer Use turn, covers every display while unlocked, and relocks if it sees local keyboard or pointer input. + +
Animation. A white laptop screen shows a large yellow padlock. A small blue tag reading Active task appears and the padlock opens briefly while a lilac cover spreads over the screen, then the padlock snaps shut again when a key press icon appears.
Locked use on macOS unlocks only for an active Computer Use turn and relocks on local input, per OpenAI's docs on 26 Oct 2026.
+ +## Codex computer use vs Claude Code computer use + +Both drive real apps; they differ on platform and plan. Checked 26 Oct 2026 against OpenAI's docs and Anthropic's [computer use docs](https://code.claude.com/docs/en/computer-use): + +| | Codex | Claude Code (CLI) | +| --- | --- | --- | +| Where | ChatGPT desktop app, macOS and Windows | Terminal, macOS; the Desktop app adds Windows | +| Status | Live in supported regions | Research preview | +| Plans | Plus, Pro, Business, Enterprise, API | Pro or Max, not Team or Enterprise | +| How to turn on | Install the Computer Use plugin | Enable the `computer-use` MCP server in `/mcp` | +| Background or headless | Background on macOS, locked use | No: needs an interactive session, not `-p` | + +Anthropic's docs add an order Claude follows: an MCP server first, then Bash, then Claude in Chrome for browser work, and computer use only when none of those fit. For a wider comparison, see [Codex CLI vs Claude Code](/blog/codex-cli-vs-claude-code/). + + diff --git a/blog/src/posts/cursor-alternatives.md b/blog/src/posts/cursor-alternatives.md index 5ec2fc6313..8ad7b883dd 100644 --- a/blog/src/posts/cursor-alternatives.md +++ b/blog/src/posts/cursor-alternatives.md @@ -1,7 +1,7 @@ --- title: "Cursor Alternatives: 7 AI Coding Tools to Switch To" seoTitle: "7 Cursor Alternatives in 2026: Free, Open Source, Cheaper" -description: "Seven Cursor alternatives checked on 8 Oct 2026: Claude Code, Codex, Cline, OpenCode, Zed, Devin Desktop and GitHub Copilot, by licence and price." +description: "Seven Cursor alternatives by licence and price: Claude Code, Codex, Cline, OpenCode, Zed, Devin Desktop and GitHub Copilot. Cursor plans rechecked 26 Oct 2026." date: 2026-10-08 category: comparisons categoryLabel: Comparisons @@ -11,15 +11,15 @@ secondaryKeywords: ["cursor alternative", "open source cursor alternative", "fre tags: ["Comparisons", "Cursor", "Editors", "Open Source", "Cost"] faq: - q: "What is the best Cursor alternative?" - a: "We think Devin Desktop is the closest swap. Its own page says it is the new name for Windsurf, an editor with agents built in. On 8 Oct 2026 its pricing page listed a Free plan and Pro at $20 per month, the same price as Cursor's Individual plan. We ran no test, so this is about shape and price. If you want the editor to be open source, pick Zed." + a: "We think Devin Desktop is the closest swap. Its own page says it is the new name for Windsurf, an editor with agents built in. On 8 Oct 2026 its pricing page listed a Free plan and Pro at $20 per month. On 26 Oct 2026 Cursor's pricing page listed Plus at $8 a month with Grok models only and Premium at $30. If you want the editor to be open source, pick Zed." - q: "Is there a free Cursor alternative?" a: "Yes. On 8 Oct 2026 Zed's pricing page listed its Personal plan at $0 forever, Cline's said it is free for individual developers, and OpenCode's site said free models are included. Devin Desktop has a Free plan with a light quota and GitHub Copilot has a Free plan with limited chat and agent usage. With Cline you still pay for AI inference." - q: "Is there an open source alternative to Cursor?" a: "Yes, four of the seven in the sources we opened. On 8 Oct 2026 GitHub listed Codex CLI and Cline under Apache 2.0 and OpenCode under MIT. Zed's README says its source is licensed primarily under GPL-3.0-or-later, with Apache-2.0 components where marked." - q: "Is there a cheaper alternative to Cursor?" - a: "Yes, on the first paid plan. On 8 Oct 2026 Cursor's Individual plan was $20 a month. Zed Pro and GitHub Copilot Pro were each $10 per month, and OpenCode Go was $10 a month, per each vendor's own page. Zed Pro includes $5 of tokens and bills by usage beyond that, so a heavy month can cost more." + a: "It depends on the models you need. On 26 Oct 2026 Cursor's Plus plan was $8 a month with Grok models only, and Premium, which adds other labs' models billed at API list price, was $30. On 8 Oct 2026 Zed Pro and GitHub Copilot Pro were each $10 per month, and OpenCode Go was $10 a month, per each vendor's own page. Zed Pro includes $5 of tokens and bills by usage beyond that, so a heavy month can cost more." - q: "Can I use Cursor and another coding agent together?" - a: "Yes. OpenAI's Codex README says its IDE extension installs in Cursor, and Anthropic's overview has an install link for Cursor too. Munder Difflin is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer." + a: "Yes. OpenAI's Codex README says its IDE extension installs in Cursor, and Anthropic's overview has an install link for Cursor too. Munder Difflin is free and open source: a desktop app that runs a team of AI agents for all kinds of knowledge work, coding included, on your own computer." --- Our picks for a Cursor alternative are Devin Desktop (the new name for Windsurf) if you want the closest swap, Zed if you want an open source editor, and Claude Code or Codex if you would rather hand work to an agent. Checked 8 Oct 2026. @@ -28,11 +28,11 @@ This list is for people who use Cursor, or nearly did, and want something else. ## Why look for a Cursor alternative? -We see three reasons people look elsewhere: price, licence and plan limits. On 8 Oct 2026, [Cursor's pricing page](https://cursor.com/pricing) listed Hobby as free with "Limited Agent requests" and Individual at $20 a month. Its [models and pricing docs](https://cursor.com/docs/models-and-pricing) put Pro Plus at $60 and Ultra at $200 a month, and say that when you exceed your included monthly usage you add on-demand usage or upgrade. [Cursor pricing](/blog/cursor-pricing/) covers the 12 Oct changes. +We see three reasons people look elsewhere: price, licence and plan limits. Since 12 Oct 2026 Cursor is sold as part of The X Subscription. On 26 Oct 2026 [Cursor's pricing page](https://cursor.com/pricing) listed Free at $0, Plus at $8 a month with "Only Grok models", Premium at $30, Super at $100 and Ultra at $200; other labs' models start on Premium at API list price. Older plans keep working. [Cursor pricing](/blog/cursor-pricing/) has the full table. -
Animation. An empty coat rack with three hooks. Three coats swing in and hang up one by one, labelled price, licence and plan limits, and the rack leans a little further with each coat.
Three reasons, one rack. Plans from Cursor's pricing page and docs, 8 Oct 2026.
+
Animation. An empty coat rack with three hooks. Three coats swing in and hang up one by one, labelled price, licence and plan limits, and the rack leans a little further with each coat.
Three reasons, one rack. Plans from Cursor's pricing page, 26 Oct 2026.
-Cursor's [public GitHub repo](https://github.com/cursor/cursor) carried no licence on 8 Oct 2026. If none of that bothers you, stay. See also [Claude Code vs Cursor](/blog/claude-code-vs-cursor/) and our [Claude Code alternatives](/blog/claude-code-alternatives/). +Cursor's [public GitHub repo](https://github.com/cursor/cursor) carried no licence on 8 Oct 2026. See also [Claude Code vs Cursor](/blog/claude-code-vs-cursor/) and our [Claude Code alternatives](/blog/claude-code-alternatives/). ## Seven alternatives, plus a different kind of tool @@ -40,7 +40,7 @@ The Best for lines are our view. **1. Claude Code.** [Claude Code](https://code.claude.com/docs/en/overview) is, in Anthropic's words, "an agentic coding tool that reads your codebase, edits files, runs commands, and integrates with your development tools". Its licence file says "All rights reserved". [Anthropic's pricing page](https://claude.com/pricing) includes it in Pro, $20 if billed monthly. Best for: handing over whole tasks. See [Claude Code pricing](/blog/how-much-does-claude-code-cost/). -**2. Munder Difflin.** [Munder Difflin](https://harnessmd.com/download) is what we make, and it is not a replacement for Cursor. It is a different kind of tool: a free and open source desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It fits you if you want several agents working at once. Start with [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). +**2. Munder Difflin.** [Munder Difflin](https://harnessmd.com/download) is what we make, and it is not a replacement for Cursor. It is free and open source: a desktop app that runs a team of AI agents for all kinds of knowledge work, coding included, on your own computer. It fits you if you want several agents working at once. Start with [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). **3. Codex.** [Codex CLI](https://github.com/openai/codex) is "a coding agent from OpenAI that runs locally on your computer", with an IDE extension too. The CLI code is Apache 2.0. On OpenAI's [pricing page](https://learn.chatgpt.com/docs/pricing), Plus, $20 a month, is the first plan that lists the CLI and IDE extension. Best for: OpenAI models in the editor you already have. See [what is Codex](/blog/what-is-codex/). @@ -60,7 +60,7 @@ Each row comes from the tool's own site or GitHub repo. The prices are our selec | Tool | Type | Licence | Price to start | Models | | --- | --- | --- | --- | --- | -| Cursor | Editor | None on its repo. | Hobby, free; Individual, $20 a month. | OpenAI, Anthropic, Google, SpaceXAI, and more. | +| Cursor | Editor | None on its repo. | Free; Plus, $8 a month (26 Oct 2026). | Grok only on Plus; other labs from Premium at API list price (26 Oct 2026). | | Claude Code | Terminal, IDE, desktop, browser | All rights reserved. | Claude Pro, $20 if billed monthly. | Claude. | | Munder Difflin | Desktop app for a team of agents | Open source. | Free and open source. | Whatever your agents run. | | Codex | Terminal and IDE extension | Apache 2.0 (CLI). | Plus, $20 a month, for the CLI and IDE extension. | OpenAI. | @@ -72,17 +72,17 @@ Each row comes from the tool's own site or GitHub repo. The prices are our selec ## What is the closest alternative to Cursor? -Devin Desktop is the closest, we think. Both are editors with agents built in, and both pair a limited free plan with a plan at $20 a month (Cursor Individual, Devin Pro) on 8 Oct 2026. We ran no test, so this is about shape and price, not quality. +Devin Desktop is the closest, we think. Both are editors with agents built in, both have a limited free plan, and both reach models from several labs on a paid plan: Devin Pro at $20 a month (8 Oct 2026), and Cursor Premium at $30, with other labs' models billed at API list price (26 Oct 2026). This is about shape, not quality. -
Animation. Two bookends slide together and hold two books upright, one marked Cursor and one marked Devin Desktop. The books are the same height and each spine shows 20 dollars. A small label under the second book flips from Windsurf to Devin Desktop.
Same shape, same $20 plan. Prices from each vendor's pricing page, 8 Oct 2026.
+
Animation. Two bookends slide together and hold two books upright, one marked Cursor and one marked Devin Desktop. The books are the same height; the Cursor spine shows Premium, 30 dollars, and the Devin Desktop spine shows Pro, 20 dollars. A small label under the second book flips from Windsurf to Devin Desktop.
Same shape: editors with agents built in. Cursor from its pricing page on 26 Oct 2026, Devin on 8 Oct 2026.
## Is there a free Cursor alternative? -Yes: five of the seven list a free way in. Those are Zed Personal, Cline, OpenCode's free models, Devin Desktop Free with its light quota, and Copilot Free with limited chat and agent usage. OpenAI's Free plan lists "GPT-6 Luna at Standard speed in the desktop app, subject to rollout", so we do not count it. +Yes: five of the seven list a free way in. Those are Zed Personal, Cline, OpenCode's free models, Devin Desktop Free with its light quota, and Copilot Free with limited chat and agent usage.
Animation. A vending machine with seven slots, one for each tool. Five slots light up with a tag reading free and drop a small box into the tray one by one. The slots for Claude Code and Codex CLI stay dark and show 20 dollars.
Five free ways in; Claude Code and the Codex CLI sit on $20 plans, Claude Pro if billed monthly. From each vendor's pricing page, 8 Oct 2026.
-Free software is not a free model: with Cline you still pay for AI inference. Cursor has a free Hobby plan of its own, too. +Cursor has a Free plan of its own, too. ## Is there an open source Cursor alternative? @@ -90,7 +90,7 @@ Yes, four of the seven list an open source licence in the sources above: Codex C ## Who should pick what? -Our view: pick by the reason you are leaving. Price alone rarely settles it: three of these list a $20 plan, Claude Pro if billed monthly, the same as Cursor (8 Oct 2026). +Our view: pick by the reason you are leaving. Price alone rarely settles it: three of these list a $20 plan, Claude Pro if billed monthly (8 Oct 2026), between Cursor Plus at $8 and Premium at $30 (26 Oct 2026).
Animation. A train departure board with seven rows. The letter tiles flip and settle row by row, each row showing a reason to leave on the left and the tool or tools for it on the right.
Seven reasons, seven rows: read your row.
diff --git a/blog/src/posts/pi-agent-alternatives.md b/blog/src/posts/pi-agent-alternatives.md new file mode 100644 index 0000000000..db8b525068 --- /dev/null +++ b/blog/src/posts/pi-agent-alternatives.md @@ -0,0 +1,88 @@ +--- +title: "Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)" +seoTitle: "Pi Agent Alternatives (Oct 2026): 7 Coding Agents Compared" +description: "Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices." +date: 2026-10-26 +category: comparisons +categoryLabel: Comparisons +type: Non-technical +primaryKeyword: "pi agent alternatives" +secondaryKeywords: ["pi agent alternative", "pi coding agent alternatives", "alternatives to pi agent", "pi.dev alternative", "pi agent vs opencode"] +tags: ["Comparisons", "Pi", "CLI Agents", "Open Source"] +faq: + - q: "What is the best Pi agent alternative?" + a: "OpenCode, if you want another open source terminal agent on any model. Its site lists 75+ LLM providers, local models included, and says OpenCode is 100% free to use, as of 26 Oct 2026." + - q: "Is there a Pi alternative with permissions built in?" + a: "Yes. Claude Code has permission modes, from Manual, which asks before edits and commands, to auto, which runs actions with background safety checks. Codex CLI runs in a sandbox and asks before it leaves the workspace or uses the network. Pi's README says it does not include a built in permission system and points to containers or a sandbox instead, as of 26 Oct 2026." + - q: "Can I keep Pi and use other agents too?" + a: "Yes. Munder Difflin is free and open source: a desktop app that runs a team of AI agents for all kinds of knowledge work, coding included, on your own computer. Pi is one of the CLIs it runs, next to Claude Code, Codex and OpenCode." + - q: "Which Pi alternative works outside the terminal?" + a: "Hermes Agent, Munder Difflin and Goose. Munder Difflin is a desktop app. Hermes Agent's README says it runs in Telegram, Discord, Slack, WhatsApp, Signal and the CLI from one gateway. Goose ships a desktop app and a CLI. Both checked 26 Oct 2026." +--- + +The best Pi agent alternatives, checked 26 Oct 2026: OpenCode for another open source agent on any model, then [Munder Difflin](https://harnessmd.com/download), free and open source: a desktop app that runs a team of AI agents for all kinds of knowledge work, coding included, on your own computer. Also: Claude Code, Codex CLI, Hermes Agent, Goose and Crush. + +
Animation. On a white background, a large round key ring hangs from a hook. A small grey key labelled Pi sits at the top, and seven blue and lilac keys slide onto the ring one by one, labelled OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush.
Seven Pi agent alternatives, each checked on its own GitHub page or site on 26 Oct 2026.
+ +New to Pi itself? Start with [what is Pi agent](/blog/what-is-pi-agent/). This list sits in our [Comparisons hub](/blog/topics/comparisons/). + +## Why look for a Pi agent alternative? + +Our view: people look past Pi when they want features in the box rather than features they build. Pi's [README](https://github.com/earendil-works/pi), read on 26 Oct 2026, is clear about the trade: + +- **Small on purpose.** It calls Pi "a minimal, extensible agent harness that you can make your own", and says it "skips features like sub-agents and plan mode". You add them with extensions or packages. +- **No permission system.** In its words: "Pi does not include a built-in permission system for restricting filesystem, process, network, or credential access." It runs with your user's permissions and points to containers or a sandbox for limits. +- **Terminal and code.** Pi works in the terminal, in print or JSON mode, over RPC and through a TypeScript SDK. Chat apps, schedules and work beyond code need something else. + +None of this is a flaw for people who like to build their own agent. Pi is MIT licensed, and release v1.4.0 shipped on 21 Oct 2026. + +
Animation. A small white kit box labelled Pi opens to show three empty dashed slots labelled Subagents, Plan mode and Permissions. A yellow tag drops beside them reading Add with extensions.
Pi skips subagents and plan mode and has no built in permission system, per its README on 26 Oct 2026.
+ +## Which Pi agent alternatives are worth a look? + +Seven tools pass, each live with a release in October 2026. + +**1. OpenCode.** [OpenCode](https://github.com/anomalyco/opencode) is "The open source AI coding agent", MIT licensed, in the terminal and as a desktop app in beta, with release v1.19.4 on 22 Oct 2026. Its [site](https://opencode.ai) lists "75+ LLM providers", local models included, and says "OpenCode is 100% free to use" (26 Oct 2026). Best for: a fuller open source agent on any model. See [OpenCode alternatives](/blog/opencode-alternatives/). + +**2. Munder Difflin.** Keep Pi and add more agents. [Munder Difflin](https://github.com/HarnessMD/munder-difflin) is MIT licensed and runs Pi as one of its CLIs, next to Claude Code, Codex, OpenCode, Gemini CLI and Copilot. Each agent gets its own terminal, and they share a task board; the [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup. Best for: a team of agents for research, writing, ops and code. + +**3. Claude Code.** Anthropic's agent "reads your codebase, edits files, runs commands, and integrates with your development tools", per its [docs](https://code.claude.com/docs/en/overview). It runs Claude models and has permission modes built in. [Anthropic's pricing page](https://claude.com/pricing) includes it in Pro, $20 if billed monthly, as of 26 Oct 2026. Best for: Claude with permissions built in. + +**4. Codex CLI.** [Codex CLI](https://github.com/openai/codex) is "a coding agent from OpenAI that runs locally on your computer", Apache 2.0. On ChatGPT plans the CLI starts at Plus, $20 a month, per OpenAI's [Codex pricing page](https://learn.chatgpt.com/docs/pricing) on 26 Oct 2026. Best for: people who already pay for ChatGPT. See [Codex alternatives](/blog/codex-alternatives/). + +**5. Hermes Agent.** [Hermes Agent](https://github.com/NousResearch/hermes-agent) is "The self-improving AI agent built by Nous Research", MIT licensed, release v0.22.5 on 23 Oct 2026. Its README says "Use any model you want", lists a built in cron scheduler and subagents, and runs in Telegram, Discord, Slack, WhatsApp, Signal and the CLI. Best for: an agent that lives in your chat apps. See [what is Hermes Agent](/blog/what-is-hermes-agent/). + +**6. Goose.** [Goose](https://github.com/aaif-goose/goose) is "a general-purpose AI agent that runs on your machine", as a desktop app and a CLI, and "works with 15+ providers", Ollama included. Apache 2.0, release v1.56.0 on 23 Oct 2026. Best for: one agent for code and other work. + +**7. Crush.** [Crush](https://github.com/charmbracelet/crush) from Charm is a terminal agent that lets you "switch LLMs mid-session while preserving context" and add tools through MCP. It uses the Functional Source License 1.1 with an MIT future licence, release v0.98.2 on 23 Oct 2026. Best for: a polished terminal agent with LSP context. + +## Pi agent alternatives compared, checked 26 Oct 2026 + +| Tool | Licence | Models | Price as of 26 Oct 2026 | +| --- | --- | --- | --- | +| Pi | MIT | 15+ providers, Ollama included | Free; you pay your model provider. | +| OpenCode | MIT | 75+ LLM providers, local included | Free to use. | +| Munder Difflin | MIT | The CLIs you run, Pi included | Free and open source. | +| Claude Code | None listed on GitHub | Claude | In Pro, $20 if billed monthly (26 Oct 2026). | +| Codex CLI | Apache 2.0 | OpenAI | CLI from Plus, $20 a month (26 Oct 2026), or an API key. | +| Hermes Agent | MIT | Any model, Nous Portal and OpenRouter among them | Free; you pay your model provider. | +| Goose | Apache 2.0 | 15+ providers, Ollama included | Free and open source. | +| Crush | FSL 1.1, MIT future licence | OpenAI or Anthropic compatible APIs | Free; you pay your model provider. | + +The rows are our selection. Licences come from GitHub and prices from each vendor's page, all read on 26 Oct 2026. Pi's provider count is from our [Pi explainer](/blog/what-is-pi-agent/), checked 2 Oct 2026. + +
Animation. A big round selector dial on a white card has seven stops labelled OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush. The pointer turns from stop to stop, and a small blue card under the dial shows each tool's Best for line.
Pick by who you already pay and what the work is.
+ +## Which Pi agent alternative should you pick? + +Pick by what you missed in Pi. + +- **OpenCode** if you want Pi's openness with more in the box. +- **Munder Difflin** if you want to keep Pi and run other agents beside it for any knowledge work, coding included. +- **Claude Code** if you want permission modes and Anthropic's models. +- **Codex CLI** if you already pay for ChatGPT. +- **Hermes Agent** for an agent you reach from chat apps, on a schedule. +- **Goose** for one agent across code and workflows, with a desktop app. +- **Crush** for a terminal agent that swaps models mid session. + + diff --git a/blog/src/posts/what-is-codex.md b/blog/src/posts/what-is-codex.md index 89bba9b89b..0f0c6aa2b8 100644 --- a/blog/src/posts/what-is-codex.md +++ b/blog/src/posts/what-is-codex.md @@ -59,7 +59,7 @@ Codex Cloud runs a task in its own workspace on OpenAI's machines. OpenAI's [clo ## What is Codex used for? -Codex is used for the jobs you would hand a colleague with repo access: fixing a bug, building a feature, explaining an unfamiliar codebase or reviewing a pull request. The API key option, in the pricing page's words, is "great for automation in shared environments like CI." Codex Cloud suits work that outlasts your attention span. +Codex is used for the jobs you would hand a colleague with repo access: fixing a bug, building a feature, explaining an unfamiliar codebase or reviewing a pull request. The API key option, in the pricing page's words, is "great for automation in shared environments like CI." Codex Cloud suits work that outlasts your attention span. To test apps that have no command line, the desktop app adds [computer use](/blog/codex-computer-use/). ## Is Codex free? diff --git a/blog/src/posts/what-is-pi-agent.md b/blog/src/posts/what-is-pi-agent.md index 156a65eae1..1e774a4ef0 100644 --- a/blog/src/posts/what-is-pi-agent.md +++ b/blog/src/posts/what-is-pi-agent.md @@ -107,7 +107,7 @@ Pi if you want an agent you reshape yourself, Claude Code for Anthropic's own ag **Pick Claude Code if** you're on a Claude plan and want Anthropic's agent with permissions built in. -**Pick Codex CLI or OpenCode if** you want guard rails out of the box: approval modes in Codex, a read only Plan agent in OpenCode. Our [what is OpenCode](/blog/what-is-opencode/) explainer and [Codex CLI vs Claude Code](/blog/codex-cli-vs-claude-code/) cover both. +**Pick Codex CLI or OpenCode if** you want guard rails out of the box: approval modes in Codex, a read only Plan agent in OpenCode. Our [what is OpenCode](/blog/what-is-opencode/) explainer and [Codex CLI vs Claude Code](/blog/codex-cli-vs-claude-code/) cover both. For seven more options, see [Pi agent alternatives](/blog/pi-agent-alternatives/). ## Can you run Pi agents inside Munder Difflin? diff --git a/docs/blog/agent-security-and-sandboxing/index.html b/docs/blog/agent-security-and-sandboxing/index.html index 8158798ee6..ff8538b98e 100644 --- a/docs/blog/agent-security-and-sandboxing/index.html +++ b/docs/blog/agent-security-and-sandboxing/index.html @@ -327,50 +327,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-23/index.html b/docs/blog/agent-tools-today-2026-10-23/index.html index bcd206a928..756c587b69 100644 --- a/docs/blog/agent-tools-today-2026-10-23/index.html +++ b/docs/blog/agent-tools-today-2026-10-23/index.html @@ -235,7 +235,7 @@

How this list is built

- +
- + 4 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
4 min
-

Is Claude Duet Good for Developers? What It Does With Code and What It Doesn't

-

Claude Duet can prototype and build web apps, but it is built for business apps, not your repo. What developers get from it, and where Claude Code fits better.

+

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

+

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

Comparisons - Read → + Read →
diff --git a/docs/blog/ai-agent-use-cases-our-office/index.html b/docs/blog/ai-agent-use-cases-our-office/index.html index d8f1d10fe4..0a3897664c 100644 --- a/docs/blog/ai-agent-use-cases-our-office/index.html +++ b/docs/blog/ai-agent-use-cases-our-office/index.html @@ -422,50 +422,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/aider-vs-claude-code/index.html b/docs/blog/aider-vs-claude-code/index.html index 721d2a2345..a707aa9852 100644 --- a/docs/blog/aider-vs-claude-code/index.html +++ b/docs/blog/aider-vs-claude-code/index.html @@ -307,34 +307,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/asd-ste100-prompting/index.html b/docs/blog/asd-ste100-prompting/index.html index faba314014..6fd2a55686 100644 --- a/docs/blog/asd-ste100-prompting/index.html +++ b/docs/blog/asd-ste100-prompting/index.html @@ -365,50 +365,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/assets/media/claude-md-best-practices/gauge.png b/docs/blog/assets/media/claude-md-best-practices/gauge.png new file mode 100644 index 0000000000..bffe839b05 Binary files /dev/null and b/docs/blog/assets/media/claude-md-best-practices/gauge.png differ diff --git a/docs/blog/assets/media/claude-md-best-practices/hero.png b/docs/blog/assets/media/claude-md-best-practices/hero.png new file mode 100644 index 0000000000..2e586a857b Binary files /dev/null and b/docs/blog/assets/media/claude-md-best-practices/hero.png differ diff --git a/docs/blog/assets/media/claude-md-best-practices/junction.png b/docs/blog/assets/media/claude-md-best-practices/junction.png new file mode 100644 index 0000000000..e954079c60 Binary files /dev/null and b/docs/blog/assets/media/claude-md-best-practices/junction.png differ diff --git a/docs/blog/assets/media/claude-md-best-practices/motion.css b/docs/blog/assets/media/claude-md-best-practices/motion.css new file mode 100644 index 0000000000..e7b1df8a0b --- /dev/null +++ b/docs/blog/assets/media/claude-md-best-practices/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "CLAUDE.md best practices". 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/docs/blog/assets/media/claude-md-best-practices/motion.js b/docs/blog/assets/media/claude-md-best-practices/motion.js new file mode 100644 index 0000000000..643337700f --- /dev/null +++ b/docs/blog/assets/media/claude-md-best-practices/motion.js @@ -0,0 +1,251 @@ +/* Bright coded scenes for "CLAUDE.md best practices". 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 CAP = "#6B5878"; + const hint = (svg, x, y, s) => txt(svg, x, y, s, { "text-anchor": "end", "font-size": 16, fill: MUTE, "font-weight": 500 }); + const pill = (svg, x, y, w, label, fn) => { + const g = el("g", {}, svg); + el("rect", { x, y, width: w, height: 52, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: x - 6, y: y - 6, width: w + 12, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + txt(g, x + w / 2, y + 34, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + const lift = (on, at, now) => (on ? oc(seg(now - at, 0, 0.35)) : 1 - oc(seg(now - at, 0, 0.35))); + + const SC = {}; + + /* ---- 1. four instruction files drop onto a pile, broadest first ---- */ + SC.stack = { + dur: 7.6, still: 6.6, + build(svg, api) { + hint(svg, 774, 50, "tap a sheet"); + const SH = [["Managed", LIL], ["User", SKY], ["Project", MINT], ["Local", YEL]]; + const X0 = 150, Y0 = 300, DX = 36, DY = -62, SW = 340, SHH = 230; + el("ellipse", { cx: X0 + SW / 2 + 54, cy: Y0 + SHH + 26, rx: 230, ry: 12, fill: SOFT }, svg); + const sheets = SH.map(([name, col], i) => { + const x = X0 + DX * i, y = Y0 + DY * i, g = el("g", {}, svg), body = el("g", {}, g); + el("rect", { x: x - 8, y: y - 8, width: SW + 16, height: SHH + 16, rx: 20, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "4 7", class: "mg-ring" }, body); + el("rect", { x: x + 6, y: y + 6, width: SW, height: SHH, rx: 14, fill: SOFT }, body); + el("rect", { x, y, width: SW, height: SHH, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, body); + [0, 1, 2, 3].forEach((r) => el("path", { d: `M${x + 28} ${y + 40 + r * 30} H${x + 28 + [250, 200, 270, 150][r]}`, stroke: SOFT, "stroke-width": 8, "stroke-linecap": "round" }, body)); + const cw = name.length * 12.5 + 34; + el("rect", { x: x + 22, y: y + SHH - 48, width: cw, height: 34, rx: 17, fill: col, stroke: INK, "stroke-width": 2.5 }, body); + txt(body, x + 22 + cw / 2, y + SHH - 24, name, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const s = { g, body, x, y, up: false, at: -99, cx: x + SW / 2, cy: y + SHH / 2 }; + button(g, `${name} sheet. Lift it`, () => { const now = api.now(); sheets.forEach((o) => { if (o !== s && o.up) { o.up = false; o.at = now; } }); s.up = !s.up; s.at = now; g.setAttribute("aria-pressed", s.up); api.poke(); }); + g.setAttribute("aria-pressed", "false"); + return s; + }); + const arrow = el("g", {}, svg); + const top = sheets[3], ay = top.y + 76; + el("path", { d: `M770 ${ay} H${top.x + SW + 22} M${top.x + SW + 36} ${ay - 12} L${top.x + SW + 22} ${ay} L${top.x + SW + 36} ${ay + 12}`, stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, arrow); + txt(arrow, (770 + top.x + SW + 22) / 2 + 6, ay - 18, "Read last", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + return (t, now) => { + const out = seg(t, 7.1, 7.6); + sheets.forEach((s, i) => { + const t0 = 0.4 + i * 1.0, k = ob(seg(t, t0, t0 + 0.75)), seen = seg(t, t0, t0 + 0.12); + const L = lift(s.up, s.at, now); + const dy = -340 * (1 - k) - 14 * L, dx = -130 * L, r = 4 * (1 - cl(k)) * (i % 2 ? 1 : -1) - 3 * L; + s.body.setAttribute("transform", `translate(${dx} ${dy}) rotate(${r} ${s.cx} ${s.cy})`); + s.g.setAttribute("opacity", seen * (1 - out)); + }); + const a = oc(seg(t, 4.5, 5.1)); + arrow.setAttribute("opacity", a * (1 - out)); arrow.setAttribute("transform", `translate(${30 * (1 - a)} 0)`); + }; + }, + }; + + /* ---- 2. a measuring tube fills to the 200 line mark, extras go to trays ---- */ + SC.gauge = { + dur: 11, still: 9.8, + build(svg, api) { + const T = { l: 230, r: 370, top: 70, bot: 512 }, BASE = 497, GAP = 16, NB = 16, NL = 6, MARK = 244; + el("rect", { x: T.l + 8, y: T.top + 8, width: T.r - T.l, height: T.bot - T.top, rx: 26, fill: SOFT }, svg); + el("path", { d: `M${T.l} ${T.top} V${T.bot - 26} Q${T.l} ${T.bot} ${T.l + 26} ${T.bot} H${T.r - 26} Q${T.r} ${T.bot} ${T.r} ${T.bot - 26} V${T.top}`, fill: "#fff", stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + el("path", { d: `M${T.l - 12} ${T.top} H${T.l + 10} M${T.r - 10} ${T.top} H${T.r + 12}`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + for (let y = T.bot - 34; y > T.top + 20; y -= 32) el("path", { d: `M${T.r} ${y} H${T.r + (((T.bot - 34 - y) / 32) % 2 ? 10 : 18)}`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, svg); + const W = [96, 72, 88, 60, 100, 80, 68, 92, 76, 98, 64, 86, 74, 94, 70, 84, 90, 66, 80, 96, 72, 62]; + const lines = Array.from({ length: NB + NL }, (_, i) => ({ n: el("rect", { x: 0, y: -4.5, width: W[i], height: 9, rx: 4.5, fill: i < NB ? BLUE : LIL, stroke: INK, "stroke-width": 1.5 }, svg), d: i < NB ? 0.4 + i * 0.17 : 3.4 + (i - NB) * 0.22 })); + const mark = el("g", {}, svg); + el("path", { d: `M200 ${MARK} H${T.r + 22}`, stroke: INK, "stroke-width": 12, "stroke-linecap": "round" }, mark); + el("path", { d: `M200 ${MARK} H${T.r + 22}`, stroke: YEL, "stroke-width": 7, "stroke-linecap": "round" }, mark); + el("rect", { x: 62, y: MARK - 22, width: 140, height: 44, rx: 22, fill: YEL, stroke: INK, "stroke-width": 3 }, mark); + txt(mark, 132, MARK + 8, "200 lines", { "text-anchor": "middle", "font-size": 21, "font-weight": 700 }); + const TR = [["Rules", 112], ["Skills", 244], ["Imports", 376]].map(([name, y]) => { + el("rect", { x: 528, y: y + 6, width: 170, height: 52, rx: 10, fill: SOFT }, svg); + el("path", { d: `M520 ${y} V${y + 40} Q520 ${y + 52} 532 ${y + 52} H678 Q690 ${y + 52} 690 ${y + 40} V${y}`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + txt(svg, 605, y + 88, name, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + return y; + }); + pill(svg, 535, 510, 140, "Tidy", () => api.seek(0)); + return (t) => { + const settle = io(seg(t, 7.9, 8.7)), gap = GAP - 2 * settle, out = seg(t, 10.4, 11); + lines.forEach((L, i) => { + const k = oc(seg(t, L.d, L.d + 0.4)), bounce = Math.sin(seg(t, L.d + 0.4, L.d + 0.65) * Math.PI) * -4; + let x = T.l + 20, y = i < NB ? BASE - i * gap : BASE - i * GAP - 12, s = 1; + y = T.top - 10 + (y - T.top + 10) * k + bounce; + if (i >= NB) { + const j = i - NB, f0 = 5.4 + j * 0.3, f = io(seg(t, f0, f0 + 0.8)); + const tx = 545 + (j % 2) * 12, ty = TR[Math.floor(j / 2)] + 40 - (j % 2) * 13; + x = x + (tx - x) * f; y = y + (ty - y) * f - Math.sin(f * Math.PI) * 70; s = 1 - 0.18 * f; + } + L.n.setAttribute("transform", `translate(${x} ${y}) scale(${s})`); + L.n.setAttribute("opacity", seg(t, L.d, L.d + 0.08) * (1 - out)); + }); + const wob = Math.sin(seg(t, 4.8, 5.3) * Math.PI * 3) * 3 * (1 - seg(t, 4.8, 5.3)); + mark.setAttribute("transform", `translate(${wob} 0)`); + }; + }, + }; + + /* ---- 3. a cart takes the agents track when there is no Claude file ---- */ + SC.junction = { + dur: 10.5, still: 9.6, + build(svg, api) { + hint(svg, 774, 50, "tap the switch"); + const J = [330, 330], END = 556; + const branch = (dy) => `M50 330 H330 C390 ${330 + dy * 40} 410 ${330 + dy * 160} 480 ${330 + dy * 160} H${END}`; + const rail = (d, g) => [el("path", { d, stroke: INK, "stroke-width": 17, fill: "none", "stroke-linecap": "round" }, g), el("path", { d, stroke: "#fff", "stroke-width": 10, fill: "none", "stroke-linecap": "round" }, g)]; + const mid = el("g", {}, svg), midRail = rail(`M330 330 H${END}`, mid), midLen = END - 330; + rail(branch(-1), svg); rail(branch(1), svg); + for (let x = 70; x < 320; x += 30) el("path", { d: `M${x} 341 V346`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const up = el("path", { d: branch(-1), fill: "none" }, svg), dn = el("path", { d: branch(1), fill: "none" }, svg); + const L = up.getTotalLength(), JD = 280 - 46, STOP = L - 58; + const card = (y, h, label, col) => { + const g = el("g", {}, svg); + el("rect", { x: 566, y: y - h / 2 + 6, width: 200, height: h, rx: 18, fill: SOFT }, g); + el("rect", { x: 560, y: y - h / 2, width: 200, height: h, rx: 18, fill: col, stroke: INK, "stroke-width": 3 }, g); + txt(g, 660, y + 9, label, { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + const tick = el("g", {}, g); + el("circle", { cx: 756, cy: y - h / 2 + 4, r: 18, fill: MINT, stroke: INK, "stroke-width": 2.5 }, tick); + el("path", { d: `M747 ${y - h / 2 + 4} l6 6 l11 -12`, stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, tick); + return { g, tick }; + }; + const cC = card(170, 84, "Claude file", LIL), cA = card(490, 84, "Agents file", SKY), cB = card(330, 60, "Both", MINT); + cB.tick.remove(); + const sw = el("g", {}, svg), blade = el("g", {}, sw); + el("circle", { cx: J[0], cy: J[1], r: 46, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, sw); + el("rect", { x: J[0] - 6, y: J[1] - 7, width: 58, height: 14, rx: 7, fill: YEL, stroke: INK, "stroke-width": 2.5 }, blade); + el("circle", { cx: J[0], cy: J[1], r: 13, fill: YEL, stroke: INK, "stroke-width": 3 }, sw); + const cart = el("g", {}, svg), body = el("g", {}, cart); + el("rect", { x: -34, y: -50, width: 68, height: 32, rx: 9, fill: BLUE, stroke: INK, "stroke-width": 3 }, body); + el("path", { d: "M-20 -38 H20", stroke: "#fff", "stroke-width": 4, "stroke-linecap": "round" }, body); + [-18, 18].forEach((x) => el("circle", { cx: x, cy: -15, r: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, body)); + let user = null, lastS = 0, lastD = 0, lastOp = 1, prevT = 0; + button(sw, "Track switch. Flip it", () => { user = { to: lastS > 0.5 ? 0 : 1, s: lastS, d: lastD, op: lastOp, at: api.now() }; api.poke(); }); + const pt = (d, s) => { const a = up.getPointAtLength(d), b = dn.getPointAtLength(d); return [a.x + (b.x - a.x) * s, a.y + (b.y - a.y) * s]; }; + return (t, now) => { + if (t < prevT - 1) user = null; prevT = t; + const out = seg(t, 10, 10.5); + let s, d, opC; + if (user) { + const u = now - user.at, back = io(seg(u, 0, 0.8)), flip = io(seg(u, 0.8, 1.1)), go = io(seg(u, 1.2, 3)); + const from = Math.min(user.d, JD); + s = user.s + (user.to - user.s) * flip; + d = u < 1.2 ? user.d + (from - user.d) * back : from + (STOP - from) * go; + opC = user.op + (1 - 0.7 * user.to - user.op) * flip; + } else { + s = io(seg(t, 3.3, 3.8)); + d = JD * oc(seg(t, 0.5, 2.3)) + (STOP - JD) * io(seg(t, 3.9, 5.8)); + opC = 1 - 0.7 * seg(t, 2.5, 3.3); + } + lastS = s; lastD = d; lastOp = opC; + const [x, y] = pt(d, s), [x2, y2] = pt(Math.min(d + 2, L), s), ang = (Math.atan2(y2 - y, x2 - x) * 180) / Math.PI; + cart.setAttribute("transform", `translate(${x} ${y}) rotate(${ang})`); + cart.setAttribute("opacity", seg(t, 0, 0.3) * (1 - out)); + blade.setAttribute("transform", `rotate(${-34 + 68 * s} ${J[0]} ${J[1]})`); + sw.setAttribute("aria-pressed", s > 0.5); + cC.g.setAttribute("opacity", opC + (1 - opC) * out); + const arr = seg(d, STOP - 12, STOP); + cC.tick.setAttribute("opacity", arr * (1 - s) * (1 - out)); cA.tick.setAttribute("opacity", arr * s * (1 - out)); + const b = io(seg(t, 6.4, 7.4)), bc = ob(seg(t, 7.2, 7.7)); + midRail.forEach((n) => n.setAttribute("stroke-dasharray", `${midLen * b + 0.01} ${midLen + 40}`)); + mid.setAttribute("opacity", (b > 0 ? 1 : 0) * (1 - out)); + cB.g.setAttribute("opacity", cl(bc) * (1 - out)); cB.g.setAttribute("transform", `translate(${30 * (1 - cl(bc))} 0)`); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 8, still: 8, + build(svg) { + el("rect", { x: 64, y: 88, width: 110, height: 42, rx: 21, fill: SKY, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 119, 116, "Guide", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 60, 226, "CLAUDE.md", { "font-family": MONO, "font-size": 70, "font-weight": 700 }); + txt(svg, 60, 306, "best practices", { "font-size": 70, "font-weight": 700 }); + txt(svg, 64, 366, "What to write, where it lives, how long", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 408, "Checked 26 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const S = { x: 606, y: 140, w: 170, h: 260 }, sheet = el("g", {}, svg); + el("rect", { x: S.x + 9, y: S.y + 9, width: S.w, height: S.h, rx: 14, fill: SOFT }, sheet); + el("rect", { x: S.x, y: S.y, width: S.w, height: S.h, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, sheet); + const LW = [118, 92, 126, 78, 104]; + const rules = LW.map((w, i) => el("path", { d: `M${S.x + 24} ${S.y + 44 + i * 36} H${S.x + 24 + w}`, stroke: BLUE, "stroke-width": 8, "stroke-linecap": "round" }, sheet)); + const mk = el("g", {}, svg), y1 = S.y + 44, y2 = S.y + 44 + 4 * 36, bx = S.x + S.w + 26; + el("path", { d: `M${bx - 8} ${y1} H${bx} V${y2} H${bx - 8}`, stroke: INK, "stroke-width": 11, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, mk); + el("path", { d: `M${bx - 8} ${y1} H${bx} V${y2} H${bx - 8}`, stroke: YEL, "stroke-width": 6, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, mk); + txt(mk, bx + 16, (y1 + y2) / 2 - 4, "Under", { "font-size": 22, "font-weight": 700 }); + txt(mk, bx + 16, (y1 + y2) / 2 + 24, "200 lines", { "font-size": 22, "font-weight": 700 }); + return (t) => { + const k = ob(seg(t, 0.2, 0.9)); + sheet.setAttribute("opacity", seg(t, 0.2, 0.35)); sheet.setAttribute("transform", `translate(0 ${-40 * (1 - k)})`); + rules.forEach((r, i) => { const q = oc(seg(t, 0.8 + i * 0.18, 1.2 + i * 0.18)); r.setAttribute("stroke-dasharray", `${LW[i] * q + 0.01} 400`); r.setAttribute("opacity", q > 0 ? 1 : 0); }); + const m = oc(seg(t, 1.9, 2.4)); mk.setAttribute("opacity", m); mk.setAttribute("transform", `translate(${16 * (1 - m)} 0)`); + }; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/docs/blog/assets/media/claude-md-best-practices/stack.png b/docs/blog/assets/media/claude-md-best-practices/stack.png new file mode 100644 index 0000000000..4cfe07aa0d Binary files /dev/null and b/docs/blog/assets/media/claude-md-best-practices/stack.png differ diff --git a/docs/blog/assets/media/codex-computer-use/approval.png b/docs/blog/assets/media/codex-computer-use/approval.png new file mode 100644 index 0000000000..77bc0fb3e4 Binary files /dev/null and b/docs/blog/assets/media/codex-computer-use/approval.png differ diff --git a/docs/blog/assets/media/codex-computer-use/hero.png b/docs/blog/assets/media/codex-computer-use/hero.png new file mode 100644 index 0000000000..64689644eb Binary files /dev/null and b/docs/blog/assets/media/codex-computer-use/hero.png differ diff --git a/docs/blog/assets/media/codex-computer-use/motion.css b/docs/blog/assets/media/codex-computer-use/motion.css new file mode 100644 index 0000000000..dfe632ff0c --- /dev/null +++ b/docs/blog/assets/media/codex-computer-use/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Codex computer use". 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/docs/blog/assets/media/codex-computer-use/motion.js b/docs/blog/assets/media/codex-computer-use/motion.js new file mode 100644 index 0000000000..9db72536e5 --- /dev/null +++ b/docs/blog/assets/media/codex-computer-use/motion.js @@ -0,0 +1,281 @@ +/* Bright coded scenes for "Codex computer use". 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 CAP = "#6B5878"; + const pill = (svg, x, y, w, label, fn) => { + const g = el("g", {}, svg); + el("rect", { x, y, width: w, height: 52, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: x - 6, y: y - 6, width: w + 12, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + txt(g, x + w / 2, y + 34, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + /* a plain arrow cursor, tip at 0,0 */ + const pointer = (p, s = 1.3) => { + const g = el("g", {}, p), ring = el("circle", { cx: 0, cy: 0, r: 10, fill: "none", stroke: BLUE, "stroke-width": 3 }, g); + const arrow = el("g", {}, g); + el("path", { d: "M0 0 V30 L7.5 23 L13 35.5 L19.5 32.5 L14 20.5 H24.5 Z", fill: BLUE, stroke: INK, "stroke-width": 2.4, "stroke-linejoin": "round", transform: `scale(${s})` }, arrow); + return { g, arrow, ring }; + }; + const press = (P, k) => { // k from 0 to 1 over one click + const dn = Math.sin(cl(k) * Math.PI); + P.arrow.setAttribute("transform", `scale(${1 - 0.1 * dn})`); + P.ring.setAttribute("r", 8 + 22 * k); P.ring.setAttribute("opacity", k > 0 && k < 1 ? 1 - k : 0); + }; + /* a switch: soft track, mint when on */ + const toggle = (p, x, y, w = 68, h = 36) => { + const g = el("g", {}, p); + el("rect", { x: x - 7, y: y - 7, width: w + 14, height: h + 14, rx: (h + 14) / 2, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, g); + el("rect", { x, y, width: w, height: h, rx: h / 2, fill: SOFT, stroke: INK, "stroke-width": 3 }, g); + const on = el("rect", { x, y, width: w, height: h, rx: h / 2, fill: MINT, stroke: INK, "stroke-width": 3 }, g); + const r = h / 2 - 6, knob = el("circle", { cx: x + h / 2, cy: y + h / 2, r, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, g); + return { g, set: (v) => { on.setAttribute("opacity", cl(v)); knob.setAttribute("cx", x + h / 2 + (w - h) * cl(v)); } }; + }; + const tick = (p, cx, cy, r = 17) => { + const g = el("g", {}, p); + el("circle", { cx, cy, r, fill: MINT, stroke: INK, "stroke-width": 2.5 }, g); + el("path", { d: `M${cx - r * 0.5} ${cy} l${r * 0.35} ${r * 0.35} l${r * 0.65} ${-r * 0.7}`, stroke: INK, "stroke-width": 3.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); + return g; + }; + const windowFrame = (p, x, y, w, h, title) => { + el("rect", { x: x + 10, y: y + 10, width: w, height: h, rx: 18, fill: SOFT }, p); + el("rect", { x, y, width: w, height: h, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 3 }, p); + el("path", { d: `M${x} ${y + 54} H${x + w}`, stroke: INK, "stroke-width": 3 }, p); + [0, 1, 2].forEach((i) => el("circle", { cx: x + 28 + i * 24, cy: y + 27, r: 7, fill: [SOFT, YEL, MINT][i], stroke: INK, "stroke-width": 2.2 }, p)); + if (title) txt(p, x + w / 2, y + 35, title, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + }; + const lerp = (a, b, k) => a + (b - a) * k; + + const SC = {}; + + /* ---- 1. a pointer flips one switch in a Settings window and clicks Save ---- */ + SC.window = { + dur: 10, still: 8.6, + build(svg, api) { + const root = el("g", {}, svg); + const X = 50, Y = 110, WW = 460, WH = 390; + windowFrame(root, X, Y, WW, WH, "Settings"); + const ROWS = [204, 274, 344], BAR = [190, 150, 210]; + const sw = ROWS.map((y, i) => { + el("rect", { x: X + 40, y: y - 6, width: BAR[i], height: 12, rx: 6, fill: SOFT }, root); + const s = toggle(root, X + WW - 108, y - 18); + s.g.setAttribute("aria-pressed", "false"); + return s; + }); + const SV = { x: X + WW - 176, y: Y + WH - 76, w: 136, h: 50 }; + const save = el("g", {}, root); + el("rect", { x: SV.x, y: SV.y + 5, width: SV.w, height: SV.h, rx: 25, fill: INK }, save); + const saveTop = el("g", {}, save); + el("rect", { x: SV.x, y: SV.y, width: SV.w, height: SV.h, rx: 25, fill: YEL, stroke: INK, "stroke-width": 3 }, saveTop); + txt(saveTop, SV.x + SV.w / 2, SV.y + 33, "Save", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + const card = el("g", {}, root), CX = 548, CY = 252, CW = 232, CH = 96; + el("rect", { x: CX + 6, y: CY + 6, width: CW, height: CH, rx: 18, fill: SOFT }, card); + el("rect", { x: CX, y: CY, width: CW, height: CH, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 3 }, card); + txt(card, CX + CW / 2, CY + CH / 2 + 8, "Done in the app", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + tick(card, CX + 4, CY + 4); + const P = pointer(root); + const T2 = [X + WW - 108 + 50, ROWS[1] + 4], SVP = [SV.x + SV.w - 30, SV.y + SV.h / 2 + 4], START = [760, 590]; + const user = [null, null, null], last = [0, 0, 0]; + let prevT = 0; + sw.forEach((s, i) => button(s.g, `Switch ${i + 1}. Flip it`, () => { user[i] = { from: last[i], to: last[i] > 0.5 ? 0 : 1, at: api.now() }; api.poke(); })); + return (t, now) => { + if (t < prevT - 1) user.fill(null); prevT = t; + root.setAttribute("opacity", seg(t, 0, 0.4) * (1 - seg(t, 9.4, 10))); + sw.forEach((s, i) => { + const u = user[i], auto = i === 1 ? io(seg(t, 3.0, 3.3)) : 0; + const v = u ? lerp(u.from, u.to, oc(seg(now - u.at, 0, 0.25))) : auto; + last[i] = v; s.set(v); s.g.setAttribute("aria-pressed", v > 0.5); + }); + const a = io(seg(t, 1.0, 2.6)), b = io(seg(t, 3.7, 4.9)); + let x = lerp(START[0], T2[0], a) + Math.sin(a * Math.PI) * 30, y = lerp(START[1], T2[1], a); + x = lerp(x, SVP[0], b); y = lerp(y, SVP[1], b) - Math.sin(b * Math.PI) * 30; + P.g.setAttribute("transform", `translate(${x} ${y})`); + P.g.setAttribute("opacity", seg(t, 0.9, 1.2)); + press(P, t < 3.5 ? seg(t, 2.75, 3.3) : seg(t, 5.0, 5.55)); + saveTop.setAttribute("transform", `translate(0 ${4 * Math.sin(seg(t, 5.0, 5.3) * Math.PI)})`); + const c = ob(seg(t, 5.5, 6.1)); + card.setAttribute("opacity", seg(t, 5.5, 5.65)); card.setAttribute("transform", `translate(${24 * (1 - c)} 0)`); + }; + }, + }; + + /* ---- 2. an approval card; Always allow sends the app tile to a shelf ---- */ + SC.approval = { + dur: 10, still: 8.6, + build(svg, api) { + const root = el("g", {}, svg); + const C = { x: 160, y: 56, w: 480, h: 262 }; + el("rect", { x: C.x + 10, y: C.y + 10, width: C.w, height: C.h, rx: 22, fill: SOFT }, root); + el("rect", { x: C.x, y: C.y, width: C.w, height: C.h, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, root); + el("rect", { x: 196, y: 92, width: 90, height: 90, rx: 18, fill: "none", stroke: MUTE, "stroke-width": 2, "stroke-dasharray": "5 7" }, root); + txt(root, 312, 150, "Use Calculator?", { "font-size": 30, "font-weight": 700 }); + const B = [["Allow once", 196], ["Always allow", 420]].map(([label, x]) => { + const g = el("g", {}, root), y = 230, w = 204, h = 54; + el("rect", { x: x - 7, y: y - 7, width: w + 14, height: h + 14, rx: 34, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, g); + el("rect", { x, y, width: w, height: h, rx: 27, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const lit = el("rect", { x, y, width: w, height: h, rx: 27, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + txt(g, x + w / 2, y + 35, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + g.setAttribute("aria-pressed", "false"); + return { g, lit, label }; + }); + const S = { x: 130, y: 400, w: 540, h: 172 }; + el("rect", { x: S.x + 8, y: S.y + 8, width: S.w, height: S.h, rx: 22, fill: SOFT }, root); + el("rect", { x: S.x, y: S.y, width: S.w, height: S.h, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, root); + txt(root, S.x + 32, S.y + 146, "Always allowed apps", { "font-size": 22, "font-weight": 700 }); + [0, 1, 2, 3].forEach((i) => el("rect", { x: S.x + 32 + i * 96, y: S.y + 24, width: 76, height: 76, rx: 16, fill: "none", stroke: MUTE, "stroke-width": 2, "stroke-dasharray": "5 7" }, root)); + const tile = el("g", {}, root); + el("rect", { x: 0, y: 0, width: 90, height: 90, rx: 18, fill: SKY, stroke: INK, "stroke-width": 3 }, tile); + el("rect", { x: 14, y: 13, width: 62, height: 20, rx: 5, fill: "#fff", stroke: INK, "stroke-width": 2.2 }, tile); + [0, 1].forEach((r) => [0, 1, 2].forEach((c) => el("rect", { x: 14 + c * 23, y: 42 + r * 21, width: 16, height: 15, rx: 4, fill: r === 1 && c === 2 ? YEL : "#fff", stroke: INK, "stroke-width": 2 }, tile))); + const FROM = [196, 92, 1], TO = [S.x + 32 + 1.5, S.y + 24 + 1.5, 73 / 90]; + let user = null, prevT = 0, lA = 0, lO = 0, lT = 0; + const pick = (which) => { user = { which, at: api.now(), A: lA, O: lO, T: lT }; api.poke(); }; + button(B[0].g, "Allow once", () => pick(0)); button(B[1].g, "Always allow", () => pick(1)); + return (t, now) => { + if (t < prevT - 1) user = null; prevT = t; + root.setAttribute("opacity", seg(t, 0, 0.4) * (1 - seg(t, 9.4, 10))); + let A, O, T; + if (user) { + const u = now - user.at, k = oc(seg(u, 0, 0.3)), m = io(seg(u, 0.4, 1.6)), al = user.which === 1; + A = lerp(user.A, al ? 1 : 0, k); O = lerp(user.O, al ? 0 : 1, k); T = lerp(user.T, al ? 1 : 0, m); + } else { A = oc(seg(t, 3.0, 3.3)); O = 0; T = io(seg(t, 4.0, 5.4)); } + lA = A; lO = O; lT = T; + B[0].lit.setAttribute("opacity", O); B[1].lit.setAttribute("opacity", A); + B[0].g.setAttribute("aria-pressed", O > 0.5); B[1].g.setAttribute("aria-pressed", A > 0.5); + const x = lerp(FROM[0], TO[0], T), y = lerp(FROM[1], TO[1], T) - Math.sin(T * Math.PI) * 50, s = lerp(FROM[2], TO[2], T); + tile.setAttribute("transform", `translate(${x} ${y}) rotate(${Math.sin(T * Math.PI) * -6} 45 45) scale(${s})`); + }; + }, + }; + + /* ---- 3. a padlock opens under a lilac cover for an active task, then relocks on a key press ---- */ + SC.padlock = { + dur: 10, still: 9, + build(svg, api) { + const root = el("g", {}, svg); + el("rect", { x: 128, y: 48, width: 480, height: 350, rx: 20, fill: SOFT }, root); + el("rect", { x: 120, y: 40, width: 480, height: 350, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 4 }, root); + el("rect", { x: 140, y: 60, width: 440, height: 312, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 2 }, root); + el("path", { d: "M70 390 H650 L624 428 H96 Z", fill: "#fff", stroke: INK, "stroke-width": 3.5, "stroke-linejoin": "round" }, root); + el("path", { d: "M318 390 Q318 402 330 402 H390 Q402 402 402 390", fill: SOFT, stroke: INK, "stroke-width": 2.5 }, root); + const clip = el("clipPath", { id: "mg-pl-clip" }, svg); + el("rect", { x: 141, y: 61, width: 438, height: 310, rx: 9 }, clip); + const cover = el("circle", { cx: 360, cy: 294, r: 0, fill: LIL, "clip-path": "url(#mg-pl-clip)" }, root); + const tag = el("g", {}, root); + el("rect", { x: 412, y: 82, width: 148, height: 40, rx: 20, fill: BLUE, stroke: INK, "stroke-width": 2.5 }, tag); + txt(tag, 486, 109, "Active task", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const lock = el("g", {}, root), shackle = el("g", {}, lock), body = el("g", {}, lock); + const SH = "M318 244 V200 A42 42 0 0 1 402 200 V244"; + el("path", { d: SH, stroke: INK, "stroke-width": 26, fill: "none", "stroke-linecap": "round" }, shackle); + el("path", { d: SH, stroke: YEL, "stroke-width": 14, fill: "none", "stroke-linecap": "round" }, shackle); + el("rect", { x: 288, y: 236, width: 144, height: 118, rx: 22, fill: YEL, stroke: INK, "stroke-width": 4 }, body); + el("circle", { cx: 360, cy: 284, r: 12, fill: INK }, body); + el("rect", { x: 354, y: 284, width: 12, height: 36, rx: 5, fill: INK }, body); + const key = el("g", {}, root), K = { x: 662, y: 300, s: 80 }; + el("rect", { x: K.x, y: K.y + 8, width: K.s, height: K.s - 6, rx: 16, fill: INK }, key); + const cap = el("g", {}, key); + el("rect", { x: K.x, y: K.y, width: K.s, height: K.s - 6, rx: 16, fill: SKY, stroke: INK, "stroke-width": 3 }, cap); + el("path", { d: `M${K.x + 40} ${K.y + 18} V${K.y + 52} M${K.x + 28} ${K.y + 40} L${K.x + 40} ${K.y + 52} L${K.x + 52} ${K.y + 40}`, stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, cap); + const burst = el("path", { d: `M${K.x + 40} ${K.y - 14} V${K.y - 30} M${K.x + 8} ${K.y - 8} L${K.x - 4} ${K.y - 20} M${K.x + 72} ${K.y - 8} L${K.x + 84} ${K.y - 20}`, stroke: INK, "stroke-width": 3.5, "stroke-linecap": "round" }, key); + pill(svg, 620, 512, 140, "Lock", () => api.seek(0)); + return (t) => { + root.setAttribute("opacity", seg(t, 0, 0.4) * (1 - seg(t, 9.4, 10))); + const pop = ob(seg(t, 0.4, 1.0)); + lock.setAttribute("opacity", seg(t, 0.4, 0.55)); + const shut = ob(seg(t, 6.05, 6.3)), open = io(seg(t, 2.3, 2.75)) * (1 - shut); + const jolt = Math.sin(seg(t, 6.2, 6.5) * Math.PI) * 5; + lock.setAttribute("transform", `translate(360 ${295 + jolt}) scale(${0.6 + 0.4 * pop}) translate(-360 -295)`); + shackle.setAttribute("transform", `translate(0 ${-26 * open}) rotate(${-28 * open} 318 244)`); + const tg = oc(seg(t, 1.4, 1.9)); + tag.setAttribute("opacity", seg(t, 1.4, 1.6)); tag.setAttribute("transform", `translate(0 ${-14 * (1 - tg)})`); + cover.setAttribute("r", 470 * io(seg(t, 2.6, 4.0)) * (1 - io(seg(t, 6.5, 7.5)))); + const kp = ob(seg(t, 5.3, 5.7)), down = Math.sin(seg(t, 5.85, 6.15) * Math.PI); + key.setAttribute("opacity", seg(t, 5.3, 5.45)); + key.setAttribute("transform", `translate(${K.x + 40} ${K.y + 37}) scale(${0.6 + 0.4 * kp}) translate(${-K.x - 40} ${-K.y - 37})`); + cap.setAttribute("transform", `translate(0 ${7 * down})`); + burst.setAttribute("opacity", seg(t, 5.9, 6.05)); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 8, still: 8, + build(svg) { + el("rect", { x: 64, y: 88, width: 110, height: 42, rx: 21, fill: SKY, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 119, 116, "Guide", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 60, 230, "Codex", { "font-family": MONO, "font-size": 76, "font-weight": 700 }); + txt(svg, 60, 310, "computer use", { "font-size": 70, "font-weight": 700 }); + txt(svg, 64, 370, "Setup, plans and limits", { "font-size": 27, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 412, "Checked 26 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const win = el("g", {}, svg), X = 600, Y = 130, WW = 300, WH = 270; + windowFrame(win, X, Y, WW, WH); + [[X + 34, Y + 92, 170], [X + 34, Y + 122, 120]].forEach(([x, y, w]) => el("rect", { x, y: y - 6, width: w, height: 12, rx: 6, fill: SOFT }, win)); + const sw = toggle(win, X + 80, Y + 170, 140, 66); + const P = pointer(svg, 1.5); + return (t) => { + const k = ob(seg(t, 0.2, 0.9)); + win.setAttribute("opacity", seg(t, 0.2, 0.35)); win.setAttribute("transform", `translate(0 ${-30 * (1 - k)})`); + const a = io(seg(t, 1.0, 2.0)); + P.g.setAttribute("transform", `translate(${lerp(940, X + 200, a)} ${lerp(530, Y + 212, a) - Math.sin(a * Math.PI) * 20})`); + P.g.setAttribute("opacity", seg(t, 1.0, 1.2)); + press(P, seg(t, 2.1, 2.6)); + sw.set(io(seg(t, 2.25, 2.55))); + }; + }, + }; + + /* ---- 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/docs/blog/assets/media/codex-computer-use/padlock.png b/docs/blog/assets/media/codex-computer-use/padlock.png new file mode 100644 index 0000000000..339783b7a8 Binary files /dev/null and b/docs/blog/assets/media/codex-computer-use/padlock.png differ diff --git a/docs/blog/assets/media/codex-computer-use/window.png b/docs/blog/assets/media/codex-computer-use/window.png new file mode 100644 index 0000000000..e51608c10e Binary files /dev/null and b/docs/blog/assets/media/codex-computer-use/window.png differ diff --git a/docs/blog/assets/media/cursor-alternatives/bookends.png b/docs/blog/assets/media/cursor-alternatives/bookends.png index a942ba9a62..cf8fd562b9 100644 Binary files a/docs/blog/assets/media/cursor-alternatives/bookends.png and b/docs/blog/assets/media/cursor-alternatives/bookends.png differ diff --git a/docs/blog/assets/media/cursor-alternatives/motion.js b/docs/blog/assets/media/cursor-alternatives/motion.js index a01e35b058..a362a8bac0 100644 --- a/docs/blog/assets/media/cursor-alternatives/motion.js +++ b/docs/blog/assets/media/cursor-alternatives/motion.js @@ -39,7 +39,7 @@ el("path", { d: `M${PX} ${BASE - 46} L${PX - 78} ${BASE} M${PX} ${BASE - 46} L${PX + 78} ${BASE}`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round", fill: "none" }, rack); el("rect", { x: 104, y: RAIL - 11, width: 592, height: 22, rx: 11, fill: YEL, stroke: INK, "stroke-width": 3 }, rack); HX.forEach((x) => el("path", { d: `M${x} ${RAIL + 11} V${RAIL + 30}`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, rack)); - const LINES = ["Price: Individual $20, Pro Plus $60, Ultra $200 a month", "Licence: its public GitHub repo carried no licence", "Plan limits: Hobby is free with “Limited Agent requests”"]; + const LINES = ["Price: Plus $8, Premium $30, Ultra $200 a month", "Licence: its public GitHub repo carried no licence", "Plan limits: Plus runs Grok models only"]; const NAME = ["price", "licence", "plan limits"], COL = [YEL, LIL, SKY]; let pinned = -1; const coats = HX.map((x, i) => { @@ -66,7 +66,7 @@ c.g.setAttribute("transform", `translate(${c.x + 130 * (1 - oc(q))} ${RAIL + 22 - 30 * (1 - q * q)}) rotate(${-lean + sw})`); c.g.setAttribute("opacity", seg(tt, 0, 0.15) * (1 - out)); }); - cap.textContent = pinned >= 0 ? LINES[pinned] : t > 7.6 && t < 10.6 ? "Hobby free; Individual $20, Pro Plus $60, Ultra $200 a month" : last < 0 || t >= 10.6 ? "Three reasons: price, licence and plan limits" : LINES[last]; + cap.textContent = pinned >= 0 ? LINES[pinned] : t > 7.6 && t < 10.6 ? "Free $0; Plus $8, Premium $30, Ultra $200 a month" : last < 0 || t >= 10.6 ? "Three reasons: price, licence and plan limits" : LINES[last]; }; }, }; @@ -83,7 +83,7 @@ el("rect", { x: -76, y: -21, width: 152, height: 42, rx: 21, fill: MINT, stroke: INK, "stroke-width": 3 }, chip); txt(chip, 0, 7, "same height", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); el("rect", { x: 50, y: SH, width: 700, height: 16, rx: 8, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); - const book = (name, col, plan, right) => { + const book = (name, col, plan, right, price) => { const g = el("g", {}, svg), s = right ? -1 : 1, cx = (s * BW) / 2; el("rect", { x: right ? -BW : 0, y: -BH, width: BW, height: BH, rx: 10, fill: col, stroke: INK, "stroke-width": 3 }, g); el("path", { d: `M${cx - BW / 2} ${-BH + 26} h${BW} M${cx - BW / 2} ${-BH + 38} h${BW}`, stroke: INK, "stroke-width": 3 }, g); @@ -91,11 +91,11 @@ const p = el("g", {}, g); el("rect", { x: -58, y: -52, width: 116, height: 104, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, p); txt(p, 0, -24, plan, { "text-anchor": "middle", "font-size": 19, "font-weight": 500 }); - txt(p, 0, 12, "$20", { "text-anchor": "middle", "font-size": 34, "font-weight": 700 }); + txt(p, 0, 12, price, { "text-anchor": "middle", "font-size": 34, "font-weight": 700 }); txt(p, 0, 38, "a month", { "text-anchor": "middle", "font-size": 19, "font-weight": 500 }); return { g, p, cx }; }; - const A = book(["Cursor"], SKY, "Individual", false), B = book(["Devin", "Desktop"], LIL, "Pro", true); + const A = book(["Cursor"], SKY, "Premium", false, "$30"), B = book(["Devin", "Desktop"], LIL, "Pro", true, "$20"); const end = (right) => { const g = el("g", {}, svg); el("path", { d: `M0 0 V${-PL} a8 8 0 0 0 -16 0 V-16 H-112 a8 8 0 0 0 0 16 Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round", transform: right ? "scale(-1 1)" : "" }, g); return g; }; const EA = end(false), EB = end(true); const LBX = RX - BW / 2, LBY = 470; @@ -124,7 +124,7 @@ face.setAttribute("transform", `scale(1 ${Math.max(0.04, Math.min(Math.abs(Math.cos(Math.PI * u)), Math.abs(Math.cos(Math.PI * w))))})`); ft.textContent = side ? "Devin Desktop" : "Windsurf"; fr.setAttribute("fill", side ? YEL : "#fff"); note.textContent = side ? "new name" : "old name"; - cap.textContent = t < 3 || t >= 10.4 ? "Both are editors with agents built in" : t < 4.5 ? "Same shape" : t < 6.6 ? "Both list a plan at $20 a month: Cursor Individual, Devin Pro" : t < 8.4 ? "“Devin Desktop is the new name for Windsurf.”" : "We ran no test, so this is about shape and price"; + cap.textContent = t < 3 || t >= 10.4 ? "Both are editors with agents built in" : t < 4.5 ? "Same shape" : t < 6.6 ? "Several labs’ models: Cursor Premium $30, Devin Pro $20" : t < 8.4 ? "“Devin Desktop is the new name for Windsurf.”" : "This is about shape, not quality"; }; }, }; diff --git a/docs/blog/assets/media/cursor-alternatives/rack.png b/docs/blog/assets/media/cursor-alternatives/rack.png index b21e48ea62..2ba59e14cc 100644 Binary files a/docs/blog/assets/media/cursor-alternatives/rack.png and b/docs/blog/assets/media/cursor-alternatives/rack.png differ diff --git a/docs/blog/assets/media/pi-agent-alternatives/dial.png b/docs/blog/assets/media/pi-agent-alternatives/dial.png new file mode 100644 index 0000000000..7bb9a7cbb9 Binary files /dev/null and b/docs/blog/assets/media/pi-agent-alternatives/dial.png differ diff --git a/docs/blog/assets/media/pi-agent-alternatives/hero.png b/docs/blog/assets/media/pi-agent-alternatives/hero.png new file mode 100644 index 0000000000..c1975dc84a Binary files /dev/null and b/docs/blog/assets/media/pi-agent-alternatives/hero.png differ diff --git a/docs/blog/assets/media/pi-agent-alternatives/keyring.png b/docs/blog/assets/media/pi-agent-alternatives/keyring.png new file mode 100644 index 0000000000..e5d8be7736 Binary files /dev/null and b/docs/blog/assets/media/pi-agent-alternatives/keyring.png differ diff --git a/docs/blog/assets/media/pi-agent-alternatives/kit.png b/docs/blog/assets/media/pi-agent-alternatives/kit.png new file mode 100644 index 0000000000..561bda481f Binary files /dev/null and b/docs/blog/assets/media/pi-agent-alternatives/kit.png differ diff --git a/docs/blog/assets/media/pi-agent-alternatives/motion.css b/docs/blog/assets/media/pi-agent-alternatives/motion.css new file mode 100644 index 0000000000..5725a47703 --- /dev/null +++ b/docs/blog/assets/media/pi-agent-alternatives/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Pi agent 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/docs/blog/assets/media/pi-agent-alternatives/motion.js b/docs/blog/assets/media/pi-agent-alternatives/motion.js new file mode 100644 index 0000000000..bf47be2ed9 --- /dev/null +++ b/docs/blog/assets/media/pi-agent-alternatives/motion.js @@ -0,0 +1,248 @@ +/* Bright coded scenes for "Pi agent 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 GREY = "#D9D4DF", CAP = "#6B5878"; + const hint = (svg, x, y, s, anchor = "end") => txt(svg, x, y, s, { "text-anchor": anchor, "font-size": 16, fill: MUTE, "font-weight": 500 }); + const pill = (svg, x, y, w, label, fn) => { + const g = el("g", {}, svg); + el("rect", { x, y, width: w, height: 52, rx: 26, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + el("rect", { x: x - 6, y: y - 6, width: w + 12, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, g); + txt(g, x + w / 2, y + 34, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + /* a name in the mono font, split onto two centred lines when it has a space */ + const name2 = (g, x, y, name, size, a = {}) => { + const parts = name.split(" "), gap = size + 3; + parts.forEach((p, j) => txt(g, x, y + (j - (parts.length - 1) / 2) * gap + size * 0.36, p, Object.assign({ "text-anchor": "middle", "font-family": MONO, "font-size": size, "font-weight": 700 }, a))); + }; + const PICKS = ["OpenCode", "Munder Difflin", "Claude Code", "Codex CLI", "Hermes Agent", "Goose", "Crush"]; + const BEST = ["More in the box, any model", "A team of agents, Pi included", "Permission modes, Claude models", "You already pay for ChatGPT", "Chat apps and schedules", "Code and workflows, desktop app", "Swap models mid session"]; + + const SC = {}; + + /* ---- 1. seven keys slide onto a ring that already holds the Pi key ---- */ + SC.keyring = { + dur: 11.4, still: 9.5, + build(svg, api) { + hint(svg, 774, 50, "tap a key"); + const C = [400, 300], R = 150, E = 240; + const at = (deg, r = R) => { const a = (deg * Math.PI) / 180; return [C[0] + Math.cos(a) * r, C[1] + Math.sin(a) * r]; }; + el("ellipse", { cx: 400, cy: 572, rx: 170, ry: 12, fill: SOFT }, svg); + el("rect", { x: 348, y: 20, width: 104, height: 22, rx: 9, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + [370, 430].forEach((x) => el("circle", { cx: x, cy: 31, r: 4, fill: "#fff", stroke: INK, "stroke-width": 2 }, svg)); + el("path", { d: "M400 42 V150", stroke: INK, "stroke-width": 8, "stroke-linecap": "round", fill: "none" }, svg); + el("circle", { cx: C[0], cy: C[1], r: R, fill: "none", stroke: INK, "stroke-width": 13 }, svg); + el("circle", { cx: C[0], cy: C[1], r: R, fill: "none", stroke: SOFT, "stroke-width": 6 }, svg); + const forced = PICKS.map(() => null).concat([null]); + const key = (fill, name, br, size, i) => { + const g = el("g", {}, svg), blade = el("g", {}, g), bow = el("g", {}, g); + el("path", { d: `M${br - 8} -8 H${br + 50} L${br + 58} -1 V3 H${br + 48} V11 H${br + 40} V3 H${br + 31} V12 H${br + 23} V3 H${br - 8} Z`, fill, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, blade); + el("circle", { r: br + 7, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, bow); + el("circle", { r: br, fill, stroke: INK, "stroke-width": 3 }, bow); + name2(bow, 0, 0, name, size); + button(g, `Lift the ${name} key`, () => { forced[i] = api.now(); api.poke(); }); + return { g, blade }; + }; + const place = (k, deg, lift, s, out) => { + const [x, y] = at(deg, R + lift); + k.g.setAttribute("transform", `translate(${x} ${y}) scale(${s})`); + k.blade.setAttribute("transform", `rotate(${deg + (out ? 0 : 180)})`); + }; + const pi = key(GREY, "Pi", 34, 24, 7); + const keys = PICKS.map((n, i) => key(i % 2 ? LIL : BLUE, n, 44, 15, i)); + const lifted = (i, now) => { if (forced[i] == null) return 0; const u = now - forced[i]; return oc(seg(u, 0, 0.25)) * (1 - io(seg(u, 1.3, 1.7))); }; + return (t, now) => { + const lp = lifted(7, now); + place(pi, -90, -22 * lp, 1 + 0.08 * lp, false); + keys.forEach((k, i) => { + const a = 0.6 + i * 0.95, m = io(seg(t, a, a + 0.85)), slot = -45 + 45 * i; + const ap = oc(seg(t, a - 0.2, a + 0.1)), gone = io(seg(t, 10.3 + (6 - i) * 0.05, 10.8 + (6 - i) * 0.05)), lv = lifted(i, now); + place(k, E + (slot - E) * m, 26 * lv + 40 * gone, (0.6 + 0.4 * ap) * (1 + 0.1 * lv), true); + k.g.setAttribute("opacity", ap * (1 - gone)); + }); + }; + }, + }; + + /* ---- 2. the Pi kit box opens on three empty slots ---- */ + SC.kit = { + dur: 10.5, still: 8.4, + build(svg, api) { + pill(svg, 646, 34, 112, "Open", () => api.seek(0)); + hint(svg, 758, 114, "tap Open"); + const X = 110, Wd = 420, HY = 210, TH = 130; + el("ellipse", { cx: X + Wd / 2, cy: 512, rx: 240, ry: 14, fill: SOFT }, svg); + const back = el("g", {}, svg); + el("rect", { x: X, y: HY, width: Wd, height: TH, rx: 6, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + const labels = ["Subagents", "Plan mode", "Permissions"].map((s, i) => { + const x = X + 12 + i * 136; + el("rect", { x, y: HY + 17, width: 124, height: 96, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 2.5, "stroke-dasharray": "8 7" }, svg); + return txt(svg, x + 62, HY + 71, s, { "text-anchor": "middle", "font-size": 17, "font-weight": 700 }); + }); + el("rect", { x: X, y: HY + TH, width: Wd, height: 160, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + txt(svg, X + Wd / 2, HY + TH + 112, "Pi", { "text-anchor": "middle", "font-family": MONO, "font-size": 76, "font-weight": 700 }); + el("rect", { x: X + Wd / 2 - 22, y: HY + TH - 6, width: 44, height: 22, rx: 6, fill: YEL, stroke: INK, "stroke-width": 2.5 }, svg); + const front = el("g", {}, svg); + const lid = el("g", {}, front); + const face = el("rect", { x: X - 4, y: HY, width: Wd + 8, height: TH, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, lid); + const top = el("rect", { x: X + 24, width: Wd - 48, rx: 10, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "2 8", "stroke-linecap": "round" }, lid); + const tag = el("g", {}, svg), tagIn = el("g", {}, tag); + el("path", { d: "M20 43 C4 43 -10 32 -16 14", fill: "none", stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, tagIn); + el("path", { d: "M28 0 H164 Q170 0 170 6 V80 Q170 86 164 86 H28 L0 58 V28 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, tagIn); + el("circle", { cx: 20, cy: 43, r: 7, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, tagIn); + txt(tagIn, 99, 38, "Add with", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(tagIn, 99, 64, "extensions", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + return (t) => { + const th = 115 * ob(seg(t, 0.6, 1.6)) * (1 - io(seg(t, 9.3, 10.1))), c = Math.cos((th * Math.PI) / 180); + const hh = Math.max(Math.abs(c) * TH, 6); + face.setAttribute("y", c >= 0 ? HY : HY - hh); face.setAttribute("height", hh); face.setAttribute("rx", Math.min(8, hh / 2)); + top.setAttribute("y", HY + 22 * c); top.setAttribute("height", Math.max(TH - 44, 0) * Math.max(c, 0.01)); + (c >= 0 ? front : back).appendChild(lid); + face.setAttribute("fill", c >= 0 ? "#fff" : "#E9E3F2"); top.setAttribute("opacity", c > 0.3 ? 1 : 0); + labels.forEach((n, i) => { const k = ob(seg(t, 1.7 + i * 0.45, 2.1 + i * 0.45)); n.setAttribute("opacity", cl(k * 2) * (1 - seg(t, 9.1, 9.4))); n.setAttribute("transform", `translate(0 ${10 * (1 - k)})`); }); + const d = seg(t, 3.4, 4.1), y = -160 + (232 + 160) * ob(d) - 34 * seg(t, 8.9, 9.3); + const k = seg(t, 4.0, 5.6), ang = 18 * (1 - k) * Math.exp(-2.5 * k) * Math.cos(9 * k) * (d >= 1 ? 1 : 0); + tag.setAttribute("transform", `translate(588 ${y})`); + tagIn.setAttribute("transform", `rotate(${ang} 20 43)`); + tag.setAttribute("opacity", d <= 0 ? 0 : 1 - seg(t, 8.9, 9.3)); + }; + }, + }; + + /* ---- 3. a selector dial steps through the seven stops ---- */ + SC.dial = { + dur: 11.6, still: 10, + build(svg, api) { + el("rect", { x: 30, y: 24, width: 740, height: 552, rx: 28, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + hint(svg, 744, 64, "tap the dial"); + const C = [400, 252], ST = [-210, -170, -130, -90, -50, -10, 30]; + const at = (deg, r) => { const a = (deg * Math.PI) / 180; return [C[0] + Math.cos(a) * r, C[1] + Math.sin(a) * r]; }; + const dots = ST.map((d, i) => { + const [x, y] = at(d, 134), [lx, ly] = at(d, 160), cx = Math.cos((d * Math.PI) / 180); + const anchor = Math.abs(cx) < 0.2 ? "middle" : cx < 0 ? "end" : "start"; + txt(svg, lx, ly + 7, PICKS[i], { "text-anchor": anchor, "font-family": MONO, "font-size": 18, "font-weight": 700 }); + return el("circle", { cx: x, cy: y, r: 9, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + }); + const g = el("g", {}, svg), knob = el("g", {}, g); + el("circle", { cx: C[0], cy: C[1], r: 118, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, g); + el("circle", { r: 108, fill: YEL, stroke: INK, "stroke-width": 3 }, knob); + for (let i = 0; i < 30; i++) { const a = (i / 30) * Math.PI * 2; el("path", { d: `M${Math.cos(a) * 96} ${Math.sin(a) * 96} L${Math.cos(a) * 106} ${Math.sin(a) * 106}`, stroke: INK, "stroke-width": 2, "stroke-linecap": "round" }, knob); } + el("circle", { r: 74, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, knob); + el("path", { d: "M-18 0 H62", stroke: INK, "stroke-width": 9, "stroke-linecap": "round" }, knob); + el("circle", { r: 13, fill: SKY, stroke: INK, "stroke-width": 2.5 }, knob); + el("path", { d: "M122 0 L104 -10 L104 10 Z", fill: INK, "stroke-linejoin": "round", stroke: INK, "stroke-width": 2 }, knob); + el("rect", { x: 130, y: 418, width: 540, height: 118, rx: 20, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const body = el("g", {}, svg); + const nm = txt(body, 400, 466, "", { "text-anchor": "middle", "font-family": MONO, "font-size": 24, "font-weight": 700 }); + const ln = txt(body, 400, 508, "", { "text-anchor": "middle", "font-size": 21, "font-weight": 500 }); + el("tspan", { "font-weight": 700 }, ln).textContent = "Best for: "; + const lnT = el("tspan", {}, ln); + const auto = (t) => { let a = ST[0]; for (let i = 0; i < 6; i++) { const T = 1.0 + i * 1.35; a += 40 * ob(seg(t, T, T + 0.5)); } return a - 240 * io(seg(t, 10.4, 11.2)); }; + let man = null, shown = -1, changed = -99, lastA = ST[0]; + const idx = (a) => cl(Math.round((a - ST[0]) / 40), 0, 6); + button(g, "Dial on OpenCode. Step to the next stop", () => { + const from = lastA, nx = (idx(from) + 1) % 7; + man = { from, to: ST[nx], at: api.now(), wrap: nx === 0 }; api.poke(); + }); + return (t, now) => { + let a = auto(t); + if (man) { const u = seg(now - man.at, 0, man.wrap ? 0.8 : 0.5); a = man.from + (man.to - man.from) * (man.wrap ? io(u) : ob(u)); } + lastA = a; + knob.setAttribute("transform", `translate(${C[0]} ${C[1]}) rotate(${a})`); + const s = idx(a); + if (s !== shown) { + if (shown !== -1) changed = now; + shown = s; nm.textContent = PICKS[s]; lnT.textContent = BEST[s]; + g.setAttribute("aria-label", `Dial on ${PICKS[s]}, best for ${BEST[s]}. Step to the next stop`); + } + dots.forEach((d, i) => { d.setAttribute("fill", i === s ? MINT : "#fff"); d.setAttribute("r", i === s ? 11 : 8); }); + const f = 0.3 + 0.7 * oc(seg(now - changed, 0, 0.3)); + body.setAttribute("opacity", f); body.setAttribute("transform", `translate(0 ${8 * (1 - f)})`); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 4, still: 4, + build(svg) { + el("rect", { x: 64, y: 48, width: 168, height: 42, rx: 21, fill: LIL, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 148, 76, "Alternatives", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 62, 172, "Pi agent alternatives", { "font-size": 72, "font-weight": 700 }); + txt(svg, 64, 224, "7 coding agents to try", { "font-size": 30, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 262, "Checked 26 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const TW = 116, gap = 11, x0 = (960 - (7 * TW + 6 * gap)) / 2, cx = (i) => x0 + i * (TW + gap) + TW / 2; + const lines = el("path", { d: `M480 366 V392 M${cx(0)} 392 H${cx(6)} ` + PICKS.map((_, i) => `M${cx(i)} 392 V418`).join(" "), fill: "none", stroke: INK, "stroke-width": 2, "stroke-linecap": "round", "stroke-dasharray": "2 8" }, svg); + const pi = el("g", {}, svg); + el("rect", { x: 425, y: 308, width: 110, height: 58, rx: 14, fill: GREY, stroke: INK, "stroke-width": 3 }, pi); + txt(pi, 480, 347, "Pi", { "text-anchor": "middle", "font-family": MONO, "font-size": 28, "font-weight": 700 }); + const FILL = [YEL, BLUE, LIL, SKY, MINT, YEL, BLUE]; + const tiles = PICKS.map((name, i) => { + const g = el("g", {}, svg), x = x0 + i * (TW + gap); + el("rect", { x, y: 420, width: TW, height: 72, rx: 14, fill: FILL[i], stroke: INK, "stroke-width": 2.5 }, g); + name2(g, x + TW / 2, 456, name, 17); + return g; + }); + return (t) => { + const c0 = oc(seg(t, 0.1, 0.6)); + pi.setAttribute("opacity", c0); pi.setAttribute("transform", `translate(0 ${-16 * (1 - c0)})`); + lines.setAttribute("opacity", seg(t, 0.5, 0.9)); + tiles.forEach((g, i) => { const k = ob(seg(t, 0.7 + i * 0.22, 1.3 + i * 0.22)); g.setAttribute("opacity", cl(k * 2)); g.setAttribute("transform", `translate(${90 * (1 - k)} 0)`); }); + }; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/docs/blog/best-ai-coding-agents/index.html b/docs/blog/best-ai-coding-agents/index.html index 54b9736418..a994005922 100644 --- a/docs/blog/best-ai-coding-agents/index.html +++ b/docs/blog/best-ai-coding-agents/index.html @@ -484,34 +484,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/best-claude-code-multi-agent-tools/index.html b/docs/blog/best-claude-code-multi-agent-tools/index.html index dae626f3e4..6239ad3e22 100644 --- a/docs/blog/best-claude-code-multi-agent-tools/index.html +++ b/docs/blog/best-claude-code-multi-agent-tools/index.html @@ -452,34 +452,34 @@

Codex Alternatives: 7 Picks Beyond OpenA
- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/build-your-own-claude-duet/index.html b/docs/blog/build-your-own-claude-duet/index.html index c1704fc6ef..874a669037 100644 --- a/docs/blog/build-your-own-claude-duet/index.html +++ b/docs/blog/build-your-own-claude-duet/index.html @@ -280,50 +280,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

Guides - Read → + Read →
- +
- + 5 min
-

Claude API Version 2026-10-21: What Breaks and How to Upgrade

-

Claude API version 2026-10-21 removes temperature, top_p and top_k and ships new SDK majors. What returns a 400 now, what breaks proxies, and how to upgrade.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/build-your-personal-agi-on-your-computer/index.html b/docs/blog/build-your-personal-agi-on-your-computer/index.html index 28fcc01b70..d21a35869f 100644 --- a/docs/blog/build-your-personal-agi-on-your-computer/index.html +++ b/docs/blog/build-your-personal-agi-on-your-computer/index.html @@ -342,50 +342,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/building-reliable-ai-agents/index.html b/docs/blog/building-reliable-ai-agents/index.html index 12b1fce2b8..86ea2b2786 100644 --- a/docs/blog/building-reliable-ai-agents/index.html +++ b/docs/blog/building-reliable-ai-agents/index.html @@ -358,50 +358,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/chatgpt-dots-alternatives/index.html b/docs/blog/chatgpt-dots-alternatives/index.html index 375ae35674..0cb399558b 100644 --- a/docs/blog/chatgpt-dots-alternatives/index.html +++ b/docs/blog/chatgpt-dots-alternatives/index.html @@ -366,34 +366,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-api-version-2026-10-21/index.html b/docs/blog/claude-api-version-2026-10-21/index.html index 187807575b..20ce332935 100644 --- a/docs/blog/claude-api-version-2026-10-21/index.html +++ b/docs/blog/claude-api-version-2026-10-21/index.html @@ -330,50 +330,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-alternatives/index.html b/docs/blog/claude-code-alternatives/index.html index 2ad105dc7e..cf427d8789 100644 --- a/docs/blog/claude-code-alternatives/index.html +++ b/docs/blog/claude-code-alternatives/index.html @@ -376,34 +376,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-code-automation-while-you-sleep/index.html b/docs/blog/claude-code-automation-while-you-sleep/index.html index e10a8b54c6..ea379d80f1 100644 --- a/docs/blog/claude-code-automation-while-you-sleep/index.html +++ b/docs/blog/claude-code-automation-while-you-sleep/index.html @@ -346,50 +346,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-best-practices/index.html b/docs/blog/claude-code-best-practices/index.html index 7c0d022222..85711125b6 100644 --- a/docs/blog/claude-code-best-practices/index.html +++ b/docs/blog/claude-code-best-practices/index.html @@ -205,7 +205,7 @@

3. Be specific in your prom

Reference specific files, mention constraints and point to example patterns. Anthropic’s best practices page contrasts “fix the login bug” with a prompt that names the symptom, the likely location in src/auth/ and a failing test to write first. Use @ to reference files, paste screenshots, or pipe data in:

cat error.log | claude -p "explain this error"

4. What should go in CLAUDE.md?

-

Only what Claude can’t infer from code and needs in every session: Bash commands, code style that differs from defaults, testing instructions, repo etiquette. Run /init for a starter file and /context to confirm Claude loaded it. For each line, Anthropic’s best practices page asks “Would removing this cause Claude to make mistakes?” If not, cut it. Anthropic’s memory docs say to target under 200 lines per file. Our view: a CLAUDE.md that reads like a policy manual gets read like one. See give Claude Code long term memory.

+

Only what Claude can’t infer from code and needs in every session: Bash commands, code style that differs from defaults, testing instructions, repo etiquette. Run /init for a starter file and /context to confirm Claude loaded it. For each line, Anthropic’s best practices page asks “Would removing this cause Claude to make mistakes?” If not, cut it. Anthropic’s memory docs say to target under 200 lines per file. Our view: a CLAUDE.md that reads like a policy manual gets read like one. See give Claude Code long term memory. Our CLAUDE.md best practices page covers where each file lives and how AGENTS.md loads.

Animation. A clipboard holds a short checklist of four lines. Each box ticks itself in turn, and a fifth, longer line fades out and is struck through before it can be ticked.
Short beats long: Anthropic's memory docs say to target under 200 lines per file.

5. Set permissions once, not per prompt

Pre-approve the tools you trust with /permissions and let sandboxed commands run without asking with /sandbox. Anthropic’s best practices page notes that with Claude Code v2.1.283 or later, auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions, where a classifier model blocks only what looks risky. On earlier versions that applies only on Pro, Max and Team plans. See why Claude Code keeps asking for permission.

@@ -349,50 +349,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-max-plan-tips/index.html b/docs/blog/claude-code-max-plan-tips/index.html index 53d891b551..d801d3f652 100644 --- a/docs/blog/claude-code-max-plan-tips/index.html +++ b/docs/blog/claude-code-max-plan-tips/index.html @@ -279,50 +279,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-multi-agent-setup-tutorial/index.html b/docs/blog/claude-code-multi-agent-setup-tutorial/index.html index 581d054970..b182d18535 100644 --- a/docs/blog/claude-code-multi-agent-setup-tutorial/index.html +++ b/docs/blog/claude-code-multi-agent-setup-tutorial/index.html @@ -335,50 +335,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-orchestration-tools-compared/index.html b/docs/blog/claude-code-orchestration-tools-compared/index.html index 395f41b484..b92bd2ded1 100644 --- a/docs/blog/claude-code-orchestration-tools-compared/index.html +++ b/docs/blog/claude-code-orchestration-tools-compared/index.html @@ -350,34 +350,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-code-skills/index.html b/docs/blog/claude-code-skills/index.html index d740b79c47..13ca5eae58 100644 --- a/docs/blog/claude-code-skills/index.html +++ b/docs/blog/claude-code-skills/index.html @@ -330,50 +330,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html b/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html index e9a56c857d..72d0a3e82e 100644 --- a/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html +++ b/docs/blog/claude-code-subagents-vs-multi-agent-harness/index.html @@ -342,50 +342,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-code-vs-antigravity/index.html b/docs/blog/claude-code-vs-antigravity/index.html index 35f91b5393..c655cb9c1d 100644 --- a/docs/blog/claude-code-vs-antigravity/index.html +++ b/docs/blog/claude-code-vs-antigravity/index.html @@ -291,34 +291,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-code-vs-cursor/index.html b/docs/blog/claude-code-vs-cursor/index.html index 439b875211..e4011e590f 100644 --- a/docs/blog/claude-code-vs-cursor/index.html +++ b/docs/blog/claude-code-vs-cursor/index.html @@ -369,34 +369,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-cowork-vs-claude-code/index.html b/docs/blog/claude-cowork-vs-claude-code/index.html index 4abbf4233e..815dc856ee 100644 --- a/docs/blog/claude-cowork-vs-claude-code/index.html +++ b/docs/blog/claude-cowork-vs-claude-code/index.html @@ -363,34 +363,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-duet-alternatives/index.html b/docs/blog/claude-duet-alternatives/index.html index a6b4bfedd4..c7d542b36b 100644 --- a/docs/blog/claude-duet-alternatives/index.html +++ b/docs/blog/claude-duet-alternatives/index.html @@ -329,34 +329,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Is Claude Duet Good for Developers? What It Does With Code and What It Doesn't

-

Claude Duet can prototype and build web apps, but it is built for business apps, not your repo. What developers get from it, and where Claude Code fits better.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-duet-app/index.html b/docs/blog/claude-duet-app/index.html index 20682523a7..a4b5bf71c3 100644 --- a/docs/blog/claude-duet-app/index.html +++ b/docs/blog/claude-duet-app/index.html @@ -319,50 +319,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

Guides - Read → + Read →
- +
- + 5 min
-

Claude API Version 2026-10-21: What Breaks and How to Upgrade

-

Claude API version 2026-10-21 removes temperature, top_p and top_k and ships new SDK majors. What returns a 400 now, what breaks proxies, and how to upgrade.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-duet-for-developers/index.html b/docs/blog/claude-duet-for-developers/index.html index 7779e45e87..2c5e14c2a6 100644 --- a/docs/blog/claude-duet-for-developers/index.html +++ b/docs/blog/claude-duet-for-developers/index.html @@ -280,34 +280,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-duet-vs-munder-difflin/index.html b/docs/blog/claude-duet-vs-munder-difflin/index.html index 778f191566..d3f1d7c298 100644 --- a/docs/blog/claude-duet-vs-munder-difflin/index.html +++ b/docs/blog/claude-duet-vs-munder-difflin/index.html @@ -326,34 +326,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-duet-waitlist/index.html b/docs/blog/claude-duet-waitlist/index.html index 3e9ca656cf..49ed952e3e 100644 --- a/docs/blog/claude-duet-waitlist/index.html +++ b/docs/blog/claude-duet-waitlist/index.html @@ -290,50 +290,50 @@

FAQ

Related in Guides

- +
- 5 min + 4 min
-

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

-

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

+

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

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

Claude Duet App: iPhone, Android and Web, and How to Get It

-

Is there a Claude Duet app? Yes, on iPhone and Android, and on a computer it runs at claude.ai. Who can get it, how the download works, and the cost.

+

Codex Computer Use: How to Turn It On, Plans and Limits (Oct 2026)

+

Codex computer use lets the ChatGPT desktop app see and click apps on macOS and Windows. Setup, which plans get it, locked use and how Claude's compares.

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

Build Your Own Claude Duet: A Free Morning Brief Agent

-

No Claude Duet invite? Build a proactive morning brief in Munder Difflin: one agent, one schedule and Slack replies, with a codebase as the example.

+

Cursor Pricing (Oct 2026): Every Plan After the 12 Oct Change

+

Cursor pricing as of 26 Oct 2026: Free, Plus $8, Premium $30, Super $100, Ultra $200, Teams seats, what changed on 12 Oct and what old plans keep.

Guides - Read → + Read →
diff --git a/docs/blog/claude-md-best-practices/index.html b/docs/blog/claude-md-best-practices/index.html new file mode 100644 index 0000000000..a5de925be7 --- /dev/null +++ b/docs/blog/claude-md-best-practices/index.html @@ -0,0 +1,475 @@ + + + + + + + + +CLAUDE.md Best Practices (Oct 2026): 8 Rules From Anthropic's Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +

CLAUDE.md Best Practices: What to Write, Where It Lives, How Long (Oct 2026)

+

CLAUDE.md best practices from Anthropic's docs, checked 26 Oct 2026: what to put in it, where each file lives, the 200 line target and how AGENTS.md loads.

+ +
+ +
Title card on white. A pill reading Guide, the title CLAUDE.md best practices, the line What to write, where it lives, how long and a Checked 26 Oct 2026 line, beside a white sheet with five short ruled lines and a yellow mark labelled Under 200 lines.
+ +
+
+

The best CLAUDE.md is short, concrete and checked often: under 200 lines, only what Claude needs in every session, and hooks for anything that must never be skipped. These eight rules come from Anthropic’s memory docs and best practices page, read on 26 Oct 2026.

+

Running several Claude Code agents? Munder Difflin is free and open source: a desktop app that runs a team of AI agents for all kinds of knowledge work, coding included, on your own computer. The install guide covers setup. For habits beyond this file, see Claude Code best practices.

+

What is a CLAUDE.md file?

+

A plain markdown file of instructions that Claude Code loads at the start of every session. The memory docs say each session “begins with a fresh context window”, and CLAUDE.md is how your build commands, conventions and rules carry over. Claude writes a second kind of memory itself, auto memory, from your corrections.

+

One line matters more than the rest: Claude treats CLAUDE.md “as context, not enforced configuration”. It is advice Claude reads, not a lock.

+

Where should CLAUDE.md files live?

+

Four places, loaded from broadest to most specific, per the memory docs on 26 Oct 2026:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ScopeWhereShared with
Managed policy/Library/Application Support/ClaudeCode/CLAUDE.md on macOSEveryone in the organisation
User~/.claude/CLAUDE.mdOnly you, every project
Project./CLAUDE.md or ./.claude/CLAUDE.mdYour team, through git
Local./CLAUDE.local.md, added to .gitignoreOnly you, this project
+
+

Files above your working folder load at launch, and files in subfolders load when Claude touches a file there. They add up rather than replace each other, so the line closest to where you started Claude is read last.

+
Animation. On a white background, four sheets of paper drop onto a pile one at a time, labelled Managed, User, Project and Local. Each sheet lands slightly offset so every label stays visible, and a small arrow beside the pile reads Read last at the top sheet.
Instruction files load from broadest to most specific and add up, per Anthropic's memory docs on 26 Oct 2026.
+

What should go in CLAUDE.md?

+

Facts Claude should hold in every session and cannot work out from the code. The memory docs name build commands, conventions, project layout and “always do X” rules, and say to add a line when:

+
    +
  • Claude makes the same mistake a second time.
  • +
  • A code review catches something Claude should have known.
  • +
  • You type the same correction you typed last session.
  • +
  • A new teammate would need the same context.
  • +
+

Write lines you could check. The docs give examples: “Use 2-space indentation” instead of “Format code properly”, and “Run npm test before committing” instead of “Test your changes”. Here is a short project file in that style:

+
# Build
+- Run `npm run build` before you say a task is done.
+- Run `npm test -- path/to/file` for one test file, not the whole suite.
+
+# Style
+- TypeScript strict mode; no `any`.
+- API handlers live in `src/api/handlers/`.
+
+# Rules
+- Never edit files in `generated/`.
+

A procedure with many steps, or a rule for one corner of the repo, belongs in a skill or a path scoped rule.

+

How long should CLAUDE.md be?

+

Under 200 lines per file. The docs say longer files “consume more context and reduce adherence”. Three tools keep it short:

+
    +
  • Path scoped rules. Files in .claude/rules/ with a paths field load only when Claude works on matching files.
  • +
  • Imports. @path/to/file pulls in another file, up to four hops deep. This organises a long file but does not cut its context cost.
  • +
  • HTML comments. Block comments are stripped before Claude sees the file, so notes for people cost no tokens.
  • +
+

The best practices page asks of every line: “Would removing this cause Claude to make mistakes?” If not, cut it.

+
Animation. A tall white measuring tube fills with blue lines of text up to a yellow mark labelled 200 lines. Extra lilac lines float off to the side into three small trays labelled Rules, Skills and Imports, and the tube level settles below the mark.
Target under 200 lines per file; move the rest into rules or skills.
+

Does Claude Code read AGENTS.md?

+

Yes, from v2.1.277, but not next to a CLAUDE.md by default. The memory docs say Claude reads AGENTS.md when you have no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in your working folder or above it. Add any one of those and AGENTS.md stops loading.

+

To keep one file for every agent, import AGENTS.md from CLAUDE.md, or set Project instructions in /config to claude-md-and-agents-md so Claude reads both. Codex, OpenCode and Pi all read AGENTS.md, so one shared file keeps a mixed team in step.

+
Animation. A small blue cart rolls along a white rail toward a junction with two tracks, one to a card labelled Claude file and one to a card labelled Agents file. A yellow switch flips the cart toward the Agents file when the Claude file card fades out, then a third track appears labelled Both.
By default Claude Code reads the shared agents file only when it finds no Claude file of its own, per Anthropic's memory docs on 26 Oct 2026.
+

How do you keep CLAUDE.md up to date?

+

Treat it like code. The best practices page says to check it into git and review it when things go wrong. Run /init for a starter file, /context to confirm it loaded and /memory to list and open your memory files. From v2.1.283, /doctor prompt-audit looks for stale instructions, missing files and contradictions, and changes nothing until you say so.

+

If one rule keeps slipping, the page suggests adding “IMPORTANT” to that line alone. If a rule must hold every time, the memory docs point to a PreToolUse hook instead; see Claude Code hooks explained.

+

CLAUDE.md best practices at a glance, checked 26 Oct 2026

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RuleFrom
Under 200 lines per fileMemory docs
Only what applies in every sessionBest practices page
Concrete lines you could verifyMemory docs
Path scoped rules and skills for the restMemory docs
No two lines that disagreeMemory docs
Commit the project file, keep CLAUDE.local.md privateBoth pages
Emphasis on the one line that slips, not manyBest practices page
Hooks for rules with no exceptionsBoth pages
+
+ + + + +
+

FAQ

+ +
+

How long should a `CLAUDE.md` file be?

+

Under 200 lines per file. Anthropic's memory docs say longer files use more context and reduce adherence, and that imports help you organise a long file but do not cut its context cost. Checked 26 Oct 2026.

+
+ +
+

Does Claude Code read `AGENTS.md`?

+

Yes, from Claude Code v2.1.277. By default it reads `AGENTS.md` only when there is no `CLAUDE.md` or `CLAUDE.local.md` in your working directory or above it. The Project instructions setting in /config can make it read both.

+
+ +
+

Should `CLAUDE.md` be committed to git?

+

The project `CLAUDE.md`, yes: Anthropic's best practices page says to check it into git so your team can contribute. Keep personal notes in `CLAUDE.local.md` and add that file to `.gitignore`.

+
+ +
+

Why does Claude ignore my `CLAUDE.md`?

+

Usually the file is too long or two lines disagree. Anthropic's docs say a rule gets lost in a long file and that Claude may pick one of two conflicting instructions arbitrarily. For a rule with no exceptions, use a hook.

+
+ +
+ + + +
+ + +
+ + + + +
+ + +
+ +
+ + + + + + diff --git a/docs/blog/claude-squad-alternative/index.html b/docs/blog/claude-squad-alternative/index.html index b978bdf9bf..cb2e9a9bf7 100644 --- a/docs/blog/claude-squad-alternative/index.html +++ b/docs/blog/claude-squad-alternative/index.html @@ -286,34 +286,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/claude-squad-vs-munder-difflin/index.html b/docs/blog/claude-squad-vs-munder-difflin/index.html index 6c09243b71..efbaddc6f0 100644 --- a/docs/blog/claude-squad-vs-munder-difflin/index.html +++ b/docs/blog/claude-squad-vs-munder-difflin/index.html @@ -375,34 +375,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/cline-alternatives/index.html b/docs/blog/cline-alternatives/index.html index 189546e920..64e29bd0be 100644 --- a/docs/blog/cline-alternatives/index.html +++ b/docs/blog/cline-alternatives/index.html @@ -354,34 +354,34 @@

Codex Alternatives: 7 Picks Beyond OpenA

- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/cline-vs-munder-difflin/index.html b/docs/blog/cline-vs-munder-difflin/index.html index 8170d8a725..db4021d7b8 100644 --- a/docs/blog/cline-vs-munder-difflin/index.html +++ b/docs/blog/cline-vs-munder-difflin/index.html @@ -342,34 +342,34 @@

Codex Alternatives: 7 Picks Beyond OpenA
- +
- + - 5 min + 4 min
-

Open Source AI Agent Office Apps on GitHub (Oct 2026)

-

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

+

Pi Agent Alternatives: 7 Coding Agents to Try (Oct 2026)

+

Seven Pi agent alternatives checked 26 Oct 2026: OpenCode, Munder Difflin, Claude Code, Codex CLI, Hermes Agent, Goose and Crush, with licences and prices.

Comparisons - Read → + Read →
- +
- + - 4 min + 5 min
-

Claude Duet Alternatives: 4 AI Agents You Can Use Today, No Waitlist

-

Claude Duet is waitlisted and US only. Four proactive agents you can run today, sorted by where they run: a vendor's cloud or hardware you control.

+

Open Source AI Agent Office Apps on GitHub (Oct 2026)

+

Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences.

Comparisons - Read → + Read →
diff --git a/docs/blog/codex-alternatives/index.html b/docs/blog/codex-alternatives/index.html index c7f6d306c9..513435365e 100644 --- a/docs/blog/codex-alternatives/index.html +++ b/docs/blog/codex-alternatives/index.html @@ -344,57 +344,57 @@

FAQ