diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index fbea5de8f..ae3ff34c6 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -4588,5 +4588,27 @@ "status": "ready" }, "inline": {} + }, + "codex-vs-cursor": { + "title": "Codex vs Cursor: Which Should You Pick in 2026?", + "category": "comparisons", + "hero": { + "file": "assets/media/codex-vs-cursor/hero.png", + "alt": "Title card on white. Codex vs Cursor in large ink type, with a Comparisons kicker, Checked 7 Oct 2026, and two table tennis bats sending a ball over a net", + "prompt": "Still of the hero scene in assets/media/codex-vs-cursor/motion.js.", + "status": "ready" + }, + "inline": {} + }, + "gemini-cli-vs-claude-code": { + "title": "Gemini CLI vs Claude Code: price, licence, models", + "category": "comparisons", + "hero": { + "file": "assets/media/gemini-cli-vs-claude-code/hero.png", + "alt": "Title card on white. The words Gemini CLI vs Claude Code in large ink type with the kicker Comparisons, the line Checked 7 Oct 2026, and two kites with bow tails", + "prompt": "Still of the hero scene in assets/media/gemini-cli-vs-claude-code/motion.js.", + "status": "ready" + }, + "inline": {} } } diff --git a/blog/src/assets/media/codex-vs-cursor/drawers.png b/blog/src/assets/media/codex-vs-cursor/drawers.png new file mode 100644 index 000000000..ad79c37e4 Binary files /dev/null and b/blog/src/assets/media/codex-vs-cursor/drawers.png differ diff --git a/blog/src/assets/media/codex-vs-cursor/greenhouse.png b/blog/src/assets/media/codex-vs-cursor/greenhouse.png new file mode 100644 index 000000000..c1664af9c Binary files /dev/null and b/blog/src/assets/media/codex-vs-cursor/greenhouse.png differ diff --git a/blog/src/assets/media/codex-vs-cursor/hero.png b/blog/src/assets/media/codex-vs-cursor/hero.png new file mode 100644 index 000000000..bb75d1df7 Binary files /dev/null and b/blog/src/assets/media/codex-vs-cursor/hero.png differ diff --git a/blog/src/assets/media/codex-vs-cursor/motion.css b/blog/src/assets/media/codex-vs-cursor/motion.css new file mode 100644 index 000000000..1f96dddd6 --- /dev/null +++ b/blog/src/assets/media/codex-vs-cursor/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Codex vs Cursor". 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-vs-cursor/motion.js b/blog/src/assets/media/codex-vs-cursor/motion.js new file mode 100644 index 000000000..d39dc3e30 --- /dev/null +++ b/blog/src/assets/media/codex-vs-cursor/motion.js @@ -0,0 +1,285 @@ +/* Bright coded scenes for "Codex vs Cursor". 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 = "#7A6A88"; + const chip = (p, s, fill, a = {}) => { + const g = el("g", {}, p), r = el("rect", { y: -19, height: 38, rx: 19, fill, stroke: INK, "stroke-width": 2.5 }, g); + const t = txt(g, 0, 7, s, Object.assign({ "text-anchor": "middle", "font-size": 20, "font-weight": 700 }, a)); + const w = t.getComputedTextLength() + 36; r.setAttribute("x", -w / 2); r.setAttribute("width", w); + return g; + }; + const show = (n, k) => n.setAttribute("opacity", k <= 0.01 ? 0 : 1); + + const SC = {}; + + /* ---- 1. two chests of drawers: six drawers open for Codex, then three for Cursor ---- */ + SC.drawers = { + dur: 11.8, still: 9.6, + build(svg) { + txt(svg, 44, 62, "Where you can use each one", { "font-size": 32, "font-weight": 700 }); + const chest = (x, name) => { + txt(svg, x + 140, 134, name, { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + el("path", { d: `M${x + 26} 468 V484 M${x + 254} 468 V484`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, svg); + el("rect", { x, y: 148, width: 280, height: 320, rx: 16, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + }; + chest(70, "Codex"); chest(450, "Cursor"); + const drawer = (x, y, h, label, col, t0, big) => { + el("rect", { x: x + 12, y, width: 256, height: h, rx: 10, fill: "#fff", stroke: GREY, "stroke-opacity": 0.35, "stroke-width": 2 }, svg); + const g = el("g", {}, svg); + const face = el("rect", { x: -128, y: -h / 2, width: 256, height: h, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const l = txt(g, -108, big ? 10 : 7, label, { "font-size": big ? 30 : 21, "font-weight": 700 }); + el("circle", { cx: 100, cy: 0, r: big ? 11 : 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + return { g, face, l, col, t0, x: x + 140, y: y + h / 2 }; + }; + const D = ["desktop app", "mobile app", "web", "CLI", "IDE extension", "Cloud"].map((s, i) => drawer(70, 156 + i * 52, 44, s, YEL, 0.6 + i * 0.75)); + D.push(drawer(450, 156, 148, "editor", SKY, 5.9, true), drawer(450, 312, 70, "CLI", SKY, 6.8), drawer(450, 390, 70, "cloud agents", SKY, 7.6)); + const cap = strip(svg, 508, 660); + return (t) => { + const out = io(seg(t, 10.7, 11.4)); + D.forEach((d) => { + const k = ob(seg(t, d.t0, d.t0 + 0.45)) * (1 - out), on = seg(t, d.t0, d.t0 + 0.12) * (1 - out); + d.g.setAttribute("transform", `translate(${d.x} ${d.y + 5 * k}) scale(${1 + 0.035 * k})`); + d.face.setAttribute("fill", on > 0.5 ? d.col : "#fff"); + d.l.setAttribute("opacity", on); + }); + cap.textContent = t < 5.7 || t >= 10.7 ? "Six places to use Codex, per OpenAI's Codex docs" : "Cursor is the editor, plus a CLI and cloud agents"; + }; + }, + }; + + /* ---- 2. a glass greenhouse opens its door; a shed closes its shutters and bars the door ---- */ + SC.greenhouse = { + dur: 11.6, still: 8.6, + build(svg, api) { + txt(svg, 44, 62, "Open code, closed code", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap the shed", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const G = 396; + el("path", { d: `M40 ${G} H760`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + /* greenhouse */ + const plant = (x, col, n) => { + const g = el("g", {}, svg); + el("path", { d: "M0 -30 V-84", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, g); + [[-15, -58, -35], [15, -70, 35], [-13, -90, -30], [13, -102, 30], [0, -114, 0]].slice(0, n).forEach(([dx, dy, r]) => el("ellipse", { cx: dx, cy: dy, rx: 15, ry: 9, fill: MINT, stroke: INK, "stroke-width": 2.5, transform: `rotate(${r} ${dx} ${dy})` }, g)); + el("path", { d: "M-19 -32 H19 L14 0 H-14 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + return { g, x }; + }; + el("path", { d: `M112 ${G} V226 L220 132 L328 226 V${G} Z`, fill: SKY, "fill-opacity": 0.3, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("path", { d: `M100 226 H340 M112 312 H186 M254 312 H328 M166 226 V179 M274 226 V179 M220 132 V226 M186 ${G} V226 M254 ${G} V226`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round", fill: "none" }, svg); + const plants = [plant(149, YEL, 5), plant(291, LIL, 4)]; + el("rect", { x: 190, y: 280, width: 60, height: G - 280, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const mid = plant(224, BLUE, 3); + const door = el("g", {}, svg); + el("rect", { x: 0, y: 0, width: 60, height: G - 280, fill: "#fff", stroke: INK, "stroke-width": 3 }, door); + el("rect", { x: 0, y: 0, width: 60, height: G - 280, fill: SKY, "fill-opacity": 0.3 }, door); + el("path", { d: "M0 58 H60", stroke: INK, "stroke-width": 2.5 }, door); + el("circle", { cx: 48, cy: 72, r: 5, fill: YEL, stroke: INK, "stroke-width": 2.5 }, door); + txt(svg, 220, 436, "Codex CLI", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + const c1 = chip(svg, "Apache-2.0", MINT, { "font-family": MONO }); + /* shed */ + const shed = el("g", {}, svg), body = el("g", {}, shed); + el("rect", { x: -116, y: -272, width: 232, height: 278, rx: 18, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, shed); + el("rect", { x: -100, y: -170, width: 200, height: 170, fill: SOFT, stroke: INK, "stroke-width": 3 }, body); + el("path", { d: "M-60 -168 V-3 M-20 -168 V-3 M20 -168 V-3 M60 -168 V-3", stroke: GREY, "stroke-opacity": 0.35, "stroke-width": 2 }, body); + el("path", { d: "M-120 -170 L0 -264 L120 -170 Z", fill: LIL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, body); + el("rect", { x: -78, y: -128, width: 62, height: 62, fill: "#fff", stroke: INK, "stroke-width": 3 }, body); + el("path", { d: "M-47 -128 V-66 M-78 -97 H-16", stroke: INK, "stroke-width": 2.5 }, body); + const leaf = (hx, dir) => { + const g = el("g", {}, body); + el("rect", { x: dir > 0 ? 0 : -31, y: -128, width: 31, height: 62, fill: BLUE, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: [-112, -97, -82].map((y) => `M${dir > 0 ? 6 : -25} ${y} h19`).join(" "), stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, g); + return { g, hx }; + }; + const leaves = [leaf(-78, 1), leaf(-16, -1)]; + el("rect", { x: 14, y: -112, width: 60, height: 112, fill: SKY, stroke: INK, "stroke-width": 3 }, body); + el("path", { d: "M34 -112 V0 M54 -112 V0", stroke: INK, "stroke-opacity": 0.25, "stroke-width": 2 }, body); + const bar = el("g", {}, body); + el("rect", { x: -44, y: -9, width: 88, height: 18, rx: 5, fill: YEL, stroke: INK, "stroke-width": 3 }, bar); + [[6, -68], [70, -68]].forEach(([x, y]) => el("path", { d: `M${x} ${y} v22 h12 v-22`, stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, body)); + txt(svg, 580, 436, "Cursor", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + const c2 = chip(svg, "proprietary", LIL); + const cap = strip(svg, 510, 700); + let poked = -99; + button(shed, "The shed stays shut. Cursor's terms say you may not reverse engineer it.", () => { poked = api.now(); api.poke(); }); + return (t, now) => { + const out = io(seg(t, 10.6, 11.3)), w = now - poked, shake = w >= 0 && w < 0.9 ? Math.sin(w * 30) * 2.2 * (1 - w / 0.9) : 0; + const open = io(seg(t, 0.5, 1.5)) * (1 - out); + door.setAttribute("transform", `translate(190 280) skewY(${-14 * open}) scale(${1 - 0.72 * open} 1)`); + [...plants, mid].forEach((p, i) => { const k = ob(seg(t, 1.5 + i * 0.35, 2.1 + i * 0.35)) * (1 - out); tf(p.g, p.x, G - 3, (0.5 + 0.4 * k) * (i === 2 ? 0.85 : 1), Math.sin(now * 1.6 + i * 2) * 2 * k); }); + const k1 = ob(seg(t, 2.9, 3.3)) * (1 - out); tf(c1, 220, 468, cl(k1, 0, 1.2)); show(c1, k1); + tf(shed, 580, G, 1, shake); + const shut = io(seg(t, 4.3, 5.2)) * (1 - out); + leaves.forEach((l) => l.g.setAttribute("transform", `translate(${l.hx} 0) scale(${0.18 + 0.82 * shut} 1)`)); + const b = oc(seg(t, 5.6, 6.2)) * (1 - out); + bar.setAttribute("transform", `translate(${44 + 150 * (1 - b)} -57)`); bar.setAttribute("opacity", cl(b * 4)); + const k2 = ob(seg(t, 6.6, 7)) * (1 - out); tf(c2, 580, 468, cl(k2, 0, 1.2)); show(c2, k2); + cap.textContent = w >= 0 && w < 3.5 ? "Cursor's terms: you may not reverse engineer it" : t < 4.1 || t >= 10.6 ? "GitHub lists openai/codex under Apache-2.0" : "Cursor's terms keep ownership with Anysphere"; + }; + }, + }; + + /* ---- 3. two staircases meet in the middle; a plank lies level on the two $20 steps ---- */ + SC.stairs = { + dur: 12.4, still: 10, + build(svg, api) { + txt(svg, 44, 62, "The monthly price steps", { "font-size": 32, "font-weight": 700 }); + txt(svg, 44, 98, "tap a step", { "font-size": 20, fill: MUTE, "font-weight": 500 }); + const G = 440, W = 84, HT = { 0: 44, 8: 88, 20: 134, 60: 190, 100: 240, 200: 296 }; + const ST = [ + [342, 0, "Free", YEL, "Codex Free: the free plan"], [258, 8, "$8", YEL, "Codex Go: $8 a month"], [174, 20, "$20", YEL, "Codex Plus: $20 a month"], [90, 100, "$100", YEL, "Codex Pro: from $100 a month"], + [458, 0, "Free", SKY, "Cursor Hobby: free, limited Agent requests"], [542, 20, "$20", SKY, "Cursor Individual: $20 a month"], [626, 60, "$60", SKY, "Cursor Pro Plus: $60 a month"], [710, 200, "$200", SKY, "Cursor Ultra: $200 a month"], + ]; + let pinned = -1; + const steps = ST.map(([x, p, label, col, line], i) => { + const h = HT[p], g = el("g", {}, svg); + const r = el("rect", { x: x - W / 2, width: W, fill: col, stroke: INK, "stroke-width": 3 }, g); + const lg = el("g", {}, g); + if (p === 100) txt(lg, x, 26, "from", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + txt(lg, x, p === 100 ? 52 : 31, label, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + el("rect", { x: x - W / 2 + 6, y: G - h + 6, width: W - 12, height: h - 12, rx: 8, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 6", class: "mg-ring" }, g); + button(g, line, () => { pinned = i; api.poke(); }); + return { r, lg, h, t0: 0.5 + i * 0.75 + (i > 3 ? 0.5 : 0) }; + }); + el("path", { d: `M30 ${G} H770`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + txt(svg, 216, G + 40, "Codex", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + txt(svg, 584, G + 40, "Cursor", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + const PT = 7.6, plank = el("g", { "pointer-events": "none" }, svg); + el("rect", { x: -222, y: -24, width: 444, height: 24, rx: 8, fill: MINT, stroke: INK, "stroke-width": 3 }, plank); + const both = txt(svg, 358, G - HT[20] - 42, "both list $20 a month", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + const cap = strip(svg, 508, 700); + return (t) => { + const out = io(seg(t, 11.5, 12.2)); + let last = -1; + steps.forEach((s, i) => { + const k = cl(ob(seg(t, s.t0, s.t0 + 0.55)), 0, 1.06) * (1 - out), h = Math.max(s.h * k, 0.01); + s.r.setAttribute("y", G - h); s.r.setAttribute("height", h); show(s.r, k); + s.lg.setAttribute("transform", `translate(0 ${G - h})`); s.lg.setAttribute("opacity", seg(k, 0.85, 1)); + if (t >= s.t0) last = i; + }); + const q = seg(t, PT, PT + 0.5), land = seg(t, PT + 0.5, PT + 1.2), wob = q >= 1 ? Math.exp(-5 * land) * Math.sin(land * 16) : 0; + tf(plank, 358, G - HT[20] - 1.5 - 140 * (1 - q * q), 1, wob * 2 + (1 - q) * -5); + plank.setAttribute("opacity", seg(t, PT, PT + 0.1) * (1 - out)); + const bp = ob(seg(t, PT + 0.7, PT + 1.1)) * (1 - out); + both.setAttribute("opacity", cl(bp * 3)); both.setAttribute("transform", `translate(0 ${10 * (1 - cl(bp, 0, 1.2))})`); + cap.textContent = pinned >= 0 ? ST[pinned][4] : t > PT + 0.6 && t < 11.5 ? "Both vendors listed a $20 a month plan" : last < 0 || t >= 11.5 ? "Monthly prices, checked 7 Oct 2026" : ST[last][4]; + }; + }, + }; + + /* ---- 4. a small book slides over a shelf of books and slots into the gap ---- */ + SC.shelf = { + dur: 11.4, still: 8.4, + build(svg) { + txt(svg, 44, 62, "Codex fits inside Cursor", { "font-size": 32, "font-weight": 700 }); + const B = 420, GAPX = 373; + el("path", { d: `M150 ${B + 44} l0 26 l34 -26 Z M650 ${B + 44} l0 26 l-34 -26 Z`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("rect", { x: 70, y: B, width: 660, height: 46, rx: 10, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 400, B + 32, "Cursor", { "text-anchor": "middle", "font-size": 28, "font-weight": 700 }); + el("path", { d: `M92 ${B} V${B - 120} M708 ${B} V${B - 120}`, stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, svg); + el("rect", { x: GAPX - 30, y: B - 200, width: 60, height: 200, rx: 6, fill: "#fff", stroke: GREY, "stroke-opacity": 0.35, "stroke-width": 2, "stroke-dasharray": "5 6" }, svg); + const book = (x, w, h, col) => { + const g = el("g", {}, svg); + el("rect", { x: -w / 2, y: -h, width: w, height: h, rx: 6, fill: col, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M${-w / 2} ${-h + 24} H${w / 2} M${-w / 2} -24 H${w / 2}`, stroke: INK, "stroke-width": 2.5 }, g); + return { g, x: x + w / 2, side: x < GAPX ? -1 : 1 }; + }; + let x = 137; const books = []; + [[52, 172, SKY], [44, 150, LIL], [60, 190, BLUE], [48, 164, SOFT], [56, 182, LIL], [46, 156, SKY], [62, 190, SOFT], [50, 170, BLUE], [44, 146, MINT]].forEach(([w, h, c], i) => { if (i === 4) x += 64; books.push(book(x, w, h, c)); x += w; }); + const tag = el("g", {}, svg); + el("path", { d: "M0 0 V-34", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, tag); + const tg = chip(tag, "Codex sidebar", MINT); tg.setAttribute("transform", "translate(0 -52)"); + const cx = el("g", {}, svg); + el("rect", { x: -30, y: -100, width: 60, height: 200, rx: 6, fill: YEL, stroke: INK, "stroke-width": 3 }, cx); + txt(cx, 0, 0, "Codex extension", { "text-anchor": "middle", "font-size": 20, "font-weight": 700, transform: "rotate(-90) translate(0 7)" }); + const cap = strip(svg, 508, 700); + return (t) => { + const out = io(seg(t, 10.4, 11.1)); + const a = oc(seg(t, 0.5, 2.3)), b = io(seg(t, 2.3, 3.3)), c = seg(t, 3.5, 4), drop = c * c; + const land = seg(t, 4, 4.5), sq = Math.sin(land * Math.PI); + const px = -130 + (245 + 130) * a + (GAPX - 245) * b, py = 200 + 12 * b - 28 * Math.sin(Math.PI * b) + (B - 100 - 212) * drop; + cx.setAttribute("transform", `translate(${px + (-200 - px) * out} ${py - 60 * out}) rotate(${90 - 90 * b})`); + cx.setAttribute("opacity", 1 - seg(out, 0.6, 1)); + books.forEach((k) => tf(k.g, k.x + k.side * 5 * sq, B, 1, k.side * 1.6 * sq)); + const tp = ob(seg(t, 4.8, 5.3)) * (1 - seg(t, 10.2, 10.5)); + tag.setAttribute("transform", `translate(${GAPX} ${B - 200 + 30 * (1 - cl(tp, 0, 1.15))})`); show(tag, tp); + cap.textContent = t < 4.6 || t >= 10.4 ? "OpenAI's README names VS Code, Cursor and Windsurf" : "OpenAI's IDE extension page lists Cursor too"; + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 92, "COMPARISONS", { "font-family": MONO, "font-size": 22, "font-weight": 700, fill: MUTE, "letter-spacing": 3 }); + txt(svg, 64, 180, "Codex vs Cursor", { "font-size": 84, "font-weight": 700 }); + el("path", { d: "M66 210 H690", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 268, "Checked 7 Oct 2026", { "font-size": 26, "font-weight": 500, fill: MUTE }); + /* a game of table tennis */ + el("rect", { x: 150, y: 448, width: 660, height: 22, rx: 11, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M250 470 V500 M710 470 V500", stroke: INK, "stroke-width": 6, "stroke-linecap": "round" }, svg); + el("rect", { x: 474, y: 398, width: 12, height: 50, rx: 4, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M300 446 Q400 250 540 446 Q630 310 686 348", stroke: MUTE, "stroke-width": 3, fill: "none", "stroke-dasharray": "2 12", "stroke-linecap": "round" }, svg); + const bat = (x, y, r, col) => { + const g = el("g", { transform: `translate(${x} ${y}) rotate(${r})` }, svg); + el("rect", { x: -11, y: 40, width: 22, height: 62, rx: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + el("ellipse", { rx: 52, ry: 58, fill: col, stroke: INK, "stroke-width": 3 }, g); + }; + bat(190, 350, -28, YEL); bat(800, 350, 28, BLUE); + el("circle", { cx: 704, cy: 354, r: 15, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/codex-vs-cursor/shelf.png b/blog/src/assets/media/codex-vs-cursor/shelf.png new file mode 100644 index 000000000..94ed058bd Binary files /dev/null and b/blog/src/assets/media/codex-vs-cursor/shelf.png differ diff --git a/blog/src/assets/media/codex-vs-cursor/stairs.png b/blog/src/assets/media/codex-vs-cursor/stairs.png new file mode 100644 index 000000000..440ad96df Binary files /dev/null and b/blog/src/assets/media/codex-vs-cursor/stairs.png differ diff --git a/blog/src/assets/media/gemini-cli-vs-claude-code/books.png b/blog/src/assets/media/gemini-cli-vs-claude-code/books.png new file mode 100644 index 000000000..5c42346e6 Binary files /dev/null and b/blog/src/assets/media/gemini-cli-vs-claude-code/books.png differ diff --git a/blog/src/assets/media/gemini-cli-vs-claude-code/bucket.png b/blog/src/assets/media/gemini-cli-vs-claude-code/bucket.png new file mode 100644 index 000000000..bccead3a0 Binary files /dev/null and b/blog/src/assets/media/gemini-cli-vs-claude-code/bucket.png differ diff --git a/blog/src/assets/media/gemini-cli-vs-claude-code/calendar.png b/blog/src/assets/media/gemini-cli-vs-claude-code/calendar.png new file mode 100644 index 000000000..10cea6afe Binary files /dev/null and b/blog/src/assets/media/gemini-cli-vs-claude-code/calendar.png differ diff --git a/blog/src/assets/media/gemini-cli-vs-claude-code/hero.png b/blog/src/assets/media/gemini-cli-vs-claude-code/hero.png new file mode 100644 index 000000000..f6f7786fa Binary files /dev/null and b/blog/src/assets/media/gemini-cli-vs-claude-code/hero.png differ diff --git a/blog/src/assets/media/gemini-cli-vs-claude-code/motion.css b/blog/src/assets/media/gemini-cli-vs-claude-code/motion.css new file mode 100644 index 000000000..1fc0b3e9d --- /dev/null +++ b/blog/src/assets/media/gemini-cli-vs-claude-code/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Gemini CLI vs Claude Code". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/gemini-cli-vs-claude-code/motion.js b/blog/src/assets/media/gemini-cli-vs-claude-code/motion.js new file mode 100644 index 000000000..d6ccb5102 --- /dev/null +++ b/blog/src/assets/media/gemini-cli-vs-claude-code/motion.js @@ -0,0 +1,283 @@ +/* Bright coded scenes for "Gemini CLI vs Claude Code". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + const strip = (svg, y, w = 680) => { + el("rect", { x: (800 - w) / 2, y, width: w, height: 48, rx: 24, fill: SOFT }, svg); + return txt(svg, 400, y + 31, "", { "text-anchor": "middle", "font-size": 20, "font-weight": 500 }); + }; + + const SC = {}; + + /* ---- 1. a calendar page tears off; three cards move board, two stay pinned ---- */ + SC.calendar = { + dur: 11.6, still: 9.4, + build(svg) { + txt(svg, 44, 62, "Google's notice: who moves, who stays", { "font-size": 32, "font-weight": 700 }); + /* wall calendar */ + el("rect", { x: 36, y: 122, width: 164, height: 182, rx: 16, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M36 160 V138 a16 16 0 0 1 16 -16 H184 a16 16 0 0 1 16 16 V160 Z", fill: LIL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + [78, 158].forEach((x) => el("rect", { x: x - 6, y: 108, width: 12, height: 30, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg)); + el("path", { d: "M62 204 H174 M62 232 H174 M62 260 H140", stroke: SOFT, "stroke-width": 8, "stroke-linecap": "round" }, svg); + const page = el("g", {}, svg); + el("rect", { x: -70, y: 0, width: 140, height: 130, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, page); + txt(page, 0, 30, "JUN", { "text-anchor": "middle", "font-family": MONO, "font-size": 20, "font-weight": 700, fill: MUTE }); + txt(page, 0, 90, "18", { "text-anchor": "middle", "font-size": 64, "font-weight": 700 }); + txt(page, 0, 118, "2026", { "text-anchor": "middle", "font-family": MONO, "font-size": 20, "font-weight": 700, fill: MUTE }); + /* two boards */ + const B1 = 222, B2 = 518, BW = 256, CW = 232, CH = 46, Y0 = 176, STEP = 58; + [[B1, "Gemini CLI"], [B2, "Antigravity CLI"]].forEach(([x, name]) => { + el("rect", { x, y: 122, width: BW - (x === B2 ? 4 : 0), height: 348, rx: 20, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, x + (BW - (x === B2 ? 4 : 0)) / 2, 157, name, { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + }); + const X1 = B1 + 12, X2 = B2 + 10; + [0, 1, 2].forEach((i) => [X1, X2].forEach((x) => el("rect", { x, y: Y0 + i * STEP, width: CW, height: CH, rx: 12, fill: "#fff", "fill-opacity": 0.6, stroke: MUTE, "stroke-width": 2, "stroke-dasharray": "5 6" }, svg))); + const arrow = el("path", { d: "M484 257 H508 M499 247 L510 257 L499 267", stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + const CARDS = [["free of charge", YEL, 1], ["Google AI Pro", SKY, 1], ["Ultra", LIL, 1], ["paid API key", MINT, 0], ["Code Assist licence", MINT, 0]]; + const cards = CARDS.map(([s, col, moves], i) => { + const g = el("g", {}, svg); + el("rect", { x: 0, y: 0, width: CW, height: CH, rx: 12, fill: col, stroke: INK, "stroke-width": 3 }, g); + txt(g, CW / 2, 31, s, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const pin = el("circle", { cx: CW / 2, cy: 0, r: 7, fill: moves ? "#fff" : INK, stroke: INK, "stroke-width": 3 }, g); + return { g, pin, moves, y: Y0 + i * STEP, a: 3.2 + i * 0.95 }; + }); + const cap = strip(svg, 508, 720); + return (t) => { + const back = io(seg(t, 10.6, 11.3)); + const p = seg(t, 1.5, 2.9), e = io(p), wig = t > 1.1 && t < 1.5 ? Math.sin(t * 40) * 3 : 0; + page.setAttribute("transform", `translate(${118 + Math.sin(p * 6.2832) * 24 * (1 - p)} ${166 + 176 * e}) rotate(${wig + Math.sin(p * 7.5) * 13 * (1 - p) - 7 * e})`); + page.setAttribute("opacity", seg(t, 0, 0.3) * (1 - seg(t, 10.6, 11))); + cards.forEach((c) => { + const m = c.moves ? io(seg(t, c.a, c.a + 0.8)) - back : 0; + const hop = c.moves ? Math.sin(cl(m) * Math.PI) * -16 : 0; + const nod = c.moves ? 0 : Math.sin(seg(t, 6.4, 7) * Math.PI) * 0.06; + c.g.setAttribute("transform", `translate(${X1 + (X2 - X1) * m + CW / 2} ${c.y + hop + CH / 2}) rotate(${Math.sin(cl(m) * Math.PI) * 4}) scale(${1 + nod}) translate(${-CW / 2} ${-CH / 2})`); + }); + arrow.setAttribute("opacity", 0.25 + 0.75 * seg(t, 2.9, 3.2) * (1 - back)); + cap.textContent = t < 3 || t > 10.6 ? "The notice set the date: 18 Jun 2026" : t < 6.3 ? "Free, Google AI Pro and Ultra: now via Antigravity CLI" : "Paid API keys and Code Assist licences stay"; + }; + }, + }; + + /* ---- 2. a dripping tap with a tally, next to a bucket that arrives full ---- */ + SC.bucket = { + dur: 12, still: 9.6, + build(svg, api) { + txt(svg, 44, 62, "Two ways to pay", { "font-size": 32, "font-weight": 700 }); + el("path", { d: "M424 104 V566", stroke: SOFT, "stroke-width": 4, "stroke-linecap": "round", "stroke-dasharray": "2 12" }, svg); + txt(svg, 190, 118, "Gemini CLI", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + txt(svg, 600, 118, "Claude Code", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + let uid = 0; + const pail = (p, cx, top, w1, w2, h, col) => { + const id = "mgb" + Math.random().toString(36).slice(2, 7) + uid++, d = `M${cx - w1 / 2} ${top} H${cx + w1 / 2} L${cx + w2 / 2} ${top + h} H${cx - w2 / 2} Z`; + el("path", { d: `M${cx - w1 / 2 + 6} ${top} Q${cx} ${top - h * 0.62} ${cx + w1 / 2 - 6} ${top}`, stroke: INK, "stroke-width": 4, fill: "none", "stroke-linecap": "round" }, p); + const cp = el("clipPath", { id }, p); el("path", { d }, cp); + el("path", { d, fill: "#fff" }, p); + const w = el("rect", { x: cx - w1 / 2, width: w1, y: top, height: h, fill: col, "clip-path": `url(#${id})` }, p); + const s = el("path", { stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round", "clip-path": `url(#${id})` }, p); + el("path", { d, fill: "none", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, p); + el("rect", { x: cx - w1 / 2 - 8, y: top - 7, width: w1 + 16, height: 14, rx: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, p); + return (lv) => { const y = top + 7 + (h - 7) * (1 - lv); w.setAttribute("y", y); w.setAttribute("height", top + h - y); s.setAttribute("d", `M${cx - w1 / 2} ${y} H${cx + w1 / 2}`); return y; }; + }; + /* left: tap, bucket, tally */ + el("rect", { x: 40, y: 142, width: 16, height: 66, rx: 6, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M56 162 H178 a26 26 0 0 1 26 26 V216 H174 V192 H56 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + el("path", { d: "M120 162 V144", stroke: INK, "stroke-width": 5, "stroke-linecap": "round" }, svg); + el("rect", { x: 92, y: 132, width: 56, height: 16, rx: 8, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + const setL = pail(svg, 190, 296, 190, 140, 126, SKY); + const DROP = "M0 -13 C7 -3 9 2 9 6 A9 9 0 0 1 -9 6 C-9 2 -7 -3 0 -13 Z"; + const N = 8, A = (i) => 1.2 + i * 1.05, FALL = 0.45; + const drops = Array.from({ length: N }, () => el("path", { d: DROP, fill: SKY, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, svg)); + const xdrop = el("path", { d: DROP, fill: SKY, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round", opacity: 0 }, svg); + el("rect", { x: 60, y: 440, width: 260, height: 50, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const MAX = 15, tally = Array.from({ length: MAX }, (_, i) => { + const gx = 86 + Math.floor(i / 5) * 78, j = i % 5; + return el("path", { d: j < 4 ? `M${gx + j * 13} 452 V478` : `M${gx - 9} 475 L${gx + 48} 455`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, svg); + }); + txt(svg, 190, 528, "pay per token", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(svg, 190, 556, "on a paid Gemini API key", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + let extra = []; + const btn = el("g", { transform: "translate(326 214)" }, svg); + el("rect", { x: -76, y: -29, width: 152, height: 58, rx: 29, fill: "none", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, btn); + el("rect", { x: -70, y: -23, width: 140, height: 46, rx: 23, fill: MINT, stroke: INK, "stroke-width": 3 }, btn); + txt(btn, 0, 7, "add a drop", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + button(btn, "Add one more drop and one more tally mark", () => { if (extra.length < MAX - N) extra.push(api.now()); api.poke(); }); + /* right: the bucket that arrives full */ + const R = el("g", {}, svg); + const setR = pail(R, 600, 250, 240, 176, 172, BLUE); + el("path", { d: "M487 276 H713", stroke: INK, "stroke-width": 2.5, "stroke-dasharray": "3 8", "stroke-linecap": "round" }, R); + el("rect", { x: 546, y: 322, width: 108, height: 58, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, R); + txt(R, 600, 363, "Pro", { "text-anchor": "middle", "font-size": 36, "font-weight": 700 }); + el("rect", { x: 498, y: 440, width: 204, height: 50, rx: 25, fill: MINT, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 600, 472, "flat monthly plan", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 600, 528, "Pro: $20 a month", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(svg, 600, 556, "billed monthly", { "text-anchor": "middle", "font-size": 20, "font-weight": 500, fill: MUTE }); + return (t, now) => { + const out = seg(t, 11.3, 11.9); + let n = 0; for (let i = 0; i < N; i++) if (t >= A(i) + FALL) n++; + const xn = extra.filter((x) => now - x >= FALL).length, shown = Math.round(n * (out < 0.5 ? 1 : 0)) + xn; + const surf = setL(cl(0.1 + 0.05 * (n * (1 - out) + xn), 0, 0.9)); + drops.forEach((d, i) => { const q = seg(t, A(i), A(i) + FALL), grow = seg(t, A(i) - 0.5, A(i)); d.setAttribute("transform", `translate(190 ${232 + (surf - 232) * q * q}) scale(${grow})`); d.setAttribute("opacity", grow > 0 && q < 1 ? 1 : 0); }); + const live = extra.find((x) => now - x >= 0 && now - x < FALL); + if (live != null) { const q = (now - live) / FALL; xdrop.setAttribute("transform", `translate(190 ${232 + (surf - 232) * q * q})`); xdrop.setAttribute("opacity", 1); } else xdrop.setAttribute("opacity", 0); + tally.forEach((k, i) => k.setAttribute("opacity", i < shown ? 1 : 0.1)); + R.setAttribute("transform", `translate(${420 * (1 - ob(seg(t, 0.2, 1.1))) + 420 * io(out)} 0)`); + setR(0.84 - 0.5 * seg(t, 1.8, 10.4)); + }; + }, + }; + + /* ---- 3. two shelves of four books, one model name on each spine ---- */ + SC.books = { + dur: 11.4, still: 9, + build(svg, api) { + txt(svg, 44, 62, "Each lists its own maker's models", { "font-size": 32, "font-weight": 700 }); + txt(svg, 756, 62, "tap a book", { "text-anchor": "end", "font-size": 20, fill: MUTE, "font-weight": 500 }); + const G = 438, BW = 64; + const SH = [ + [208, "Gemini CLI", 0.5, 22, [["gemini-3-pro-preview", 330, SKY, "one of two picks under Gemini 3"], ["gemini-3-flash-preview", 330, YEL, "one of two picks under Gemini 3"], ["gemini-2.5-pro", 262, MINT, "one of two picks under Gemini 2.5"], ["gemini-2.5-flash", 290, LIL, "one of two picks under Gemini 2.5"]]], + [592, "Claude Code", 3.2, 30, [["fable", 236, LIL, "an alias in Claude Code's model docs"], ["opus", 290, YEL, "resolves to Opus 5.5 on the Anthropic API"], ["sonnet", 262, SKY, "resolves to Sonnet 5.5 on the Anthropic API"], ["haiku", 214, MINT, "an alias in Claude Code's model docs"]]], + ]; + let pinned = null; + const all = []; + SH.forEach(([x, name, t0, fs, books]) => { + el("rect", { x: x - 178, y: G, width: 356, height: 16, rx: 8, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + [-1, 1].forEach((s) => el("path", { d: `M${x + s * 140} ${G} V${G - 110} Q${x + s * 172} ${G - 110} ${x + s * 172} ${G} Z`, fill: BLUE, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg)); + txt(svg, x, G + 52, name, { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }); + books.forEach(([s, h, col, line], i) => { + const bx = x - 136 + 2 + i * 68, g = el("g", {}, svg); + el("rect", { x: -4, y: -h - 4, width: BW + 8, height: h + 4, rx: 10, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 7", class: "mg-ring" }, g); + el("rect", { x: 0, y: -h, width: BW, height: h, rx: 7, fill: col, stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M0 ${-h + 13} H${BW} M0 -13 H${BW}`, stroke: INK, "stroke-width": 2.5 }, g); + txt(g, 0, 0, s, { "text-anchor": "middle", "font-family": MONO, "font-size": fs, "font-weight": 700, transform: `translate(${BW / 2 + fs * 0.3} ${-h / 2}) rotate(-90)` }); + const b = { g, bx, a: t0 + i * 0.55, s, line, tip: 0, t0: -9 }; + button(g, `${s}: ${line}`, () => { all.forEach((o) => { if (o !== b && o.tip) { o.tip = 0; o.t0 = api.now(); } }); b.tip = b.tip ? 0 : 1; b.t0 = api.now(); pinned = b.tip ? b : null; api.poke(); }); + all.push(b); + }); + }); + const cap = strip(svg, 512, 720); + return (t, now) => { + const out = seg(t, 10.7, 11.2); + let last = null; + all.forEach((b) => { + const q = seg(t, b.a, b.a + 0.5), k = cl((now - b.t0) / 0.25), tip = b.tip ? k : 1 - k, land = Math.sin(seg(t, b.a + 0.5, b.a + 0.8) * Math.PI) * 3; + b.g.setAttribute("transform", `translate(${b.bx + BW / 2} ${G - 360 * (1 - oc(q)) - 16 * tip}) rotate(${-5 * tip + land * (1 - q >= 0 ? 0.4 : 0)}) translate(${-BW / 2} 0)`); + b.g.setAttribute("opacity", seg(t, b.a, b.a + 0.12) * (1 - out)); + if (t >= b.a + 0.3) last = b; + }); + cap.textContent = pinned ? `${pinned.s}: ${pinned.line}` : !last || t >= 10.7 || t > 6 ? "Four model names from each tool's model docs" : last.s; + }; + }, + }; + + /* ---- 4. two umbrellas leave the stand and open, three panels each ---- */ + SC.umbrellas = { + dur: 12, still: 9.2, + build(svg) { + txt(svg, 44, 62, "Our view: who each one suits", { "font-size": 32, "font-weight": 700 }); + const LEN = 336; + const brolly = (cols, labels) => { + const g = el("g", {}, svg); + el("path", { d: `M0 -18 V${LEN} q0 24 -19 24 q-17 0 -17 -17`, stroke: INK, "stroke-width": 5, fill: "none", "stroke-linecap": "round" }, g); + const ps = cols.map((c) => el("path", { fill: c, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g)); + const ls = labels.map((lines) => { const lg = el("g", {}, g); lines.forEach((s, j) => txt(lg, 0, j * 25, s, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 })); return { lg, n: lines.length }; }); + return (x, y, rot, o) => { + const w = 13 + 161 * o, yb = 196 - 46 * o, s = 26 * o, a = w * 0.28, f = (v) => v.toFixed(1); + const rib = (px) => `Q${f(px * 0.85)} ${f(yb * 0.35)} 0 0`; + ps[0].setAttribute("d", `M0 0 C${f(-w * 0.55)} 0 ${f(-w)} ${f(yb * 0.45)} ${f(-w)} ${f(yb)} Q${f(-(w + a) / 2)} ${f(yb - s)} ${f(-a)} ${f(yb)} ${rib(-a)} Z`); + ps[1].setAttribute("d", `M0 0 Q${f(-a * 0.85)} ${f(yb * 0.35)} ${f(-a)} ${f(yb)} Q0 ${f(yb - s)} ${f(a)} ${f(yb)} ${rib(a)} Z`); + ps[2].setAttribute("d", `M0 0 C${f(w * 0.55)} 0 ${f(w)} ${f(yb * 0.45)} ${f(w)} ${f(yb)} Q${f((w + a) / 2)} ${f(yb - s)} ${f(a)} ${f(yb)} ${rib(a)} Z`); + ls.forEach((l, i) => { l.lg.setAttribute("transform", `translate(${(i - 1) * w * 0.6} ${yb * (i === 1 ? 0.68 : 0.66) - (l.n - 1) * 12})`); l.lg.setAttribute("opacity", seg(o, 0.8, 1)); }); + g.setAttribute("transform", `translate(${x} ${y}) rotate(${rot} 0 ${LEN})`); + }; + }; + const U = [ + { d: brolly([YEL, SKY, MINT], [["Apache", "2.0"], ["paid", "API key"], ["work", "licence"]]), hx: 386, hr: -4, x: 212, a: 0.8 }, + { d: brolly([LIL, YEL, SKY], [["monthly", "plan"], ["web"], ["desktop", "app"]]), hx: 414, hr: 4, x: 588, a: 3.9 }, + ]; + /* the stand sits in front of the furled umbrellas */ + el("rect", { x: 346, y: 392, width: 108, height: 128, rx: 14, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M346 432 H454 M346 480 H454", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 336, y: 382, width: 128, height: 20, rx: 10, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const names = [txt(svg, 212, 548, "Gemini CLI", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 }), txt(svg, 588, 548, "Claude Code", { "text-anchor": "middle", "font-size": 26, "font-weight": 700 })]; + return (t) => { + const back = io(seg(t, 11, 11.7)), shut = io(seg(t, 10.5, 11)); + U.forEach((u, i) => { + const m = cl(io(seg(t, u.a, u.a + 1)) - back), o = cl(ob(seg(t, u.a + 1.1, u.a + 1.9)) - shut, 0, 1.03); + const sway = Math.sin(t * 1.3 + i * 2) * 1.4 * cl(o); + u.d(u.hx + (u.x - u.hx) * m, 160 - 24 * m - 70 * Math.sin(m * Math.PI), u.hr * (1 - m) + sway, o); + names[i].setAttribute("opacity", 0.25 + 0.75 * cl(o)); + }); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 1, still: 0, + build(svg) { + txt(svg, 66, 112, "COMPARISONS", { "font-family": MONO, "font-size": 22, "font-weight": 700, fill: MUTE, "letter-spacing": 3 }); + txt(svg, 64, 208, "Gemini CLI vs", { "font-size": 76, "font-weight": 700 }); + txt(svg, 64, 296, "Claude Code", { "font-size": 76, "font-weight": 700 }); + el("path", { d: "M66 324 H490", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 386, "Checked 7 Oct 2026", { "font-size": 26, "font-weight": 500, fill: MUTE }); + const kite = (x, y, r, col, bows, tail) => { + el("path", { d: tail, stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round" }, svg); + bows.forEach(([bx, by, br, c]) => el("path", { d: "M0 0 L-15 -10 V10 Z M0 0 L15 -10 V10 Z", fill: c, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round", transform: `translate(${bx} ${by}) rotate(${br})` }, svg)); + const g = el("g", { transform: `translate(${x} ${y}) rotate(${r})` }, svg); + el("path", { d: "M0 -92 L64 -14 L0 96 L-64 -14 Z", fill: col, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("path", { d: "M0 -92 V96 M-64 -14 H64", stroke: INK, "stroke-width": 3 }, g); + }; + kite(672, 176, 24, SKY, [[612, 318, 30, YEL], [604, 392, -24, MINT], [632, 456, 20, LIL]], "M633 264 Q596 300 612 318 Q632 356 604 392 Q590 430 632 456 Q650 470 640 496"); + kite(826, 286, -16, LIL, [[832, 422, -12, SKY], [868, 470, 28, YEL]], "M852 378 Q822 400 832 422 Q846 452 868 470 Q884 484 874 506"); + return () => {}; + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/gemini-cli-vs-claude-code/umbrellas.png b/blog/src/assets/media/gemini-cli-vs-claude-code/umbrellas.png new file mode 100644 index 000000000..579dc9b39 Binary files /dev/null and b/blog/src/assets/media/gemini-cli-vs-claude-code/umbrellas.png differ diff --git a/blog/src/posts/codex-vs-cursor.md b/blog/src/posts/codex-vs-cursor.md new file mode 100644 index 000000000..f7442abb6 --- /dev/null +++ b/blog/src/posts/codex-vs-cursor.md @@ -0,0 +1,107 @@ +--- +title: "Codex vs Cursor: Which Should You Pick in 2026?" +seoTitle: "Codex vs Cursor (2026): Price, Models, Licence Compared" +description: "Codex vs Cursor, checked 7 Oct 2026: what each is, licence, paid plans from $8 a month, models, where each runs, and whether Codex works inside Cursor." +date: 2026-10-07 +category: comparisons +categoryLabel: Comparisons +type: Non-technical +primaryKeyword: "codex vs cursor" +secondaryKeywords: ["cursor vs codex", "codex cli vs cursor", "is codex better than cursor", "codex vs cursor pricing", "can you use codex in cursor"] +tags: ["Comparisons", "Codex", "IDE", "CLI Agents", "Cost"] +faq: + - q: "Is Codex better than Cursor?" + a: "Neither is better for everyone. We think Codex fits you if you want a terminal agent with open source code on a ChatGPT plan, and Cursor fits you if you want an editor with models from several labs. Both vendors listed a $20 a month plan on 7 Oct 2026." + - q: "Can you use Codex in Cursor?" + a: "Yes. The openai/codex README says: if you want Codex in your code editor (VS Code, Cursor, Windsurf), install in your IDE. OpenAI's IDE extension page lists Cursor too. Both were checked on 7 Oct 2026." + - q: "Which is cheaper, Codex or Cursor?" + a: "Outside India, Codex lists the lowest priced paid plan, but its listing does not name the CLI. On 7 Oct 2026 OpenAI's Codex pricing page listed Go at $8 a month, with GPT-6 Luna at Standard speed in the desktop app, subject to rollout. Plus at $20 a month is the first plan that lists the CLI and the IDE extension. Cursor's pricing page listed Individual at $20 a month. Cursor's docs list Start at ₹649 a month, tax inclusive, India only. Both also list a free plan: ChatGPT Free with GPT-6 Luna at Standard speed in the desktop app, subject to rollout, and Cursor Hobby with limited Agent requests." + - q: "Is Codex or Cursor open source?" + a: "The Codex CLI is. GitHub listed openai/codex under Apache-2.0 on 7 Oct 2026. Cursor is not: its terms of service say Anysphere and its licensors own the Service and forbid reverse engineering it." + - q: "Does Cursor have a CLI like Codex?" + a: "Yes. Cursor's CLI docs describe a terminal agent with Agent, Plan and Ask modes, installed with a curl command on macOS, Linux and WSL, and a PowerShell command on Windows. Checked 7 Oct 2026." +--- + +Our view: pick Codex if you want OpenAI's coding agent in your terminal with open source code, and pick Cursor if you want an editor with an agent and models from several labs. Both vendors listed a $20 a month plan. Checked 7 Oct 2026. + +This is one of our head to head posts in the [Comparisons hub](/blog/topics/comparisons/). If Claude is on your list too, read [Claude Code vs Cursor](/blog/claude-code-vs-cursor/) and [Codex vs Claude Code](/blog/codex-cli-vs-claude-code/). + +## What are Codex and Cursor? + +Codex is OpenAI's coding agent, and Cursor is a code editor with an agent built in. OpenAI's [Codex docs](https://learn.chatgpt.com/docs) list six places to use Codex: the ChatGPT desktop app, the mobile app, the web, the Codex CLI, the Codex IDE extension and Codex Cloud. Our [what is Codex](/blog/what-is-codex/) page explains each. + +Cursor is the editor, plus a CLI and cloud agents. Its [CLI docs](https://cursor.com/docs/cli/overview) say the CLI "lets you interact with AI agents directly from your terminal to write, review, and modify code", with Agent, Plan and Ask modes. + +
Animation. Six drawers open one at a time in a chest labelled Codex: desktop app, mobile app, web, CLI, IDE extension and Cloud. Beside it a chest labelled Cursor opens one large drawer for its editor, then two smaller drawers for its CLI and cloud agents.
Six places to use Codex, per OpenAI's Codex docs, and Cursor's editor, CLI and cloud agents, per Cursor's docs. Checked 7 Oct 2026.
+ +The names do not help. Cursor's model list has entries such as GPT-5.3 Codex (hidden by default), which is a model in Cursor's list, not the Codex agent. + +## Codex vs Cursor compared, checked 7 Oct 2026 + +Every row was checked on 7 Oct 2026 against the vendor's own pages or GitHub. The rows are our selection, and the plan rows cover individual plans only. + +| | Codex | Cursor | +| --- | --- | --- | +| What it is | Coding agent: CLI, desktop app, IDE extension, cloud. | Editor with an agent, a CLI and cloud agents. | +| Licence | CLI is Apache-2.0. | Proprietary. | +| Free plan | Free: GPT-6 Luna at Standard speed in the desktop app, subject to rollout. | Hobby: limited Agent requests. | +| Plan under $20 | Go, $8 a month: GPT-6 Luna at Standard speed in the desktop app, subject to rollout. | Start, ₹649 a month, tax inclusive, India only. | +| First $20 plan | Plus, $20 a month: web, CLI, IDE extension, iOS. | Individual, $20 a month. | +| Higher plans | Pro, from $100 a month. | Pro Plus, $60; Ultra, $200 a month. | +| Models | GPT-6.1 Sol and GPT-6 Luna on Plus. | Cursor's own, plus OpenAI, Anthropic, Google and others. | +| Pay by token | API key, at API pricing. | On-demand usage at API rates. | +| Latest CLI release | rust-v0.161.0, 7 Oct 2026. | Not checked. | + +Sources: OpenAI's [Codex pricing page](https://learn.chatgpt.com/docs/pricing), the [openai/codex repo](https://github.com/openai/codex), [Cursor's pricing page](https://cursor.com/pricing) and Cursor's [models and pricing docs](https://cursor.com/docs/models-and-pricing). + +## Is Codex or Cursor open source? + +The Codex CLI is open source and Cursor is not. GitHub listed openai/codex under Apache-2.0 on 7 Oct 2026, and its latest release was rust-v0.161.0, published on 7 Oct 2026. The open part is the CLI. Codex Cloud runs on OpenAI's side. + +[Cursor's terms of service](https://cursor.com/terms-of-service) say Anysphere and its licensors "own and retain all right, title and interest in and to the Service", and that you may not reverse engineer it. + +
Animation. A glass greenhouse labelled Codex CLI, Apache-2.0, has its door swing open so the plants inside show. Next to it a shed labelled Cursor, proprietary, closes its shutters and stays shut while a bar slides across its door.
Codex CLI is Apache-2.0 per GitHub; Cursor's terms keep ownership with Anysphere. Checked 7 Oct 2026.
+ +## How much do Codex and Cursor cost? + +Both start free, and both listed a $20 a month plan on 7 Oct 2026. OpenAI's Codex pricing page listed Free and Go, at $8 a month, each with "GPT-6 Luna at Standard speed in the desktop app, subject to rollout", then Plus at $20 a month and Pro from $100 a month on 7 Oct 2026. Plus is the first plan that lists the CLI and the IDE extension. An API key also works in the CLI, SDK or IDE extension, billed at API pricing, with no cloud features. + +Cursor's pricing page listed Hobby as free with "Limited Agent requests" and Individual at $20 a month on 7 Oct 2026. On the same day its models and pricing docs named the paid tiers Pro at $20, Pro Plus at $60 and Ultra at $200 a month. + +
Animation. Two staircases rise step by step and meet in the middle. The Codex steps are Free, 8 dollars, 20 dollars and from 100 dollars. The Cursor steps are Free, 20 dollars, 60 dollars and 200 dollars. A plank drops and rests level on the two 20 dollar steps.
Monthly prices from OpenAI's Codex pricing page and Cursor's pricing docs, 7 Oct 2026.
+ +The limits work differently. OpenAI's page estimates local messages per five hour period for Plus, and says weekly limits may also apply. Cursor's docs describe two usage pools that reset with your monthly billing cycle, then on-demand usage at API rates. Our [Codex pricing](/blog/codex-pricing/) page has the full plan list. + +## Which models do Codex and Cursor run? + +Codex runs OpenAI models, and Cursor offers models from several labs. OpenAI's [Codex models page](https://learn.chatgpt.com/docs/models) recommends GPT-6.1 Sol and GPT-6 Luna, and also lists Astra. The pricing page lists GPT-6.1 Sol and GPT-6 Luna on Plus, and Astra Ultrafast access on Pro $500. The same models page says a custom provider needs "a compatible Responses API endpoint". + +[Cursor's models page](https://cursor.com/docs/models) lists its own pool (Grok and Composer) plus models from Anthropic, Google, OpenAI, Z.ai, Moonshot and Meta. Claude Opus 5.5, Gemini 3.1 Pro and GPT-5.6 Sol were on it on 7 Oct 2026. Models outside Cursor's own pool are charged at the model's API price, per its pricing docs. + +## Where does each one run? + +Both run on your machine and in the cloud. The Codex CLI "runs locally on your computer", in the README's words. OpenAI's [Codex Cloud page](https://learn.chatgpt.com/docs/cloud) says you can start and follow cloud tasks from the web, mobile or desktop app, or from your terminal. + +Cursor's [cloud agents docs](https://cursor.com/docs/cloud-agent) say they "run in isolated VMs in the cloud", that you can run as many as you want in parallel, and that you need a paid Cursor plan. + +## Can you use Codex in Cursor? + +Yes, through OpenAI's IDE extension. The openai/codex README says: "If you want Codex in your code editor (VS Code, Cursor, Windsurf), install in your IDE." OpenAI's [IDE extension page](https://learn.chatgpt.com/docs/ide) lists Cursor too, and tells you to choose the Codex icon, or run `Codex: Open Codex Sidebar` from the Command Palette. + +
Animation. A small book labelled Codex extension slides over a row of books and slots into a gap on a larger shelf labelled Cursor. A tag pops up from it reading Codex sidebar.
OpenAI's README and IDE extension page both name Cursor as a supported editor. Checked 7 Oct 2026.
+ +So the choice is not strict. You can keep Cursor's editor and run Codex beside it on a ChatGPT plan that lists the IDE extension. Our [Codex CLI install guide](/blog/how-to-install-codex-cli/) covers the terminal side. + +## Who should pick Codex, and who should pick Cursor? + +Pick by where you like to work. Our view: + +- **You live in a terminal, or want open source code:** Codex. +- **You already pay for ChatGPT Plus or Pro:** Codex, since the plan lists it. +- **You want to read each change in an editor:** Cursor. +- **You want Claude, Gemini and GPT models in one picker:** Cursor. +- **You want both:** Cursor with the Codex extension. + +There is also a different kind of tool. [Munder Difflin](https://harnessmd.com/download) is free and open source: a desktop app that runs a team of coding agents such as Claude Code, Codex and Gemini CLI on your own computer. It fits if you want several agents on one job, and the [install guide](/blog/how-to-install-and-use-munder-difflin/) covers setup. + + diff --git a/blog/src/posts/gemini-cli-vs-claude-code.md b/blog/src/posts/gemini-cli-vs-claude-code.md new file mode 100644 index 000000000..84c8ef074 --- /dev/null +++ b/blog/src/posts/gemini-cli-vs-claude-code.md @@ -0,0 +1,106 @@ +--- +title: "Gemini CLI vs Claude Code: price, licence, models" +seoTitle: "Gemini CLI vs Claude Code: Price, Licence, Models (Oct 2026)" +description: "Gemini CLI vs Claude Code, checked 7 Oct 2026: whether Gemini CLI is still free, what each costs, licence, models, features and who should pick which." +date: 2026-10-07 +category: comparisons +categoryLabel: Comparisons +type: Technical +primaryKeyword: "gemini cli vs claude code" +secondaryKeywords: ["claude code vs gemini cli", "gemini cli or claude code", "is gemini cli free", "gemini cli pricing", "gemini cli vs claude code pricing"] +tags: ["Comparisons", "Claude Code", "Gemini CLI", "CLI Agents", "Engines"] +faq: + - q: "Is Gemini CLI free?" + a: "The software is free under Apache 2.0, but free use of Google's models in it is not something to count on. Google's notice of 19 May 2026 said Gemini CLI would stop serving requests for Google AI Pro and Ultra, and for people using it free of charge, on 18 Jun 2026. The README still listed a free tier when we read it on 7 Oct 2026." + - q: "Is Gemini CLI or Claude Code cheaper?" + a: "It depends on how you pay. Claude Code comes with Claude Pro at $20 a month billed monthly (claude.com/pricing, 7 Oct 2026). Gemini CLI has no plan of its own for individuals now, so you pay per token on a Gemini API key: Gemini 2.5 Pro was $1.25 in and $10.00 out per 1M tokens for prompts up to 200k tokens on Google's pricing page that day." + - q: "Is Claude Code open source like Gemini CLI?" + a: "No. The GitHub API listed Gemini CLI as Apache 2.0 on 7 Oct 2026. Claude Code's LICENSE.md read \"© Anthropic PBC. All rights reserved.\" on the same day." + - q: "Can Gemini CLI run Claude models, or Claude Code run Gemini?" + a: "Neither tool's docs list the other maker's models. Gemini CLI's model page names Gemini 3 and Gemini 2.5 models. Claude Code's model docs name Fable, Opus, Sonnet and Haiku aliases. Both pages checked 7 Oct 2026." + - q: "Can I use Gemini CLI and Claude Code together?" + a: "Yes. They are separate programs with separate sign ins, and both can be installed on one machine. Claude Code's docs say it can read an AGENTS.md file, and Gemini CLI's docs say its context file name can be set to AGENTS.md, so one instruction file can serve both." +--- + +Our view: pick Claude Code if you want a flat monthly plan, and Gemini CLI if you want an Apache 2.0 tool on a paid Gemini API key. Google said Gemini CLI would stop serving free, Google AI Pro and Ultra users on 18 Jun 2026. Checked 7 Oct 2026. + +This sits in our [Comparisons hub](/blog/topics/comparisons/). For setup, read [how to install Gemini CLI](/blog/how-to-install-gemini-cli/). For OpenAI's tool, read [Codex vs Claude Code](/blog/codex-cli-vs-claude-code/). + +## What are Gemini CLI and Claude Code? + +Both are coding agents you run in a terminal, one from Google and one from Anthropic. The [Gemini CLI repo](https://github.com/google-gemini/gemini-cli) calls it "an open-source AI agent that brings the power of Gemini directly into your terminal". The [Claude Code docs](https://code.claude.com/docs/en/overview) call it "an agentic coding tool that reads your codebase, edits files, runs commands, and integrates with your development tools", available in "your terminal, IDE, desktop app, and browser". + +Both shipped a release the day before we checked. The GitHub API (`gh api repos/google-gemini/gemini-cli/releases/latest`, and the same call for `anthropics/claude-code`) showed Gemini CLI v0.63.0 and Claude Code v2.1.292, both published 6 Oct 2026 (UTC). + +## Is Gemini CLI still free? + +No, not if you go by Google's own notice. The [transition notice](https://github.com/google-gemini/gemini-cli/discussions/27274), posted on 19 May 2026, says: "On June 18, 2026, Gemini CLI will stop serving requests for Google AI Pro and Ultra, as well as those using it free of charge. These tiers are now supported via Antigravity CLI." + +The same notice says what stays. Access through a Gemini Code Assist Standard or Enterprise licence, or through Google Cloud, "remains fully supported". Gemini CLI "will also remain accessible via paid Gemini and Gemini Enterprise Agent Platform API keys". + +
Animation. A wall calendar shows a page for 18 Jun 2026. The page tears off and floats down. Three cards pinned beside it, marked free of charge, Google AI Pro and Ultra, slide off along an arrow to a second board headed Antigravity CLI. Two cards, marked paid API key and Code Assist licence, stay pinned.
Google's notice set 18 Jun 2026 for free, Google AI Pro and Ultra users. Paid keys and work licences stay. Notice read 7 Oct 2026.
+ +The README has not caught up. As of 7 Oct 2026 it still listed "60 requests/min and 1,000 requests/day" for a Google account sign in. The notice is the later document and names free use directly, and the [Gemini CLI docs](https://geminicli.com/docs/) carry a banner: "Unpaid tier and Google One users: Gemini CLI was replaced by Antigravity CLI on June 18th, 2026." + +One loose end: the [quota page](https://geminicli.com/docs/resources/quota-and-pricing/) still lists an unpaid API key tier of 250 requests a day, "Flash model only". The notice promises paid keys and nothing else. We think you should plan on paying. + +## What does each one cost? + +Claude Code comes with a Claude subscription or an API key, and Gemini CLI is now pay per token or a work licence. These rows are our selection, from [claude.com/pricing](https://claude.com/pricing), the [Gemini API pricing page](https://ai.google.dev/gemini-api/docs/pricing) and Google's notice, all read on 7 Oct 2026. Prices are in US dollars. + +| | Gemini CLI | Claude Code | +| --- | --- | --- | +| Free use | Ended 18 Jun 2026, per Google's notice | Not listed on the Free plan | +| Individual plan | Google AI Pro and Ultra now go through Antigravity CLI | Pro: $20 a month, or $17 a month billed annually | +| Higher plan | No individual plan in the notice | Max: from $100 a month | +| Team | Gemini Code Assist Standard or Enterprise licence | Team standard seat: $25 a month, or $20 billed annually | +| API, top row | Gemini 2.5 Pro: $1.25 in, $10.00 out per 1M tokens (prompts up to 200k tokens) | Opus 5.5: $4 in, $20 out per 1M tokens | +| API, lower row | Gemini 2.5 Flash: $0.30 in, $2.50 out per 1M tokens | Sonnet 5.5: $2 in, $10 out per 1M tokens | + +Per token prices do not tell you which tool is cheaper for a task. Nobody here has measured how many tokens each one uses. The Max card says you "choose 5x or 20x more usage than Pro". [How much Claude Code costs](/blog/how-much-does-claude-code-cost/) covers the limits. + +
Animation. On the left, under Gemini CLI, a garden tap drips into a bucket and a tally slip below it gains a mark with every drop, labelled pay per token. On the right, under Claude Code, a bucket marked Pro, $20 a month, arrives already filled to a line and the level slowly drops as it is used. A tap button on the left adds one more drop and one more tally mark.
Pay per token on a paid Gemini key, or Claude Pro at $20 a month billed monthly, per both pricing pages on 7 Oct 2026.
+ +## Which one is open source? + +Gemini CLI is, and Claude Code is not. The GitHub API listed Gemini CLI as Apache-2.0 on 7 Oct 2026, and Google's notice says the project "remains available to the community as an Apache 2.0 licensed repository with no changes". + +Claude Code's [LICENSE.md](https://github.com/anthropics/claude-code/blob/main/LICENSE.md) is one line: "© Anthropic PBC. All rights reserved. Use is subject to Anthropic's Commercial Terms of Service." We go through that in [is Claude Code open source](/blog/is-claude-code-open-source/). + +## Which models does each one run? + +Each one's docs list its own maker's models. The [Gemini CLI model page](https://geminicli.com/docs/cli/model/) offers two automatic settings: Gemini 3, which picks between `gemini-3-pro-preview` and `gemini-3-flash-preview`, and Gemini 2.5, which picks between `gemini-2.5-pro` and `gemini-2.5-flash`. + +The [Claude Code model docs](https://code.claude.com/docs/en/model-config) list the aliases `fable`, `opus`, `sonnet` and `haiku`. On the Anthropic API, `opus` resolves to Opus 5.5 and `sonnet` to Sonnet 5.5, and the default on Pro, Max, Team, Enterprise and the Anthropic API is Opus 5.5. + +
Animation. Two short shelves, each held by a pair of bookends. On the Gemini CLI shelf four books slide in one by one, with spines reading gemini-3-pro-preview, gemini-3-flash-preview, gemini-2.5-pro and gemini-2.5-flash. On the Claude Code shelf four books slide in, with spines reading fable, opus, sonnet and haiku. Tapping a book tips it forward.
Four model names from each tool's model docs. Checked 7 Oct 2026.
+ +## Which features differ? + +Fewer than you might expect, going by the docs. Both sets of docs describe MCP servers, hooks, subagents and a plan mode you enter with `Shift+Tab` or `/plan`. The [Gemini CLI plan mode page](https://geminicli.com/docs/cli/plan-mode/) calls it "a read-only environment", and [Claude Code's permission docs](https://code.claude.com/docs/en/permission-modes) say plan mode has Claude "research and propose changes without making them". + +The differences we could source: + +- **Instruction file.** Gemini CLI reads `GEMINI.md`, and its [context file docs](https://geminicli.com/docs/cli/gemini-md/) say `context.fileName` can add names such as `AGENTS.md`. Claude Code reads `CLAUDE.md` and can read an `AGENTS.md` in its place. +- **Where it runs.** Claude Code's docs list the terminal, IDE extensions, a desktop app and the web. Gemini CLI's README describes a terminal tool and a GitHub Action. +- **Built in extras.** Gemini CLI's README lists Google Search grounding, conversation checkpointing and sandboxing. Claude Code's overview lists skills, auto memory and scheduled tasks. + +We have not run a benchmark between them, so we are not ranking quality or speed. + +## Who should pick which? + +Our view: it comes down to how you want to pay and whether the licence matters to you. + +
Animation. Two closed umbrellas stand in a stand. The Gemini CLI umbrella opens to show three panels reading Apache 2.0, paid API key and work licence. The Claude Code umbrella opens to show three panels reading monthly plan, web and desktop app. Each umbrella sways gently once it is open.
Our view of who each tool suits, from the sources above. Checked 7 Oct 2026.
+ +- **Gemini CLI** if you need an Apache 2.0 tool, already pay for a Gemini API key, or your company holds a Gemini Code Assist licence. +- **Claude Code** if you want one monthly price, or want the same tool in a desktop app and the browser. +- **On Google AI Pro or Ultra:** neither. Google's notice points you to Antigravity CLI. + +More options are in [Claude Code alternatives](/blog/claude-code-alternatives/) and [best AI coding agents](/blog/best-ai-coding-agents/). + +## Can you run both? + +Yes. They are separate programs with separate sign ins, so both can live on one machine. To run them side by side there is [Munder Difflin](https://harnessmd.com/download), 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. See [how to install and use Munder Difflin](/blog/how-to-install-and-use-munder-difflin/). + + diff --git a/blog/src/posts/grok-bot-pricing.md b/blog/src/posts/grok-bot-pricing.md index 6a9155149..1a3bed5b5 100644 --- a/blog/src/posts/grok-bot-pricing.md +++ b/blog/src/posts/grok-bot-pricing.md @@ -1,7 +1,7 @@ --- title: "Grok Bot Pricing: What $20, $30, $100 and $300 a Month Actually Buy" -seoTitle: "Grok Bot Pricing 2026: Every Plan, Free Trial and Cheapest Way In" -description: "Grok Bot has no plan of its own. It comes bundled with SuperGrok and Cursor plans from $20 to $300 a month. Here is what each one gets you, what the free trial is, and the $0 route for work agents." +seoTitle: "Grok Bot Pricing: Plans From $20 to $300, and Is It Free?" +description: "Grok Bot has no plan of its own. The cheapest way in is a $20 a month Cursor plan, SuperGrok is $30, and there is a seven day trial, not a free plan." date: 2026-09-26 category: comparisons categoryLabel: Comparisons diff --git a/blog/src/posts/how-to-install-gemini-cli.md b/blog/src/posts/how-to-install-gemini-cli.md index a4c91fe4e..7bab7a7d8 100644 --- a/blog/src/posts/how-to-install-gemini-cli.md +++ b/blog/src/posts/how-to-install-gemini-cli.md @@ -118,4 +118,4 @@ npm uninstall -g @google/gemini-cli The [configuration reference](https://geminicli.com/docs/reference/configuration/) lists `general.enableAutoUpdate`, on by default, but compare `gemini --version` with npm now and then anyway. If it still shows an old number after updating, `which -a gemini` lists every copy on your `PATH` in the order your shell tries them, which is how you spot a leftover Homebrew copy shadowing a fresh npm one. -Running Codex too? [How to install Codex CLI](/blog/how-to-install-codex-cli/) covers OpenAI's agent and its sign in quirks. +Running Codex too? [How to install Codex CLI](/blog/how-to-install-codex-cli/) covers OpenAI's agent and its sign in quirks. Comparing tools? See [Gemini CLI vs Claude Code](/blog/gemini-cli-vs-claude-code/). diff --git a/blog/src/posts/what-is-codex.md b/blog/src/posts/what-is-codex.md index 7f5e79bcd..a93de561d 100644 --- a/blog/src/posts/what-is-codex.md +++ b/blog/src/posts/what-is-codex.md @@ -94,4 +94,4 @@ No: ChatGPT is OpenAI's assistant, and Codex is the coding agent that ships with ## How does Codex compare with Claude Code? -Codex and Claude Code do the same job, and the plan you already pay for usually decides. Codex's CLI is open source under Apache-2.0 and its Free tier lets you try the desktop app for nothing. Claude Code is Anthropic's agent, runs Claude models only and reads `CLAUDE.md` for instructions. Neither is a clear winner for every repo. Our [Codex vs Claude Code comparison](/blog/codex-cli-vs-claude-code/) has the dated table on limits, sandboxing and computer use, and the [openai/codex repo](https://github.com/openai/codex) is the place to read the CLI's own docs. +Codex and Claude Code do the same job, and the plan you already pay for usually decides. Codex's CLI is open source under Apache-2.0 and its Free tier lets you try the desktop app for nothing. Claude Code is Anthropic's agent, runs Claude models only and reads `CLAUDE.md` for instructions. Neither is a clear winner for every repo. Our [Codex vs Claude Code comparison](/blog/codex-cli-vs-claude-code/) has the dated table on limits, sandboxing and computer use, and the [openai/codex repo](https://github.com/openai/codex) is the place to read the CLI's own docs. Prefer an editor? See [Codex vs Cursor](/blog/codex-vs-cursor/). diff --git a/docs/blog/agent-tools-today-2026-10-07/index.html b/docs/blog/agent-tools-today-2026-10-07/index.html index 223546e37..0e2547c2c 100644 --- a/docs/blog/agent-tools-today-2026-10-07/index.html +++ b/docs/blog/agent-tools-today-2026-10-07/index.html @@ -240,7 +240,7 @@

How this list is built