diff --git a/blog/src/_data/media.json b/blog/src/_data/media.json index 1ed15a3a19..0e3b531aec 100644 --- a/blog/src/_data/media.json +++ b/blog/src/_data/media.json @@ -5224,5 +5224,50 @@ "status": "ready" }, "inline": {} + }, + "agent-tools-today-2026-10-23": { + "title": "Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell", + "category": "news", + "hero": { + "file": "assets/media/agent-tools-today/hero.png", + "alt": "Hand drawn sketch: a front page with five numbered lines, a clock, and a yellow character reading it out", + "prompt": "Series hero for the Agent Tools Today daily brief, drawn in code (media-src/topic-tiles.html?t=news). Reused by every brief.", + "status": "ready" + }, + "inline": {}, + "youtube": [] + }, + "claude-duet-app": { + "title": "Claude Duet App: iPhone, Android and Web, and How to Get It", + "category": "guides", + "hero": { + "file": "assets/media/claude-duet-app/hero.png", + "alt": "Title card on white. A pill reading App, the title Claude Duet app, the line iPhone, Android and web and a Checked 23 Oct 2026 line. A phone outline, a second phone outline and a browser window light up in turn, joined by a dotted line to a small card reading Claude Duet.", + "prompt": "Title card: the hero scene in assets/media/claude-duet-app/motion.js, shot at 1600 by 900.", + "status": "ready" + }, + "inline": {} + }, + "ai-agent-office-github": { + "title": "Open Source AI Agent Office Apps on GitHub (Oct 2026)", + "category": "comparisons", + "hero": { + "file": "assets/media/ai-agent-office-github/hero.png", + "alt": "Title card on white. A pill reading GitHub, the title AI agent office apps, the line Open source, on GitHub and a Checked 23 Oct 2026 line. An office floor plan seen from above, with desks labelled Research, Write and Review, sends small cards to a board in turn.", + "prompt": "Title card: the hero scene in assets/media/ai-agent-office-github/motion.js, shot at 1600 by 900.", + "status": "ready" + }, + "inline": {} + }, + "black-october-php2shell": { + "title": "Black October: What PHP2Shell Is and How to Patch (Oct 2026)", + "category": "news", + "hero": { + "file": "assets/media/black-october-php2shell/hero.png", + "alt": "Title card on white. A pill reading Security, the title Black October, the line What PHP2Shell is and how to patch and a Checked 23 Oct 2026 line. A white server rack with four slots labelled 8.5.12, 8.4.27, 8.3.36 and 8.2.35 lights a green tick beside each slot in turn.", + "prompt": "Title card: the hero scene in assets/media/black-october-php2shell/motion.js, shot at 1600 by 900.", + "status": "ready" + }, + "inline": {} } } diff --git a/blog/src/assets/media/agent-tools-today-2026-10-23/motion.css b/blog/src/assets/media/agent-tools-today-2026-10-23/motion.css new file mode 100644 index 0000000000..7d3ae3b8e5 --- /dev/null +++ b/blog/src/assets/media/agent-tools-today-2026-10-23/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scene for "Agent tools today, 23 Oct 2026". Pairs with motion.js. */ +.prose figure.mg { margin: 42px 0; } +.prose figure.mg img, .mg .mg-svg { display: block; width: 100%; height: auto; border: 1px solid #1A1320; border-radius: 18px; background: #fff; } +.mg figcaption { font: 500 13px/1.5 "Space Grotesk", system-ui, sans-serif; color: #6B5878; padding-top: 10px; text-align: center; } +.mg .mg-svg { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } +.mg .mg-svg text { -webkit-user-select: none; user-select: none; pointer-events: none; } +.mg .mg-hit { cursor: pointer; outline: none; } +.mg [role="slider"] { cursor: grab; touch-action: none; } +.mg .mg-ring { stroke-opacity: 0; transition: stroke-opacity 0.15s; } +.mg .mg-hit:focus-visible .mg-ring { stroke-opacity: 1; } +.mg [role="slider"]:focus-visible circle:nth-child(2) { stroke-width: 5; } +@media (hover: hover) { .mg .mg-hit:hover .mg-ring { stroke-opacity: 0.5; } } +@media (prefers-reduced-motion: reduce) { .mg .mg-ring { transition: none; } } diff --git a/blog/src/assets/media/agent-tools-today-2026-10-23/motion.js b/blog/src/assets/media/agent-tools-today-2026-10-23/motion.js new file mode 100644 index 0000000000..7f4e2ddaa5 --- /dev/null +++ b/blog/src/assets/media/agent-tools-today-2026-10-23/motion.js @@ -0,0 +1,176 @@ +/* Bright coded scene for "Agent tools today, 23 Oct 2026". Vanilla JS, no library. +
holds a still image; this script swaps in a live SVG. + Plays only in view, honours reduced motion, "?mgstill" freezes each scene on its still frame. */ +(() => { + const NS = "http://www.w3.org/2000/svg"; + const INK = "#1A1320", YEL = "#FFCA54", BLUE = "#6C8EF5", LIL = "#B69CFF", MINT = "#7FD8AE", SKY = "#9AD8FF", SOFT = "#F1ECF9", MUTE = "#7A6A88"; + const SANS = '"Space Grotesk", system-ui, sans-serif', MONO = '"JetBrains Mono", ui-monospace, monospace'; + const el = (t, a = {}, p) => { const n = document.createElementNS(NS, t); for (const k in a) n.setAttribute(k, a[k]); if (p) p.appendChild(n); return n; }; + const txt = (p, x, y, s, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); n.textContent = s; return n; }; + const cl = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v)); + const seg = (t, a, b) => cl((t - a) / (b - a)); + const oc = (x) => 1 - Math.pow(1 - x, 3); + const ob = (x) => { const c = 1.70158; return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); }; + const io = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); + const tf = (n, x, y, s = 1, r = 0) => n.setAttribute("transform", `translate(${x} ${y}) rotate(${r}) scale(${s})`); + const button = (g, label, fn) => { + g.setAttribute("role", "button"); g.setAttribute("tabindex", "0"); g.setAttribute("aria-label", label); g.classList.add("mg-hit"); + g.addEventListener("click", fn); + g.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fn(); } }); + }; + + const SC = {}; + + /* ---- an agent in a sandbox sends three requests to a policy gate; two pass, one waits for review ---- */ + SC.today = { + w: 1600, h: 1200, dur: 10, still: 9, + build(svg, api) { + const A = (o) => Object.assign({ stroke: INK, "stroke-width": 4, "stroke-linejoin": "round", "stroke-linecap": "round" }, o); + const lp = (a, b, p) => a + (b - a) * p; + const hx = (c) => [1, 3, 5].map((i) => parseInt(c.slice(i, i + 2), 16)); + const mix = (a, b, p) => { const x = hx(a), y = hx(b); return "#" + x.map((v, i) => Math.round(lp(v, y[i], p)).toString(16).padStart(2, "0")).join(""); }; + /* geometry */ + const AX = 270, AY = 610, GX = 910, GW = 60, WX = 720, PX = 1240, TX = 1470, DH = 100; + const LANE = [450, 610, 770], NAME = ["File", "Package site", "New host"]; + /* beats, in seconds: each request leaves the agent, reaches the gate, and (for the first two) passes */ + const OUT = [1.0, 2.0, 3.0], T_HOLD = 6; + + const inner = el("g", {}, svg); + + /* the sandbox and the agent */ + const box = el("g", {}, inner); + el("rect", A({ x: 70, y: 250, width: 400, height: 720, rx: 32, fill: SOFT }), box); + txt(box, AX, 334, "Sandbox", { "text-anchor": "middle", "font-size": 42, "font-weight": 700 }); + el("rect", A({ x: AX - 80, y: AY - 80, width: 160, height: 160, rx: 22, fill: BLUE }), box); + txt(box, AX, AY + 12, "Agent", { "text-anchor": "middle", "font-size": 34, "font-weight": 700 }); + + /* the Policy gate: lilac posts with a door in each lane */ + const gate = el("g", {}, inner); + txt(gate, GX, 336, "Policy", { "text-anchor": "middle", "font-size": 42, "font-weight": 700 }); + el("rect", A({ x: GX - GW / 2, y: 360, width: GW, height: 500, rx: 14, fill: LIL }), gate); + const doors = LANE.map((y) => { + el("rect", { x: GX - GW / 2 + 2, y: y - DH / 2, width: GW - 4, height: DH, fill: "#fff" }, gate); + return el("rect", A({ x: GX - GW / 2, y: y - DH / 2, width: GW, height: DH, rx: 8, fill: SOFT, "stroke-width": 3 }), gate); + }); + + /* the three request cards, each with a tick; a key for Package site; a label for New host */ + const cards = NAME.map((s) => { + const g = el("g", {}, inner); + el("rect", A({ x: -140, y: -42, width: 280, height: 84, rx: 20, fill: SKY }), g); + txt(g, 0, 11, s, { "text-anchor": "middle", "font-size": 31, "font-weight": 700 }); + return g; + }); + const ticks = LANE.map((y) => { + const g = el("g", {}, inner); + el("circle", A({ cx: 0, cy: 0, r: 34, fill: MINT }), g); + const p = el("path", { d: "M-15 1 L-4 13 L16 -12", fill: "none", stroke: INK, "stroke-width": 7, "stroke-linecap": "round", "stroke-linejoin": "round", pathLength: 1, "stroke-dasharray": "1 1" }, g); + return { g, p, y }; + }); + const key = el("g", {}, inner); + el("circle", A({ cx: 0, cy: 0, r: 30, fill: LIL }), key); + el("circle", A({ cx: -9, cy: 0, r: 9, fill: "#fff", "stroke-width": 3.5 }), key); + el("path", A({ d: "M0 0 H17 M10 0 V8 M17 0 V9", fill: "none", "stroke-width": 3.5 }), key); + const wait = el("g", {}, inner); + const waitBg = el("rect", A({ x: -155, y: -28, width: 310, height: 56, rx: 28, fill: YEL, "stroke-width": 3 }), wait); + txt(wait, 0, 10, "Waiting for review", { "text-anchor": "middle", "font-size": 28, "font-weight": 600 }); + + /* the Approve button and its hint */ + const hint = txt(inner, 1262, 121, "Tap Approve", { "text-anchor": "end", "font-size": 30, "font-weight": 500, fill: MUTE }); + const btn = el("g", {}, inner), face = el("g", {}, btn); + el("rect", { x: -10, y: -10, width: 250, height: 104, rx: 52, fill: "none", stroke: INK, "stroke-width": 3, "stroke-dasharray": "10 8", class: "mg-ring" }, face); + el("rect", A({ width: 230, height: 84, rx: 42, fill: BLUE }), face); + txt(face, 115, 55, "Approve", { "text-anchor": "middle", "font-size": 36, "font-weight": 700 }); + + /* press Approve, or Enter or Space on it: the label turns mint and New host goes through */ + let approved = false, at = -99; + button(btn, "Approve: let the New host request through the policy gate", () => { + if (approved) return; + approved = true; + if (calm) { at = -99; api.redraw(); return; } + at = api.now(); + api.seek(T_HOLD); + }); + + return (t, now = 0) => { + if (approved && !calm && t < 4) approved = false; + const pa = approved ? (calm ? 9 : now - at) : -1; + inner.setAttribute("opacity", (oc(seg(t, 0, 0.3)) * (1 - io(seg(t, 9.4, 9.9)))).toFixed(3)); + box.setAttribute("opacity", io(seg(t, 0.1, 0.6)).toFixed(3)); + gate.setAttribute("opacity", io(seg(t, 0.3, 0.8)).toFixed(3)); + + cards.forEach((g, k) => { + const t0 = OUT[k], y = LANE[k]; + const go = oc(seg(t, t0, t0 + 1)); + /* pass: 0 waiting at the gate, 1 through it */ + let open, pass, tk; + if (k < 2) { + open = seg(t, t0 + 1.4, t0 + 1.7) * (1 - seg(t, t0 + 2.4, t0 + 2.7)); + pass = io(seg(t, t0 + 1.6, t0 + 2.4)); + tk = seg(t, t0 + 2.4, t0 + 2.9); + } else { + open = pa < 0 ? 0 : seg(pa, 0.3, 0.6) * (1 - seg(pa, 1.3, 1.6)); + pass = pa < 0 ? 0 : io(seg(pa, 0.5, 1.3)); + tk = pa < 0 ? 0 : seg(pa, 1.3, 1.8); + } + const x = pass > 0 ? lp(WX, PX, pass) : lp(AX, WX, go), cy = pass > 0 ? y : lp(AY, y, oc(seg(t, t0, t0 + 1))); + const s = lp(0.3, 1, oc(seg(t, t0, t0 + 0.6))); + tf(g, x.toFixed(1), cy.toFixed(1), s.toFixed(3)); + g.setAttribute("opacity", seg(t, t0, t0 + 0.15).toFixed(3)); + doors[k].setAttribute("height", (DH * (1 - open)).toFixed(1)); + doors[k].setAttribute("opacity", open > 0.97 ? 0 : 1); + const T = ticks[k]; + tf(T.g, TX, T.y, ob(seg(tk, 0, 0.5)).toFixed(3)); + T.g.setAttribute("opacity", tk > 0 ? 1 : 0); + T.p.setAttribute("stroke-dashoffset", (1 - seg(tk, 0.3, 1)).toFixed(3)); + }); + + /* the key attaches to Package site once it is through */ + const kp = seg(t, 4.7, 5.2); + tf(key, PX + 140, LANE[1] - 42, ob(kp).toFixed(3), -20 * (1 - kp)); + key.setAttribute("opacity", kp > 0 ? 1 : 0); + + /* New host waits at the gate with a yellow label; Approve turns it mint */ + const wp = seg(t, 4.6, 5.1); + tf(wait, WX - 15, LANE[2] + 86, ob(wp).toFixed(3)); + wait.setAttribute("opacity", wp > 0 ? 1 : 0); + waitBg.setAttribute("fill", mix(YEL, MINT, pa < 0 ? 0 : seg(pa, 0, 0.3))); + + const bo = io(seg(t, 0.8, 1.2)); + btn.setAttribute("opacity", bo); hint.setAttribute("opacity", bo); + tf(btn, 1290, 66); + face.setAttribute("transform", `translate(115 42) scale(${(1 - 0.06 * Math.sin(Math.PI * seg(now - at, 0, 0.2))).toFixed(3)}) translate(-115 -42)`); + }; + }, + }; + + /* ---- 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(); }, redraw: () => frame() }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/agent-tools-today-2026-10-23/today.png b/blog/src/assets/media/agent-tools-today-2026-10-23/today.png new file mode 100644 index 0000000000..23443d42bb Binary files /dev/null and b/blog/src/assets/media/agent-tools-today-2026-10-23/today.png differ diff --git a/blog/src/assets/media/ai-agent-office-github/bins.png b/blog/src/assets/media/ai-agent-office-github/bins.png new file mode 100644 index 0000000000..a85d0086a8 Binary files /dev/null and b/blog/src/assets/media/ai-agent-office-github/bins.png differ diff --git a/blog/src/assets/media/ai-agent-office-github/corkboard.png b/blog/src/assets/media/ai-agent-office-github/corkboard.png new file mode 100644 index 0000000000..ab71c6d4e5 Binary files /dev/null and b/blog/src/assets/media/ai-agent-office-github/corkboard.png differ diff --git a/blog/src/assets/media/ai-agent-office-github/hero.png b/blog/src/assets/media/ai-agent-office-github/hero.png new file mode 100644 index 0000000000..fc24854a0c Binary files /dev/null and b/blog/src/assets/media/ai-agent-office-github/hero.png differ diff --git a/blog/src/assets/media/ai-agent-office-github/lamps.png b/blog/src/assets/media/ai-agent-office-github/lamps.png new file mode 100644 index 0000000000..523a7a55a0 Binary files /dev/null and b/blog/src/assets/media/ai-agent-office-github/lamps.png differ diff --git a/blog/src/assets/media/ai-agent-office-github/motion.css b/blog/src/assets/media/ai-agent-office-github/motion.css new file mode 100644 index 0000000000..41bd58387e --- /dev/null +++ b/blog/src/assets/media/ai-agent-office-github/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Open Source AI Agent Office Apps on GitHub". 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/ai-agent-office-github/motion.js b/blog/src/assets/media/ai-agent-office-github/motion.js new file mode 100644 index 0000000000..f7bf5bdf7d --- /dev/null +++ b/blog/src/assets/media/ai-agent-office-github/motion.js @@ -0,0 +1,335 @@ +/* Bright coded scenes for "Open Source AI Agent Office Apps on GitHub". 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", CAP = "#6B5878", GREY = "#D9D0E4"; + 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 ring = (g, a) => el("rect", Object.assign({ fill: "none", stroke: INK, "stroke-width": 2, class: "mg-ring" }, a), g); + const pill = (svg, x, y, w, label, col, fn) => { + const g = el("g", {}, svg); + el("rect", { x, y, width: w, height: 48, rx: 24, fill: col, stroke: INK, "stroke-width": 3 }, g); + ring(g, { x: x - 6, y: y - 6, width: w + 12, height: 60, rx: 30 }); + txt(g, x + w / 2, y + 31, label, { "text-anchor": "middle", "font-size": 21, "font-weight": 700 }); + button(g, label, fn); return g; + }; + const hint = (svg, x, y, s) => txt(svg, x, y, s, { "text-anchor": "end", "font-size": 16, fill: MUTE, "font-weight": 500 }); + const envelope = (p) => { + const g = el("g", {}, p); + el("rect", { x: -17, y: -12, width: 34, height: 24, rx: 3, fill: "#fff", stroke: INK, "stroke-width": 2 }, g); + el("path", { d: "M-17 -11 L0 2 L17 -11", stroke: INK, "stroke-width": 2, fill: "none", "stroke-linejoin": "round" }, g); + return g; + }; + + const SC = {}; + + /* ---- 1. roles, a shared board and messages ---- */ + SC.corkboard = { + dur: 12, still: 10.6, + build(svg, api) { + const TR = [["Researcher", SKY], ["Writer", LIL], ["Reviewer", MINT]], TY = [96, 186, 276]; + [52, 230].forEach((x) => el("rect", { x, y: 120, width: 8, height: 200, fill: "#fff", stroke: INK, "stroke-width": 2 }, svg)); + const trays = TR.map(([name, col], i) => { + const g = el("g", {}, svg), y = TY[i]; + el("rect", { x: 64, y: y - 12, width: 150, height: 30, rx: 3, fill: "#fff", stroke: INK, "stroke-width": 2 }, g); + el("rect", { x: 40, y, width: 210, height: 62, rx: 10, fill: col, stroke: INK, "stroke-width": 3 }, g); + txt(g, 145, y + 40, name, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + return g; + }); + el("rect", { x: 300, y: 60, width: 460, height: 330, rx: 18, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 316, y: 104, width: 428, height: 270, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 2 }, svg); + txt(svg, 530, 92, "Shared board", { "text-anchor": "middle", "font-size": 24, "font-weight": 700 }); + txt(svg, 425, 138, "To do", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 625, 138, "Done", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + el("circle", { cx: 670, cy: 131, r: 10, fill: MINT, stroke: INK, "stroke-width": 2 }, svg); + el("path", { d: "M665 131 l3.5 4 l6.5 -8", stroke: INK, "stroke-width": 2.5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + el("path", { d: "M530 118 V362", stroke: INK, "stroke-width": 2, "stroke-dasharray": "2 8", "stroke-linecap": "round" }, svg); + const BAR = [SKY, LIL, MINT, YEL, BLUE], cardsG = el("g", {}, svg); + const cards = BAR.map((c) => { + const g = el("g", {}, cardsG); + el("rect", { x: -85, y: -17, width: 170, height: 34, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 2 }, g); + el("circle", { cx: -69, cy: 0, r: 5, fill: c, stroke: INK, "stroke-width": 1.5 }, g); + el("rect", { x: -55, y: -7, width: 92, height: 6, rx: 3, fill: c }, g); + el("rect", { x: -55, y: 3, width: 58, height: 5, rx: 2.5, fill: GREY }, g); + const tk = el("g", {}, g); + el("circle", { cx: 65, cy: 0, r: 9, fill: MINT, stroke: INK, "stroke-width": 1.5 }, tk); + el("path", { d: "M60.5 0 l3 3.5 l6 -7", stroke: INK, "stroke-width": 2, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, tk); + return { g, tk }; + }); + el("rect", { x: 40, y: 436, width: 720, height: 60, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + for (let h = 0; h < 8; h++) { + el("rect", { x: 47 + h * 90, y: 443, width: 76, height: 46, rx: 5, fill: SOFT }, svg); + if (h) el("path", { d: `M${40 + h * 90} 436 V496`, stroke: INK, "stroke-width": 2 }, svg); + } + txt(svg, 40, 534, "Messages", { "font-size": 20, "font-weight": 700 }); + const RX = 276; + const route = (i, hx) => { + const y = TY[i] + 31, d = hx >= RX ? 1 : -1; + const tail = Math.abs(hx - RX) < 28 ? `V430 L${hx} 466` : `V407 Q${RX} 420 ${RX + 13 * d} 420 H${hx - 13 * d} Q${hx} 420 ${hx} 433 V466`; + return el("path", { d: `M250 ${y} H${RX - 13} Q${RX} ${y} ${RX} ${y + 13} ${tail}`, fill: "none", stroke: "none" }, svg); + }; + const M = [[0, 1, 1], [1, 5, 2], [2, 3, 0], [0, 6, 2], [1, 0, 0], [2, 4, 1], [0, 2, 1]].map(([a, h, b], k) => { + const hx = 85 + h * 90, pin = route(a, hx), pout = route(b, hx); + return { t0: 0.4 + k * 1.4, a, b, hx, pin, pout, Lin: pin.getTotalLength(), Lout: pout.getTotalLength(), n: envelope(svg) }; + }); + const T = [1.6, 3.9, 6.2]; + let clicks = [], lastT = 0; + hint(svg, 584, 547, "tap Next card"); + pill(svg, 600, 516, 160, "Next card", YEL, () => { + const done = T.filter((x) => lastT >= x).length + clicks.length; + if (done >= 5) { clicks = []; api.seek(0.6); } else { clicks.push(api.now()); api.poke(); } + }); + return (t, now) => { + if (t < lastT - 0.5) clicks = []; + lastT = t; + const fade = 1 - seg(t, 11.3, 11.9); + let p = 0; + T.forEach((x) => (p += io(seg(t, x, x + 1.1)))); + clicks.forEach((c) => (p += io(seg(now, c, c + 0.9)))); + p = Math.min(p, 5); + cardsG.setAttribute("opacity", seg(t, 0, 0.5) * fade); + cards.forEach((c, i) => { + const k = cl(p - i); let s = 0; + for (let j = 0; j < i; j++) s += io(seg(cl(p - j), 0.55, 1)); + let x = 425, y = 167 + 42 * (i - s), sc = 1, r = 0; + if (k > 0) { const a = Math.sin(Math.PI * k); x = 425 + 210 * io(k); y = 167 + 42 * i * io(k) - 24 * a; sc = 1 + 0.08 * a; r = 5 * a; } + tf(c.g, x, y, sc, r); c.tk.setAttribute("opacity", seg(k, 0.85, 1)); + }); + const bump = [0, 0, 0]; + M.forEach((m) => { + const a = t - m.t0; let x = m.hx, y = 466, op = 1; + if (a < 0 || a > 5.6) op = 0; + else if (a < 0.7) { const pt = m.pin.getPointAtLength(m.Lin * io(a / 0.7)); x = pt.x; y = pt.y; op = seg(a, 0, 0.12); } + else if (a > 4.9) { const pt = m.pout.getPointAtLength(m.Lout * (1 - io((a - 4.9) / 0.7))); x = pt.x; y = pt.y; op = 1 - seg(a, 5.45, 5.6); } + if (a >= 0 && a < 0.3) bump[m.a] = Math.max(bump[m.a], Math.sin((Math.PI * a) / 0.3)); + if (a > 5.3 && a <= 5.6) bump[m.b] = Math.max(bump[m.b], Math.sin((Math.PI * (a - 5.3)) / 0.3)); + tf(m.n, x, y); m.n.setAttribute("opacity", op * fade); + }); + trays.forEach((g, i) => g.setAttribute("transform", `translate(0 ${-5 * bump[i]})`)); + }; + }, + }; + + /* ---- 2. eight boxes land on a shelf, each with its licence tag ---- */ + SC.shelf = { + dur: 12, still: 10, + build(svg, api) { + const B = [["ChatDev"], ["Munder", "Difflin"], ["OpenHands"], ["CrewAI"], ["AG2"], ["Pixel", "Agents"], ["AgentOffice"], ["Claude", "Squad"]]; + const LIC = ["Apache 2.0", "MIT", "MIT", "MIT", "Apache 2.0", "MIT", "MIT", "AGPL 3.0"]; + const H = [190, 208, 180, 198, 172, 204, 186, 196], COL = [YEL, SKY, LIL, MINT, YEL, SKY, LIL, MINT]; + const W = B.map((b) => Math.max(70, Math.max(...b.map((s) => s.length)) * 9.6 + 18)), GAP = 7, SY = 440; + let x0 = (800 - W.reduce((a, b) => a + b, 0) - GAP * 7) / 2; + hint(svg, 756, 46, "tap a box"); + [130, 670].forEach((x) => el("path", { d: `M${x} ${SY + 18} V${SY + 64} L${x - 34} ${SY + 18} Z`, fill: SOFT, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, svg)); + el("rect", { x: 18, y: SY, width: 764, height: 18, rx: 5, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const call = el("g", { opacity: 0 }, svg); + el("rect", { x: 220, y: 62, width: 360, height: 118, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, call); + const cName = txt(call, 400, 104, "", { "text-anchor": "middle", "font-family": MONO, "font-size": 24, "font-weight": 700 }); + const cLic = txt(call, 400, 160, "", { "text-anchor": "middle", "font-size": 46, "font-weight": 700 }); + let sel = -1, prev = -1, selAt = -99, lastT = 0; + const boxes = B.map((lines, i) => { + const w = W[i], h = H[i], x = x0 + w / 2; x0 += w + GAP; + const g = el("g", {}, svg); + el("rect", { x: -w / 2, y: -h, width: w, height: h, rx: 8, fill: COL[i], stroke: INK, "stroke-width": 3 }, g); + el("path", { d: `M${-w / 2 + 10} ${-h + 14} H${w / 2 - 10}`, stroke: INK, "stroke-width": 2, "stroke-dasharray": "2 6", "stroke-linecap": "round" }, g); + lines.forEach((s, j) => txt(g, 0, -h + 44 + j * 22, s, { "text-anchor": "middle", "font-family": MONO, "font-size": 16, "font-weight": 700 })); + ring(g, { x: -w / 2 - 7, y: -h - 7, width: w + 14, height: h + 14, rx: 12 }); + const tag = el("g", {}, g), tl = LIC[i].split(" "); + el("rect", { x: -30, y: -54, width: 60, height: 44, rx: 7, fill: "#fff", stroke: INK, "stroke-width": 2 }, tag); + tl.forEach((s, j) => txt(tag, 0, tl.length > 1 ? -35 + j * 17 : -26, s, { "text-anchor": "middle", "font-size": 14, "font-weight": 700 })); + button(g, `${lines.join(" ")}: ${LIC[i]}`, () => { prev = sel; sel = sel === i ? -1 : i; selAt = api.now(); if (lastT < 8.4) api.seek(8.6); else api.poke(); }); + return { g, tag, x, h, s: 0.5 + i * 0.85 }; + }); + return (t, now) => { + if (t < lastT - 0.5) { sel = -1; prev = -1; } + lastT = t; + const fade = 1 - seg(t, 11.3, 11.9), u = oc(seg(now - selAt, 0, 0.35)); + boxes.forEach((b, i) => { + const k = oc(seg(t, b.s, b.s + 0.8)), dk = ob(seg(t, b.s + 0.8, b.s + 1.3)); + const lift = i === sel ? u : i === prev ? 1 - u : 0; + tf(b.g, b.x + (900 - b.x) * (1 - k), SY - 36 * lift, 1, -4 * Math.sin(Math.PI * k)); + b.g.setAttribute("opacity", (t > b.s ? 1 : 0) * fade); + b.tag.setAttribute("transform", `translate(0 ${-(b.h + 10) * (1 - dk)})`); + b.tag.setAttribute("opacity", seg(t, b.s + 0.8, b.s + 0.95)); + }); + const show = sel >= 0 ? sel : prev, co = sel >= 0 ? u : 1 - u; + call.setAttribute("opacity", show >= 0 ? co * fade : 0); + call.setAttribute("transform", `translate(400 121) scale(${0.9 + 0.1 * co}) translate(-400 -121)`); + if (show >= 0) { cName.textContent = B[show].join(" "); cLic.textContent = LIC[show]; } + }; + }, + }; + + /* ---- 3. name cards drop into three bins ---- */ + SC.bins = { + dur: 12, still: 10, + build(svg, api) { + const BX = [40, 290, 540], BC = [SKY, LIL, MINT], BL = [["Apps", "you run"], ["Frameworks", "you code"], ["Viewers", "you watch"]]; + hint(svg, 624, 64, "tap Sort"); + pill(svg, 640, 34, 120, "Sort", YEL, () => api.seek(0)); + BX.forEach((x) => el("path", { d: `M${x} 256 H${x + 220} L${x + 206} 524 H${x + 14} Z`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg)); + const CARDS = [["ChatDev", 0], ["Munder Difflin", 0], ["OpenHands", 0], ["CrewAI", 1], ["AG2", 1], ["Pixel Agents", 2]]; + const cnt = [0, 0, 0], ROT = [-2, 1.5, -1, 2, -1.5, 1]; + const cards = CARDS.map(([s, b], j) => { + const g = el("g", {}, svg); + el("rect", { x: -90, y: -21, width: 180, height: 42, rx: 10, fill: YEL, stroke: INK, "stroke-width": 2.5 }, g); + txt(g, 0, 7, s, { "text-anchor": "middle", "font-family": MONO, "font-size": 19, "font-weight": 700 }); + const slot = cnt[b]++; + return { g, tx: BX[b] + 110, ty: 418 - slot * 52, s: 0.7 + j * 1.25, r: ROT[j] }; + }); + BX.forEach((x, i) => { + el("rect", { x: x - 6, y: 248, width: 232, height: 16, rx: 8, fill: BC[i], stroke: INK, "stroke-width": 3 }, svg); + BL[i].forEach((s, j) => txt(svg, x + 110, 478 + j * 28, s, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 })); + }); + return (t) => { + const fade = 1 - seg(t, 11.3, 11.9); + cards.forEach((c) => { + const a = t - c.s, pop = ob(seg(a, 0, 0.3)), k = seg(a, 0.45, 1.05); + let x = 400, y = 150, sc = pop, r = 0; + if (k > 0) { x = 400 + (c.tx - 400) * io(k); y = 150 + (c.ty - 150) * k * k - 8 * Math.sin(Math.PI * seg(a, 1.05, 1.3)); sc = 1; r = c.r * k; } + tf(c.g, x, y, Math.max(sc, 0.001), r); c.g.setAttribute("opacity", (a > 0 ? 1 : 0) * fade); + }); + }; + }, + }; + + /* ---- 4. two lamps dim, AG2 relights one ---- */ + SC.lamps = { + dur: 11, still: 9, + build(svg, api) { + const LX = [100, 330, 560], TOP = 400; + hint(svg, 624, 64, "tap Swap"); + pill(svg, 640, 34, 120, "Swap", YEL, () => api.seek(4.6)); + const lamps = LX.map((bx) => { + const cx = bx + 64, d = `M${cx - 30} 272 H${cx + 30} L${cx + 76} ${TOP} H${cx - 76} Z`; + return { bx, cx, cone: el("path", { d, fill: YEL, opacity: 0.35 }, svg), bcone: el("path", { d, fill: BLUE, opacity: 0 }, svg) }; + }); + [56, 728].forEach((x) => el("rect", { x, y: TOP + 18, width: 16, height: 110, rx: 4, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg)); + el("rect", { x: 30, y: TOP, width: 740, height: 22, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + lamps.forEach((L) => { + const { bx, cx } = L; + el("rect", { x: bx - 34, y: TOP - 14, width: 68, height: 14, rx: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: `M${bx} ${TOP - 12} L${bx - 18} 316 L${cx} 230`, stroke: INK, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + el("circle", { cx: bx - 18, cy: 316, r: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + L.halo = el("circle", { cx, cy: 266, r: 28, fill: YEL, opacity: 0.4 }, svg); + L.bhalo = el("circle", { cx, cy: 266, r: 28, fill: BLUE, opacity: 0 }, svg); + el("circle", { cx, cy: 266, r: 13, fill: GREY, stroke: INK, "stroke-width": 2.5 }, svg); + L.lit = el("circle", { cx, cy: 266, r: 13, fill: YEL, stroke: INK, "stroke-width": 2.5 }, svg); + L.blue = el("circle", { cx, cy: 266, r: 13, fill: BLUE, stroke: INK, "stroke-width": 2.5, opacity: 0 }, svg); + el("path", { d: `M${cx - 22} 222 Q${cx} 208 ${cx + 22} 222 L${cx + 38} 264 H${cx - 38} Z`, fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + }); + txt(svg, LX[1] + 64, 470, "MetaGPT", { "text-anchor": "middle", "font-family": MONO, "font-size": 22, "font-weight": 700 }); + txt(svg, LX[1] + 64, 496, "last commit 21 Jan 2026", { "text-anchor": "middle", "font-size": 17, "font-weight": 500 }); + txt(svg, LX[2] + 64, 470, "AutoGen", { "text-anchor": "middle", "font-family": MONO, "font-size": 22, "font-weight": 700 }); + txt(svg, LX[2] + 64, 496, "archived", { "text-anchor": "middle", "font-size": 17, "font-weight": 500 }); + const card = el("g", {}, svg); + el("rect", { x: -40, y: -19, width: 80, height: 38, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, card); + txt(card, 0, 8, "AG2", { "text-anchor": "middle", "font-family": MONO, "font-size": 22, "font-weight": 700 }); + const CX = LX[2] + 84; + return (t) => { + const back = seg(t, 10.2, 10.9); + const flick = (a, b) => { const k = seg(t, a, b); if (k <= 0) return 1; if (k >= 1) return 0; return (1 - k) * (Math.sin(t * 47) > 0.2 ? 1 : 0.35); }; + const lv = [1, Math.max(flick(1.4, 2.4), back), Math.max(flick(3.0, 4.0), back)]; + const blue = oc(seg(t, 6.1, 6.7)) * (1 - back); + lamps.forEach((L, i) => { + const b = i === 2 ? blue : 0, y = lv[i] * (1 - b); + L.lit.setAttribute("opacity", y); L.halo.setAttribute("opacity", 0.4 * y); L.cone.setAttribute("opacity", 0.35 * y); + L.blue.setAttribute("opacity", b); L.bhalo.setAttribute("opacity", 0.35 * b); L.bcone.setAttribute("opacity", 0.22 * b); + }); + const k = oc(seg(t, 4.8, 6.0)); + tf(card, CX + 340 * (1 - k), TOP - 20); card.setAttribute("opacity", (t >= 4.8 ? 1 : 0) * (1 - back)); + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 9, still: 7.8, + build(svg) { + el("rect", { x: 56, y: 62, width: 104, height: 38, rx: 19, fill: YEL, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 108, 88, "GitHub", { "text-anchor": "middle", "font-family": MONO, "font-size": 18, "font-weight": 700 }); + txt(svg, 56, 190, "AI agent", { "font-size": 70, "font-weight": 700 }); + txt(svg, 56, 268, "office apps", { "font-size": 70, "font-weight": 700 }); + el("path", { d: "M58 294 H420", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + const sub = txt(svg, 56, 348, "Open source, on ", { "font-size": 26, "font-weight": 500, fill: MUTE }); + el("tspan", { "font-family": MONO, "font-weight": 700 }, sub).textContent = "GitHub"; + txt(svg, 56, 470, "Checked 23 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const R = { x: 528, y: 56, w: 392, h: 428 }; + el("rect", { x: R.x, y: R.y, width: R.w, height: R.h, rx: 10, fill: "#fff" }, svg); + for (let x = R.x + 28; x < R.x + R.w; x += 28) el("path", { d: `M${x} ${R.y} V${R.y + R.h}`, stroke: SOFT, "stroke-width": 1.5 }, svg); + for (let y = R.y + 28; y < R.y + R.h; y += 28) el("path", { d: `M${R.x} ${y} H${R.x + R.w}`, stroke: SOFT, "stroke-width": 1.5 }, svg); + el("rect", { x: R.x, y: R.y, width: R.w, height: R.h, rx: 10, fill: "none", stroke: INK, "stroke-width": 4 }, svg); + el("rect", { x: 737, y: R.y + R.h - 4, width: 48, height: 8, fill: "#fff" }, svg); + el("path", { d: `M737 ${R.y + R.h} V${R.y + R.h - 48}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("path", { d: `M737 ${R.y + R.h - 48} A48 48 0 0 1 785 ${R.y + R.h}`, stroke: MUTE, "stroke-width": 1.5, "stroke-dasharray": "3 5", fill: "none" }, svg); + const D = [["Research", SKY], ["Write", LIL], ["Review", MINT]], DY = [175, 283, 391]; + D.forEach(([s, c], i) => { + el("rect", { x: 548, y: DY[i] - 16, width: 26, height: 32, rx: 8, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, svg); + el("rect", { x: 586, y: DY[i] - 32, width: 150, height: 64, rx: 8, fill: c, stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 646, DY[i] + 8, s, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + }); + el("rect", { x: 790, y: 96, width: 110, height: 350, rx: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 800, y: 132, width: 90, height: 304, rx: 6, fill: "#fff", stroke: INK, "stroke-width": 2 }, svg); + txt(svg, 845, 122, "Board", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const cards = Array.from({ length: 6 }, (_, m) => { + const g = el("g", {}, svg); + el("rect", { x: -17, y: -12, width: 34, height: 24, rx: 4, fill: YEL, stroke: INK, "stroke-width": 2 }, g); + el("rect", { x: -10, y: -5, width: 20, height: 4, rx: 2, fill: "#fff" }, g); + el("rect", { x: -10, y: 2, width: 13, height: 3, rx: 1.5, fill: "#fff" }, g); + return { g, row: m % 3, col: 1 - Math.floor(m / 3), s: 0.5 + m * 1.1 }; + }); + return (t) => { + const fade = 1 - seg(t, 8.3, 8.9); + cards.forEach((c) => { + const a = t - c.s, pop = ob(seg(a, 0, 0.25)), k = io(seg(a, 0.3, 1.0)), x0 = 716, x1 = 826 + 40 * c.col; + tf(c.g, x0 + (x1 - x0) * k, DY[c.row] - 22 * Math.sin(Math.PI * k), Math.max(0.001, pop), 0); + c.g.setAttribute("opacity", (a > 0 ? 1 : 0) * fade); + }); + }; + }, + }; + + /* ---- 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/ai-agent-office-github/shelf.png b/blog/src/assets/media/ai-agent-office-github/shelf.png new file mode 100644 index 0000000000..2881236988 Binary files /dev/null and b/blog/src/assets/media/ai-agent-office-github/shelf.png differ diff --git a/blog/src/assets/media/black-october-php2shell/calendar.png b/blog/src/assets/media/black-october-php2shell/calendar.png new file mode 100644 index 0000000000..008d5f1da0 Binary files /dev/null and b/blog/src/assets/media/black-october-php2shell/calendar.png differ diff --git a/blog/src/assets/media/black-october-php2shell/clipboard.png b/blog/src/assets/media/black-october-php2shell/clipboard.png new file mode 100644 index 0000000000..212bc2f28f Binary files /dev/null and b/blog/src/assets/media/black-october-php2shell/clipboard.png differ diff --git a/blog/src/assets/media/black-october-php2shell/conveyor.png b/blog/src/assets/media/black-october-php2shell/conveyor.png new file mode 100644 index 0000000000..b663c24dc8 Binary files /dev/null and b/blog/src/assets/media/black-october-php2shell/conveyor.png differ diff --git a/blog/src/assets/media/black-october-php2shell/hero.png b/blog/src/assets/media/black-october-php2shell/hero.png new file mode 100644 index 0000000000..0454b63516 Binary files /dev/null and b/blog/src/assets/media/black-october-php2shell/hero.png differ diff --git a/blog/src/assets/media/black-october-php2shell/motion.css b/blog/src/assets/media/black-october-php2shell/motion.css new file mode 100644 index 0000000000..9d9dd697d0 --- /dev/null +++ b/blog/src/assets/media/black-october-php2shell/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Black October: What PHP2Shell Is and How to Patch". 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/black-october-php2shell/motion.js b/blog/src/assets/media/black-october-php2shell/motion.js new file mode 100644 index 0000000000..d484424c65 --- /dev/null +++ b/blog/src/assets/media/black-october-php2shell/motion.js @@ -0,0 +1,314 @@ +/* Bright coded scenes for "Black October: What PHP2Shell Is and How to Patch". 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", CAP = "#6B5878", GREY = "#DCD6E3"; + 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; }; + /* text with mono runs: parts is a list of [string, isMono] */ + const rich = (p, x, y, parts, a = {}) => { const n = el("text", Object.assign({ x, y, "font-family": SANS, "font-size": 24, "font-weight": 600, fill: INK }, a), p); parts.forEach(([s, m]) => { const t = el("tspan", m ? { "font-family": MONO, "font-weight": 700 } : {}, n); t.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(); } }); + }; + /* a pill button with a focus ring, and the muted hint beside it */ + const pill = (svg, x, y, w, label, col, fn) => { + const g = el("g", {}, svg); + el("rect", { x: x - 6, y: y - 6, width: w + 12, height: 56, rx: 28, fill: "none", stroke: INK, "stroke-width": 2.5, class: "mg-ring" }, g); + el("rect", { x, y, width: w, height: 44, rx: 22, fill: col, stroke: INK, "stroke-width": 3 }, g); + txt(g, x + w / 2, y + 29, label, { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + const hint = (svg, x, y, s, anchor = "end") => txt(svg, x, y, s, { "text-anchor": anchor, "font-size": 16, "font-weight": 500, fill: MUTE }); + /* a mint tick in a round badge, drawn around 0,0 */ + const badge = (p, r = 18) => { + const g = el("g", {}, p); + el("circle", { r, fill: MINT, stroke: INK, "stroke-width": 2.5 }, g); + el("path", { d: `M${-r * 0.42} ${r * 0.02} L${-r * 0.1} ${r * 0.34} L${r * 0.44} ${-r * 0.32}`, stroke: INK, "stroke-width": 3, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); + return g; + }; + + const SC = {}; + + /* ---- 1. the October 2026 calendar: the 21st gets ringed and the page turns into Black October ---- */ + SC.calendar = { + dur: 10, still: 9.6, + build(svg, api) { + const X = 50, Y = 64, W = 420, CW = 56, CH = 60, GX = X + (W - 7 * CW) / 2, GY = Y + 104; + el("circle", { cx: X + W / 2, cy: 24, r: 6, fill: INK }, svg); + el("path", { d: `M${X + W / 2} 24 L${X + 80} ${Y} M${X + W / 2} 24 L${X + W - 80} ${Y}`, stroke: INK, "stroke-width": 2, fill: "none" }, svg); + el("rect", { x: X + 10, y: Y + 10, width: W, height: 480, rx: 20, fill: SOFT }, svg); + el("rect", { x: X, y: Y, width: W, height: 480, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const head = el("path", { d: `M${X} ${Y + 84} V${Y + 20} Q${X} ${Y} ${X + 20} ${Y} H${X + W - 20} Q${X + W} ${Y} ${X + W} ${Y + 20} V${Y + 84} Z`, fill: LIL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + [X + 80, X + W - 80].forEach((x) => el("circle", { cx: x, cy: Y, r: 7, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg)); + const hg = el("g", {}, svg); + const h1 = txt(hg, 0, 13, "October 2026", { "text-anchor": "middle", "font-size": 36, "font-weight": 700 }); + const h2 = txt(hg, 0, 13, "Black October", { "text-anchor": "middle", "font-size": 36, "font-weight": 700 }); + for (let r = 1; r < 5; r++) el("path", { d: `M${GX} ${GY + r * CH} H${GX + 7 * CW}`, stroke: SOFT, "stroke-width": 2 }, svg); + let c21 = null; + for (let d = 1; d <= 31; d++) { + const i = d + 2, cx = GX + (i % 7) * CW + CW / 2, cy = GY + Math.floor(i / 7) * CH + CH / 2; + txt(svg, cx, cy + 8, String(d), { "text-anchor": "middle", "font-size": 22, "font-weight": d === 21 ? 700 : 500, fill: d === 21 ? INK : CAP }); + if (d === 21) c21 = [cx, cy]; + } + const ring = el("circle", { cx: c21[0], cy: c21[1], r: 25, fill: "none", stroke: YEL, "stroke-width": 7, pathLength: 1, "stroke-dasharray": 1, "stroke-linecap": "round", transform: `rotate(-90 ${c21[0]} ${c21[1]})` }, svg); + const lx = GX + 3 * CW; + const lead = el("path", { d: `M${c21[0] + 19} ${c21[1] + 18} Q${lx} ${c21[1] + 24} ${lx} ${c21[1] + 44} V${GY + 5 * CH + 18}`, stroke: INK, "stroke-width": 2.5, fill: "none", pathLength: 1, "stroke-dasharray": 1, "stroke-linecap": "round" }, svg); + const lab = el("g", {}, svg); + el("rect", { x: -90, y: 0, width: 180, height: 40, rx: 20, fill: YEL, stroke: INK, "stroke-width": 2.5 }, lab); + txt(lab, 0, 27, "Fixes released", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + const tag = el("g", {}, svg); + el("path", { d: "M10 52 L48 14 H250 V196 H48 L10 158 Z", fill: YEL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, tag); + el("circle", { cx: 42, cy: 105, r: 9, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, tag); + txt(tag, 72, 74, "PHP2Shell", { "font-family": MONO, "font-size": 28, "font-weight": 700 }); + el("path", { d: "M72 94 H230", stroke: INK, "stroke-width": 2, "stroke-dasharray": "2 7", "stroke-linecap": "round" }, tag); + txt(tag, 72, 134, "2 CVEs", { "font-size": 26, "font-weight": 700 }); + txt(tag, 72, 172, "CVSS 9.2", { "font-size": 26, "font-weight": 700 }); + hint(svg, 604, 559, "tap Replay"); + pill(svg, 622, 530, 130, "Replay", SKY, () => api.seek(0)); + return (t) => { + ring.setAttribute("stroke-dashoffset", 1 - io(seg(t, 1.2, 2.2))); + lead.setAttribute("stroke-dashoffset", 1 - seg(t, 2.1, 2.6)); + const p = ob(seg(t, 2.5, 2.9)); tf(lab, lx, GY + 5 * CH + 18, p); lab.setAttribute("opacity", seg(t, 2.5, 2.6)); + const s = oc(seg(t, 3.4, 4.2)); tf(tag, 508 + 300 * (1 - s), 196, 1, 4 * (1 - s) - 2); tag.setAttribute("opacity", seg(t, 3.4, 3.7)); + const f = seg(t, 5.6, 6.4), first = f < 0.5, sy = first ? 1 - io(f * 2) : io(f * 2 - 1); + hg.setAttribute("transform", `translate(${X + W / 2} ${Y + 42}) scale(1 ${Math.max(sy, 0.001)})`); + h1.setAttribute("opacity", first ? 1 : 0); h2.setAttribute("opacity", first ? 0 : 1); + head.setAttribute("fill", first ? LIL : BLUE); + }; + }, + }; + + /* ---- 2. a conveyor: Request 1 hits the memory limit, leaves a stale note, Request 2 takes it ---- */ + SC.conveyor = { + dur: 11, still: 10.6, + build(svg, api) { + const BY = 430, NX = 480, S = 1.25; + el("path", { d: `M90 ${BY + 34} V505 M710 ${BY + 34} V505 M70 505 H110 M690 505 H730`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("rect", { x: 30, y: BY, width: 740, height: 34, rx: 17, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const rollers = Array.from({ length: 11 }, (_, i) => { const g = el("g", {}, svg); el("circle", { r: 10, fill: SOFT, stroke: INK, "stroke-width": 2 }, g); el("path", { d: "M-6 0 H6", stroke: INK, "stroke-width": 2, "stroke-linecap": "round" }, g); return { g, x: 64 + i * 67 }; }); + /* gauge */ + txt(svg, 700, 100, "Memory limit", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + el("rect", { x: 680, y: 118, width: 40, height: 230, rx: 20, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const gf = el("rect", { x: 686, width: 28, rx: 14, fill: BLUE }, svg); + el("path", { d: "M670 132 H730", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const tray = (label, col) => { + const g = el("g", {}, svg), blocks = []; + el("path", { d: "M-95 -100 V0 H95 V-100", fill: "#fff", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + for (let r = 0; r < 3; r++) for (let c = 0; c < 4; c++) blocks.push(el("rect", { x: -86 + c * 44, y: -55 - r * 20, width: 40, height: 17, rx: 5, fill: [BLUE, LIL, SKY][(r + c) % 3], stroke: INK, "stroke-width": 2 }, g)); + el("rect", { x: -95, y: -36, width: 190, height: 36, fill: col, stroke: INK, "stroke-width": 3 }, g); + txt(g, 0, -12, label, { "text-anchor": "middle", "font-size": 18, "font-weight": 700 }); + return { g, blocks }; + }; + const t1 = tray("Request 1", SKY), t2 = tray("Request 2", LIL); + t2.blocks.forEach((b) => b.setAttribute("opacity", 0)); + const note = el("g", {}, svg); + el("path", { d: "M-38 -38 H38 V24 L24 38 H-38 Z", fill: YEL, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, note); + el("path", { d: "M38 24 H24 V38", fill: "none", stroke: INK, "stroke-width": 2, "stroke-linejoin": "round" }, note); + txt(note, 0, -6, "Stale", { "text-anchor": "middle", "font-size": 17, "font-weight": 700 }); + txt(note, 0, 16, "state", { "text-anchor": "middle", "font-size": 17, "font-weight": 700 }); + hint(svg, 584, 559, "tap Next request"); + pill(svg, 600, 530, 160, "Next request", YEL, () => api.seek(6)); + const x1 = (t) => -130 + 430 * seg(t, 0.3, 4.4); + const x2 = (t) => -140 + 400 * oc(seg(t, 6.2, 7.6)) + 270 * io(seg(t, 8.5, 9.8)); + return (t) => { + const a = x1(t), b = x2(t), travel = a + 110 + b + 120; + rollers.forEach((r) => tf(r.g, r.x, BY + 17, 1, (travel / 10) * 57.3)); + const k = seg(t, 1.0, 4.4), drain = seg(t, 5.4, 6.2), lvl = k * (1 - drain), h = 218 * lvl; + gf.setAttribute("y", 342 - h); gf.setAttribute("height", Math.max(h, 0)); gf.setAttribute("opacity", h > 1 ? 1 : 0); + gf.setAttribute("fill", k >= 1 && drain < 0.5 ? YEL : BLUE); + const jolt = Math.sin(seg(t, 4.4, 4.9) * Math.PI * 4) * 3 * (1 - seg(t, 4.4, 4.9)), lift = io(seg(t, 5.3, 5.9)); + tf(t1.g, a, BY - 120 * lift, S, jolt); t1.g.setAttribute("opacity", 1 - lift); + t1.blocks.forEach((n, i) => n.setAttribute("opacity", k * 12 > i ? 1 : 0)); + tf(t2.g, b, BY, S); + /* the note: pops out of tray 1, lands on the belt, then hops into tray 2 and rides along */ + const pop = seg(t, 4.7, 5.2), hop = seg(t, 7.7, 8.2); + let nx, ny, nr, no = 1; + if (t < 4.7) { nx = a; ny = BY - 75; nr = 0; no = 0; } + else if (hop <= 0) { const q = io(pop); nx = a + (NX - a) * q; ny = BY - 75 - 90 * Math.sin(q * Math.PI) + 21 * q; nr = -4 * q; } + else { const q = io(hop); nx = NX + (b - NX) * q; ny = BY - 54 - 70 * Math.sin(q * Math.PI) - 41 * q; nr = -4 + 7 * q; } + if (t >= 8.2) { nx = b; ny = BY - 95; nr = 3; } + tf(note, nx, ny, S, nr); note.setAttribute("opacity", no); + }; + }, + }; + + /* ---- 3. a tally counter clicks through four stops ---- */ + SC.tally = { + dur: 11.5, still: 11, + build(svg, api) { + const ST = [1.0, 3.2, 5.4, 7.6], SX = [115, 305, 495, 685], TY = 420; + const STOP = [["10 hours before", "thousands"], ["45 minutes after", "tens of thousands"], ["2 hours after", "hundreds of thousands"], ["later", "millions"]]; + el("path", { d: "M30 280 H770", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + el("path", { d: "M30 290 H770", stroke: SOFT, "stroke-width": 8, "stroke-linecap": "round" }, svg); + const plunger = el("g", {}, svg); + el("rect", { x: 382, y: 82, width: 36, height: 30, fill: GREY, stroke: INK, "stroke-width": 3 }, plunger); + el("rect", { x: 362, y: 62, width: 76, height: 26, rx: 13, fill: LIL, stroke: INK, "stroke-width": 3 }, plunger); + el("circle", { cx: 660, cy: 192, r: 28, fill: "none", stroke: INK, "stroke-width": 9 }, svg); + el("circle", { cx: 660, cy: 192, r: 28, fill: "none", stroke: GREY, "stroke-width": 4 }, svg); + el("rect", { x: 170, y: 104, width: 460, height: 176, rx: 70, fill: GREY, stroke: INK, "stroke-width": 3.5 }, svg); + el("rect", { x: 210, y: 154, width: 380, height: 76, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + const defs = el("defs", {}, svg), cp = el("clipPath", { id: "mg-bo-tally-win" }, defs); + el("rect", { x: 212, y: 156, width: 376, height: 72, rx: 12 }, cp); + const win = el("g", { "clip-path": "url(#mg-bo-tally-win)" }, svg); + const vals = STOP.map((s) => txt(win, 400, 202, s[1], { "text-anchor": "middle", "font-size": 28, "font-weight": 700 })); + const tag = el("g", {}, svg); + el("path", { d: "M-210 0 H200 L216 22 L200 44 H-210 Z", fill: YEL, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, tag); + el("circle", { cx: 196, cy: 22, r: 5, fill: "#fff", stroke: INK, "stroke-width": 2 }, tag); + txt(tag, -6, 30, "Attempts, not confirmed breaches", { "text-anchor": "middle", "font-size": 21, "font-weight": 700 }); + el("path", { d: `M${SX[0]} ${TY} H${SX[3]}`, stroke: SOFT, "stroke-width": 10, "stroke-linecap": "round" }, svg); + const track = el("path", { stroke: LIL, "stroke-width": 10, "stroke-linecap": "round" }, svg); + const dots = STOP.map((s, i) => { + const halo = el("circle", { cx: SX[i], cy: TY, r: 20, fill: "none", stroke: LIL, "stroke-width": 3 }, svg); + const d = el("circle", { cx: SX[i], cy: TY, r: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + txt(svg, SX[i], TY + 46, s[0], { "text-anchor": "middle", "font-size": 19, "font-weight": 700 }); + txt(svg, SX[i], TY + 72, s[1], { "text-anchor": "middle", "font-size": 16, "font-weight": 500, fill: CAP }); + return { d, halo }; + }); + let man = -1, at = 0, last = 0; + const idxAt = (t) => { let i = -1; ST.forEach((s, j) => { if (t >= s) i = j; }); return i; }; + hint(svg, 646, 51, "tap Step"); + pill(svg, 660, 22, 104, "Step", SKY, () => { + const cur = man >= 0 ? man : idxAt(last); + if (cur >= 3) { man = 3; at = api.now() - 9; } else { man = cur + 1; at = api.now(); } + api.poke(); + }); + return (t, now) => { + last = t; + const tt = man < 0 ? t : man === 3 ? Math.min(ST[3] + (now - at), 11) : Math.min(ST[man] + (now - at), ST[man] + 1.2); + const i = idxAt(tt), k = i < 0 ? 1 : oc(seg(tt, ST[i], ST[i] + 0.4)); + const press = i < 0 ? 0 : Math.sin(seg(tt, ST[i], ST[i] + 0.3) * Math.PI); + plunger.setAttribute("transform", `translate(0 ${12 * press})`); + vals.forEach((v, j) => { + const y = j === i ? 40 * (1 - k) : j === i - 1 ? -40 * k : 0, o = j === i ? k : j === i - 1 ? 1 - k : 0; + v.setAttribute("transform", `translate(0 ${y})`); v.setAttribute("opacity", o); + }); + track.setAttribute("d", `M${SX[0]} ${TY} H${i > 0 ? SX[i - 1] + (SX[i] - SX[i - 1]) * k : SX[0]}`); + track.setAttribute("opacity", i > 0 ? 1 : 0); + dots.forEach((d, j) => { + d.d.setAttribute("fill", j <= i ? LIL : "#fff"); + const q = j === i ? seg(tt, ST[i], ST[i] + 0.6) : 1; + d.halo.setAttribute("opacity", j === i ? 1 - q * 0.4 : 0); d.halo.setAttribute("r", 14 + 8 * oc(q)); + }); + const g = oc(seg(tt, 8.6, 9.2)); tf(tag, 400, 316 - 16 * (1 - g)); tag.setAttribute("opacity", g); + }; + }, + }; + + /* ---- 4. a clipboard checklist ticks itself off ---- */ + SC.clipboard = { + dur: 10, still: 9.6, + build(svg, api) { + const T = [1.2, 2.5, 3.8, 5.1], RY = [170, 255, 340, 425], BX = 190; + const ROWS = [[["Run ", 0], ["php -v", 1]], [["Update to the fixed release", 0]], [["Keep the WAF on", 0]], [["Check logs from 17 Oct", 0]]]; + el("rect", { x: 152, y: 72, width: 520, height: 500, rx: 22, fill: LIL }, svg); + el("rect", { x: 140, y: 60, width: 520, height: 500, rx: 22, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 330, y: 36, width: 140, height: 56, rx: 14, fill: GREY, stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: 400, cy: 56, r: 9, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + const ticks = RY.map((y, i) => { + el("rect", { x: BX, y: y - 23, width: 46, height: 46, rx: 9, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + rich(svg, 262, y + 9, ROWS[i], { "font-size": 26, "font-weight": 600 }); + if (i < 3) el("path", { d: `M190 ${y + 42} H610`, stroke: SOFT, "stroke-width": 2 }, svg); + const g = el("g", {}, svg); + const d = "M-12 1 L-4 10 L13 -11"; + el("path", { d, stroke: INK, "stroke-width": 10, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); + el("path", { d, stroke: MINT, "stroke-width": 5, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, g); + return { g, x: BX + 23, y }; + }); + const note = el("g", {}, svg); + el("rect", { x: -182, y: -30, width: 364, height: 60, rx: 6, fill: YEL, stroke: INK, "stroke-width": 2.5 }, note); + txt(note, 0, 8, "Plugins alone do not fix it", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + let man = -1, at = 0, last = 0; + const countAt = (t) => T.filter((s) => t >= s).length; + hint(svg, 778, 510, "tap Check"); + pill(svg, 682, 528, 96, "Check", MINT, () => { + const cur = man >= 0 ? man : countAt(last); + man = cur >= 4 ? 1 : cur + 1; at = api.now(); api.poke(); + }); + const land = (g, x, y, k) => { tf(g, x, y - 34 * (1 - k), 1.5 - 0.5 * k, -12 * (1 - k)); g.setAttribute("opacity", k > 0 ? 1 : 0); }; + return (t, now) => { + last = t; + let nk; + if (man < 0) { + ticks.forEach((s, i) => land(s.g, s.x, s.y, oc(seg(t, T[i], T[i] + 0.35)))); + nk = ob(seg(t, 6.6, 7.1)); + } else { + const e = now - at; + ticks.forEach((s, i) => land(s.g, s.x, s.y, i < man - 1 ? 1 : i === man - 1 ? oc(seg(e, 0, 0.35)) : 0)); + nk = man === 4 ? ob(seg(e, 0.4, 0.9)) : 0; + } + tf(note, 400, 510 + 20 * (1 - nk), 1, -2); note.setAttribute("opacity", cl(nk)); + }; + }, + }; + + /* ---- title card, 16 by 9: a server rack whose four PHP versions get a tick in turn ---- */ + SC.hero = { + w: 960, h: 540, dur: 6, still: 5.8, + build(svg) { + el("rect", { x: 64, y: 90, width: 132, height: 42, rx: 21, fill: LIL, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 130, 118, "Security", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + txt(svg, 60, 238, "Black", { "font-size": 100, "font-weight": 700 }); + txt(svg, 60, 336, "October", { "font-size": 100, "font-weight": 700 }); + el("path", { d: "M66 364 H468", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + rich(svg, 64, 418, [["What ", 0], ["PHP2Shell", 1], [" is and how to patch", 0]], { "font-size": 25, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 464, "Checked 23 Oct 2026", { "font-size": 18, "font-weight": 500, fill: CAP }); + const RX = 600, RW = 250; + el("rect", { x: RX + 12, y: 82, width: RW, height: 400, rx: 18, fill: SOFT }, svg); + el("rect", { x: RX, y: 70, width: RW, height: 400, rx: 18, fill: "#fff", stroke: INK, "stroke-width": 3.5 }, svg); + el("path", { d: `M${RX + 30} 470 V490 H${RX + 60} V470 M${RX + RW - 60} 470 V490 H${RX + RW - 30} V470`, fill: GREY, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + const V = ["8.5.12", "8.4.27", "8.3.36", "8.2.35"]; + const ticks = V.map((v, i) => { + const y = 96 + i * 92; + el("rect", { x: RX + 22, y, width: RW - 44, height: 72, rx: 10, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, RX + 42, y + 46, v, { "font-family": MONO, "font-size": 28, "font-weight": 700 }); + [0, 1, 2].forEach((j) => el("path", { d: `M${RX + 172 + j * 12} ${y + 24} V${y + 48}`, stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg)); + el("circle", { cx: RX + RW + 42, cy: y + 36, r: 18, fill: "#fff", stroke: INK, "stroke-width": 2, "stroke-dasharray": "3 5" }, svg); + const b = badge(svg, 18); + return { b, x: RX + RW + 42, y: y + 36, at: 0.8 + i * 0.9 }; + }); + return (t) => ticks.forEach((s) => { const k = ob(seg(t, s.at, s.at + 0.4)); tf(s.b, s.x, s.y, cl(k, 0, 1.3)); s.b.setAttribute("opacity", t >= s.at ? 1 : 0); }); + }, + }; + + /* ---- player ---- */ + const q = new URLSearchParams(location.search), frozen = q.has("mgstill"), fixed = parseFloat(q.get("mgstill")); + const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; + const mount = (fig) => { + const sc = SC[fig.dataset.scene], img = fig.querySelector("img"); if (!sc || !img) return; + const W = sc.w || 800, H = sc.h || 600; + const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, class: "mg-svg", role: "group", "aria-label": img.alt || "" }); + svg.style.aspectRatio = `${W} / ${H}`; + el("rect", { width: W, height: H, fill: "#fff" }, svg); + img.replaceWith(svg); + let off = 0, raf = 0, seen = false, held = calm; + const base = performance.now(), clock = () => (performance.now() - base) / 1000; + let draw = () => {}; + const frame = () => { const now = clock(); draw(held ? sc.still : (((now + off) % sc.dur) + sc.dur) % sc.dur, now); }; + const tick = () => { cancelAnimationFrame(raf); frame(); if (seen && !held) raf = requestAnimationFrame(tick); }; + const api = { now: clock, seek: (s) => { off = s - clock(); held = false; tick(); }, poke: () => { held = false; tick(); } }; + draw = sc.build(svg, api); + if (frozen) { draw(isNaN(fixed) ? sc.still : fixed, 0); return; } + new IntersectionObserver(([e]) => { + seen = e.isIntersecting; + if (seen) { tick(); if (!fig.dataset.sent && window.posthog) { fig.dataset.sent = 1; window.posthog.capture("blog_scene_view", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } } + else cancelAnimationFrame(raf); + }, { threshold: 0.25 }).observe(fig); + svg.addEventListener("click", () => { if (!fig.dataset.hit && window.posthog) { fig.dataset.hit = 1; window.posthog.capture("blog_scene_interact", { slug: location.pathname.split("/").filter(Boolean).pop(), scene: fig.dataset.scene }); } }); + frame(); + }; + const go = () => document.querySelectorAll("figure.mg[data-scene]").forEach(mount); + const fonts = document.fonts && document.fonts.load ? Promise.all([document.fonts.load('700 32px "Space Grotesk"'), document.fonts.load('500 20px "Space Grotesk"'), document.fonts.load('700 20px "JetBrains Mono"')]).catch(() => {}) : Promise.resolve(); + const start = () => fonts.then(go, go); + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start(); +})(); diff --git a/blog/src/assets/media/black-october-php2shell/tally.png b/blog/src/assets/media/black-october-php2shell/tally.png new file mode 100644 index 0000000000..ccdc0569d9 Binary files /dev/null and b/blog/src/assets/media/black-october-php2shell/tally.png differ diff --git a/blog/src/assets/media/claude-duet-app/hero.png b/blog/src/assets/media/claude-duet-app/hero.png new file mode 100644 index 0000000000..749ca617dc Binary files /dev/null and b/blog/src/assets/media/claude-duet-app/hero.png differ diff --git a/blog/src/assets/media/claude-duet-app/jar.png b/blog/src/assets/media/claude-duet-app/jar.png new file mode 100644 index 0000000000..29a870d5cd Binary files /dev/null and b/blog/src/assets/media/claude-duet-app/jar.png differ diff --git a/blog/src/assets/media/claude-duet-app/keyring.png b/blog/src/assets/media/claude-duet-app/keyring.png new file mode 100644 index 0000000000..6127d22a5a Binary files /dev/null and b/blog/src/assets/media/claude-duet-app/keyring.png differ diff --git a/blog/src/assets/media/claude-duet-app/motion.css b/blog/src/assets/media/claude-duet-app/motion.css new file mode 100644 index 0000000000..4966d72c36 --- /dev/null +++ b/blog/src/assets/media/claude-duet-app/motion.css @@ -0,0 +1,13 @@ +/* Bright coded scenes for "Claude Duet app". 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-duet-app/motion.js b/blog/src/assets/media/claude-duet-app/motion.js new file mode 100644 index 0000000000..ad771dc2e4 --- /dev/null +++ b/blog/src/assets/media/claude-duet-app/motion.js @@ -0,0 +1,368 @@ +/* Bright coded scenes for "Claude Duet app". 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", CAP = "#6B5878"; + 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(); } }); + }; + let uid = 0; + const pill = (svg, cx, cy, w, label, fn) => { + const g = el("g", {}, svg); + el("rect", { x: cx - w / 2 - 7, y: cy - 32, width: w + 14, height: 64, rx: 32, fill: "none", stroke: INK, "stroke-width": 2.5, class: "mg-ring" }, g); + el("rect", { x: cx - w / 2, y: cy - 25, width: w, height: 50, rx: 25, fill: YEL, stroke: INK, "stroke-width": 3 }, g); + txt(g, cx, cy + 8, label, { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + button(g, label, fn); + return g; + }; + const hint = (svg, s, x = 756, y = 52, anchor = "end") => txt(svg, x, y, s, { "text-anchor": anchor, "font-size": 18, "font-weight": 500, fill: MUTE }); + + const SC = {}; + + /* ---- 1. one key ring moves from the Claude app hook to the Claude Duet hook ---- */ + SC.keyring = { + dur: 7.2, still: 6.4, + build(svg, api) { + hint(svg, "tap Swing"); + const HX = [240, 560], P = [[252, 205], [572, 205]]; + HX.forEach((x, i) => { + txt(svg, x, 112, i ? "Claude Duet" : "Claude app", { "text-anchor": "middle", "font-family": MONO, "font-size": 24, "font-weight": 700 }); + el("rect", { x: x - 34, y: 130, width: 68, height: 32, rx: 10, fill: i ? LIL : SKY, stroke: INK, "stroke-width": 3 }, svg); + [-20, 20].forEach((d) => el("circle", { cx: x + d, cy: 146, r: 3.5, fill: "#fff", stroke: INK, "stroke-width": 2 }, svg)); + }); + const ring = el("g", {}, svg), dang = el("g", {}, ring); + const T = [["Memory", BLUE, -140], ["Skills", LIL, 0], ["Connectors", MINT, 140]]; + T.forEach(([, , x]) => el("path", { d: `M0 60 L${x} 176`, stroke: INK, "stroke-width": 2 }, dang)); + T.forEach(([s, c, x]) => { + el("rect", { x: x - 62, y: 176, width: 124, height: 40, rx: 10, fill: c, stroke: INK, "stroke-width": 3 }, dang); + txt(dang, x, 202.5, s, { "text-anchor": "middle", "font-size": 18, "font-weight": 700 }); + }); + el("path", { d: "M0 60 V86", stroke: INK, "stroke-width": 2.5 }, ring); + el("rect", { x: -128, y: 86, width: 256, height: 48, rx: 12, fill: YEL, stroke: INK, "stroke-width": 3 }, ring); + el("circle", { cx: -108, cy: 110, r: 5, fill: "#fff", stroke: INK, "stroke-width": 2 }, ring); + txt(ring, 10, 117, "Your Claude account", { "text-anchor": "middle", "font-size": 20, "font-weight": 700 }); + el("circle", { cx: 0, cy: 30, r: 30, fill: "none", stroke: INK, "stroke-width": 10 }, ring); + el("circle", { cx: 0, cy: 30, r: 30, fill: "none", stroke: YEL, "stroke-width": 4.5 }, ring); + HX.forEach((x) => el("path", { d: `M${x} 160 V188 Q${x} 208 ${x + 12} 208 Q${x + 26} 208 ${x + 26} 192`, stroke: INK, "stroke-width": 7, fill: "none", "stroke-linecap": "round" }, svg)); + pill(svg, 400, 528, 150, "Swing", () => api.seek(0.75)); + const ang = (t) => { + if (t < 0.9) return 3 * Math.sin(t * 2.6) * (1 - seg(t, 0.6, 0.9)); + if (t < 3) return 14 * Math.sin(2 * Math.PI * io(seg(t, 0.9, 3))); + const s = t - 3; return -12 * Math.exp(-1.6 * s) * Math.sin(4.8 * s); + }; + return (t) => { + const u = io(seg(t, 0.9, 3)), th = ang(t); + const x = P[0][0] + (P[1][0] - P[0][0]) * u, y = P[0][1] - 50 * Math.sin(Math.PI * u); + ring.setAttribute("transform", `translate(${x} ${y}) rotate(${th})`); + dang.setAttribute("transform", `rotate(${(ang(t - 0.18) - th) * 0.8} 0 60)`); + ring.setAttribute("opacity", seg(t, 0, 0.35) * (1 - seg(t, 6.85, 7.2))); + }; + }, + }; + + /* ---- 2. four stands on a shelf, three get a lamp, one stays empty ---- */ + SC.stands = { + dur: 8.4, still: 7.8, + build(svg, api) { + const X = [130, 310, 490, 670], ON = [1.0, 2.2, 3.4], CARD = 4.6, LITC = [SKY, MINT, LIL]; + const LAB = [["iPhone"], ["Android"], ["Web at", "claude.ai"], ["Mac or", "Windows app"]]; + const tap = [null, null, null, null]; + const cones = X.map((x) => el("path", { d: `M${x - 26} 108 L${x + 26} 108 L${x + 76} 398 L${x - 76} 398 Z`, fill: YEL, opacity: 0 }, svg)); + const lamps = X.map((x) => { + el("path", { d: `M${x} 0 V72`, stroke: INK, "stroke-width": 2.5 }, svg); + const bulb = el("circle", { cx: x, cy: 106, r: 9, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + const shade = el("path", { d: `M${x - 30} 104 L${x - 15} 70 H${x + 15} L${x + 30} 104 Z`, fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + return { bulb, shade }; + }); + el("rect", { x: 40, y: 400, width: 720, height: 20, rx: 6, fill: YEL, stroke: INK, "stroke-width": 3 }, svg); + [220, 580].forEach((x) => el("path", { d: `M${x - 18} 420 V462 L${x + 22} 420 Z`, fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg)); + LAB.forEach((ls, i) => ls.forEach((s, j) => txt(svg, X[i], 456 + j * 26, s, Object.assign({ "text-anchor": "middle", "font-size": 20, "font-weight": 600 }, s === "claude.ai" ? { "font-family": MONO } : {})))); + hint(svg, "tap a stand", 400, 562, "middle"); + const screens = []; + let card; + X.forEach((x, i) => { + const g = el("g", {}, svg); + el("rect", { x: x - 84, y: 172, width: 168, height: 238, rx: 18, fill: "#fff", "fill-opacity": 0, stroke: INK, "stroke-width": 2.5, "stroke-dasharray": "5 6", class: "mg-ring" }, g); + if (i === 2) { + el("path", { d: `M${x - 60} 400 L${x - 44} 378 H${x + 44} L${x + 60} 400 Z`, fill: LIL, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, g); + el("rect", { x: x - 52, y: 290, width: 104, height: 78, rx: 8, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + screens[i] = el("rect", { x: x - 44, y: 298, width: 88, height: 62, rx: 4, fill: SOFT }, g); + el("rect", { x: x - 66, y: 366, width: 132, height: 12, rx: 5, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + } else { + el("path", { d: `M${x + 10} 386 L${x + 40} 310`, stroke: INK, "stroke-width": 4, "stroke-linecap": "round" }, g); + el("rect", { x: x - 42, y: 386, width: 84, height: 14, rx: 4, fill: LIL, stroke: INK, "stroke-width": 3 }, g); + if (i < 3) { + el("rect", { x: x - 28, y: 280, width: 56, height: 104, rx: 11, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + screens[i] = el("rect", { x: x - 21, y: 291, width: 42, height: 78, rx: 4, fill: SOFT }, g); + } else { + el("rect", { x: x - 28, y: 280, width: 56, height: 104, rx: 11, fill: "none", stroke: MUTE, "stroke-width": 2, "stroke-dasharray": "6 6" }, g); + card = el("g", {}, g); + el("rect", { x: x - 80, y: 184, width: 160, height: 74, rx: 12, fill: SOFT, stroke: INK, "stroke-width": 2.5 }, card); + txt(card, x, 215, "Not named", { "text-anchor": "middle", "font-size": 19, "font-weight": 700 }); + txt(card, x, 241, "by Anthropic", { "text-anchor": "middle", "font-size": 19, "font-weight": 700 }); + } + el("rect", { x: x - 36, y: 374, width: 72, height: 12, rx: 4, fill: LIL, stroke: INK, "stroke-width": 3 }, g); + } + button(g, LAB[i].join(" "), () => { tap[i] = api.now(); api.poke(); }); + }); + return (t, now) => { + for (let i = 0; i < 3; i++) { + const tapped = tap[i] != null, k = tapped ? 1 : oc(seg(t, ON[i], ON[i] + 0.4)); + const p = Math.sin((tapped ? seg(now - tap[i], 0, 0.5) : seg(t, ON[i], ON[i] + 0.4)) * Math.PI); + cones[i].setAttribute("opacity", 0.32 * k); + lamps[i].bulb.setAttribute("fill", k > 0.4 ? YEL : "#fff"); + lamps[i].shade.setAttribute("fill", k > 0.4 ? YEL : SOFT); + lamps[i].bulb.setAttribute("r", 9 + 4 * p); + screens[i].setAttribute("fill", k > 0.4 ? LITC[i] : SOFT); + } + if (tap[3] != null) { card.setAttribute("opacity", 1); card.setAttribute("transform", `translate(670 221) scale(${1 + 0.12 * Math.sin(seg(now - tap[3], 0, 0.5) * Math.PI)}) translate(-670 -221)`); } + else { const k = ob(seg(t, CARD, CARD + 0.45)); card.setAttribute("opacity", cl(k * 3)); card.setAttribute("transform", `translate(670 221) scale(${Math.max(0.01, k)}) translate(-670 -221)`); } + }; + }, + }; + + /* ---- 3. invitation parcel, sign in key, a few minutes, ready lamp ---- */ + SC.parcel = { + dur: 9.6, still: 9.0, + build(svg, api) { + hint(svg, "tap Open"); + const defs = el("defs", {}, svg); + // doormat + el("rect", { x: 70, y: 228, width: 260, height: 24, rx: 8, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + for (let x = 92; x <= 308; x += 18) el("path", { d: `M${x} 234 V246`, stroke: "#fff", "stroke-width": 3, "stroke-linecap": "round" }, svg); + // parcel with its tag behind the front + const box = el("g", {}, svg), pid = "mgc" + (++uid); + el("rect", { x: 0, y: -400, width: 800, height: 626 }, el("clipPath", { id: pid }, defs)); + const tagG = el("g", {}, el("g", { "clip-path": `url(#${pid})` }, box)); + el("path", { d: "M200 84 V150", stroke: INK, "stroke-width": 2.5 }, tagG); + el("rect", { x: 112, y: 46, width: 176, height: 38, rx: 10, fill: SKY, stroke: INK, "stroke-width": 2.5 }, tagG); + txt(tagG, 200, 71, "iPhone or Android", { "text-anchor": "middle", "font-size": 17, "font-weight": 700 }); + el("rect", { x: 115, y: 138, width: 170, height: 90, rx: 6, fill: YEL, stroke: INK, "stroke-width": 3 }, box); + txt(box, 200, 191, "Invitation email", { "text-anchor": "middle", "font-size": 18, "font-weight": 700 }); + const fl = el("rect", { x: 115, y: 130, width: 85, height: 10, rx: 3, fill: YEL, stroke: INK, "stroke-width": 3 }, box); + const fr = el("rect", { x: 200, y: 130, width: 85, height: 10, rx: 3, fill: YEL, stroke: INK, "stroke-width": 3 }, box); + // lock + const shackle = el("g", {}, svg); + el("path", { d: "M595 142 V108 A30 30 0 0 1 655 108 V142", stroke: INK, "stroke-width": 11, fill: "none" }, shackle); + el("path", { d: "M595 142 V108 A30 30 0 0 1 655 108 V142", stroke: SOFT, "stroke-width": 5, fill: "none" }, shackle); + const body = el("rect", { x: 570, y: 138, width: 110, height: 92, rx: 14, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: 625, cy: 178, r: 8, fill: INK }, svg); + el("path", { d: "M621 180 L618 200 H632 L629 180 Z", fill: INK }, svg); + txt(svg, 625, 264, "Sign in", { "text-anchor": "middle", "font-size": 20, "font-weight": 600 }); + // key with its tag + const slide = el("g", {}, svg); + el("path", { d: "M-118 16 L-152 54", stroke: INK, "stroke-width": 2.5 }, slide); + el("rect", { x: -250, y: 54, width: 196, height: 34, rx: 10, fill: MINT, stroke: INK, "stroke-width": 2.5 }, slide); + txt(slide, -152, 77, "Your Claude account", { "text-anchor": "middle", "font-size": 17, "font-weight": 700 }); + const key = el("g", {}, slide); + el("rect", { x: -104, y: -5, width: 104, height: 10, rx: 3, fill: YEL, stroke: INK, "stroke-width": 2.5 }, key); + el("path", { d: "M-24 5 V15 H-16 V5 M-12 5 V12 H-4 V5", fill: YEL, stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }, key); + el("circle", { cx: -118, cy: 0, r: 18, fill: YEL, stroke: INK, "stroke-width": 3 }, key); + el("circle", { cx: -118, cy: 0, r: 5, fill: "#fff", stroke: INK, "stroke-width": 2 }, key); + // sand timer + const gid = "mgc" + (++uid), glass = "M168 322 C168 360 196 372 196 385 C196 398 168 410 168 448 H232 C232 410 204 398 204 385 C204 372 232 360 232 322 Z"; + el("path", { d: glass }, el("clipPath", { id: gid }, defs)); + el("path", { d: glass, fill: "#fff" }, svg); + const sand = el("g", { "clip-path": `url(#${gid})` }, svg); + const top = el("rect", { x: 160, y: 326, width: 80, height: 60, fill: YEL }, sand); + const bot = el("rect", { x: 160, y: 448, width: 80, height: 0, fill: YEL }, sand); + const stream = el("path", { d: "M200 384 V448", stroke: YEL, "stroke-width": 4 }, sand); + el("path", { d: glass, fill: "none", stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + [158, 242].forEach((x) => el("path", { d: `M${x} 322 V448`, stroke: INK, "stroke-width": 4 }, svg)); + [310, 448].forEach((y) => el("rect", { x: 148, y, width: 104, height: 12, rx: 5, fill: LIL, stroke: INK, "stroke-width": 3 }, svg)); + txt(svg, 200, 494, "A few minutes", { "text-anchor": "middle", "font-size": 20, "font-weight": 600 }); + // desk lamp + el("path", { d: "M470 460 H740", stroke: INK, "stroke-width": 3, "stroke-linecap": "round" }, svg); + const beam = el("path", { d: "M582 366 L662 366 L716 458 L528 458 Z", fill: YEL, opacity: 0 }, svg); + el("path", { d: "M600 446 L566 386 L618 332", stroke: INK, "stroke-width": 7, fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + el("circle", { cx: 566, cy: 386, r: 7, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 550, y: 444, width: 100, height: 16, rx: 8, fill: BLUE, stroke: INK, "stroke-width": 3 }, svg); + const bulb = el("circle", { cx: 622, cy: 366, r: 10, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + const shade = el("path", { d: "M600 326 L644 326 L662 366 L582 366 Z", fill: SOFT, stroke: INK, "stroke-width": 3, "stroke-linejoin": "round" }, svg); + txt(svg, 600, 494, "Ready", { "text-anchor": "middle", "font-size": 20, "font-weight": 600 }); + pill(svg, 400, 548, 130, "Open", () => api.seek(0)); + return (t) => { + const d = seg(t, 0.4, 1.1), bump = Math.sin(seg(t, 1.1, 1.35) * Math.PI) * 12; + box.setAttribute("transform", `translate(0 ${-260 * (1 - oc(d)) - bump})`); + box.setAttribute("opacity", seg(t, 0.4, 0.5)); + const f = io(seg(t, 1.4, 1.9)); + fl.setAttribute("transform", `rotate(${-125 * f} 115 140)`); + fr.setAttribute("transform", `rotate(${125 * f} 285 140)`); + tagG.setAttribute("transform", `translate(0 ${100 * (1 - ob(seg(t, 1.7, 2.3)))})`); + const s = oc(seg(t, 2.6, 3.3)), turn = io(seg(t, 3.3, 3.9)); + slide.setAttribute("transform", `translate(${625 - 60 * (1 - s)} 182)`); + slide.setAttribute("opacity", seg(t, 2.6, 2.9)); + key.setAttribute("transform", `scale(1 ${Math.cos(Math.PI * turn)})`); + const open = ob(seg(t, 3.9, 4.25)); + shackle.setAttribute("transform", `translate(0 ${-16 * open})`); + body.setAttribute("fill", open > 0.5 ? MINT : BLUE); + const k = io(seg(t, 4.6, 6.8)); + top.setAttribute("y", 326 + 58 * k); top.setAttribute("height", 60 - 58 * k); + bot.setAttribute("y", 448 - 56 * k); bot.setAttribute("height", 56 * k); + stream.setAttribute("opacity", k > 0 && k < 1 ? 1 : 0); + const on = oc(seg(t, 7.0, 7.3)); + beam.setAttribute("opacity", 0.34 * on); + bulb.setAttribute("fill", on > 0.3 ? YEL : "#fff"); shade.setAttribute("fill", on > 0.3 ? YEL : SOFT); + }; + }, + }; + + /* ---- 4. three spoons scoop from one jar, the price tag reads None ---- */ + SC.jar = { + dur: 9.2, still: 8.6, + build(svg, api) { + hint(svg, "tap Scoop"); + const defs = el("defs", {}, svg), S = [0.7, 2.5, 4.3], FLIP = 6.3; + const lvl = (n) => 0.35 + 0.55 * Math.pow(0.85, n), LY = (f) => 466 - f * 236; + el("rect", { x: 30, y: 470, width: 740, height: 16, rx: 6, fill: SOFT, stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 486, y: 248, width: 262, height: 222, rx: 18, fill: SOFT }, svg); + // lid leaning on the left, with its price tag + el("circle", { cx: 122, cy: 404, r: 62, fill: LIL, stroke: INK, "stroke-width": 3 }, svg); + el("circle", { cx: 122, cy: 404, r: 48, fill: "none", stroke: INK, "stroke-width": 2 }, svg); + el("circle", { cx: 122, cy: 350, r: 6, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + el("path", { d: "M122 356 V396", stroke: INK, "stroke-width": 2.5 }, svg); + const tag = el("g", {}, svg); + const tagR = el("rect", { x: 47, y: 388, width: 150, height: 48, rx: 10, fill: YEL, stroke: INK, "stroke-width": 3 }, tag); + el("circle", { cx: 122, cy: 397, r: 4, fill: "#fff", stroke: INK, "stroke-width": 2 }, tag); + const tagT = txt(tag, 122, 426, "Extra charge", { "text-anchor": "middle", "font-size": 19, "font-weight": 700 }); + // jar contents + const jarD = "M250 200 V222 Q210 226 210 260 V430 Q210 470 250 470 H390 Q430 470 430 430 V260 Q430 226 390 222 V200"; + const cid = "mgc" + (++uid); + el("path", { d: jarD + " Z" }, el("clipPath", { id: cid }, defs)); + const fillG = el("g", { "clip-path": `url(#${cid})` }, svg); + const goo = el("rect", { x: 205, y: 254, width: 230, height: 300, fill: YEL }, fillG); + const surf = el("path", { d: "M205 254 H435", stroke: INK, "stroke-width": 2.5 }, fillG); + el("path", { d: "M228 272 V420", stroke: "#fff", "stroke-width": 6, "stroke-linecap": "round", opacity: 0.8 }, fillG); + // spoons + const SP = [["Chats", SKY], ["Background checks", LIL], ["Large tasks", MINT]]; + const REST = [[612, 290], [612, 360], [612, 430]]; + const spoons = SP.map(([s, c]) => { + const g = el("g", {}, svg); + el("rect", { x: -58, y: -14, width: 172, height: 28, rx: 14, fill: c, stroke: INK, "stroke-width": 3 }, g); + txt(g, 30, 5, s, { "text-anchor": "middle", "font-size": 14, "font-weight": 700 }); + el("ellipse", { cx: -80, cy: 0, rx: 25, ry: 17, fill: "#fff", stroke: INK, "stroke-width": 3 }, g); + const scoop = el("circle", { cx: -80, cy: 0, r: 12, fill: YEL, stroke: INK, "stroke-width": 2 }, g); + return { g, scoop }; + }); + // glass on top so a dipping spoon reads as inside + el("path", { d: jarD, fill: "none", stroke: INK, "stroke-width": 4, "stroke-linecap": "round", "stroke-linejoin": "round" }, svg); + el("rect", { x: 242, y: 192, width: 156, height: 14, rx: 7, fill: "none", stroke: INK, "stroke-width": 3 }, svg); + el("rect", { x: 252, y: 360, width: 136, height: 66, rx: 10, fill: "#fff", stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 320, 388, "Your plan's", { "text-anchor": "middle", "font-size": 18, "font-weight": 700 }); + txt(svg, 320, 412, "usage limits", { "text-anchor": "middle", "font-size": 18, "font-weight": 700 }); + let extra = 0, tapAt = null; + pill(svg, 400, 545, 140, "Scoop", () => { extra++; tapAt = api.now(); api.poke(); }); + // r: seconds into one scoop; y: level line; returns [x, y, angle] + const pose = (j, r, y) => { + const [rx, ry] = REST[j], st = [341, 112], dip = [341, y + 18 - 77]; + const a = io(seg(r, 0, 0.45)), dn = io(seg(r, 0.45, 0.75)), up = io(seg(r, 0.75, 1.0)), back = io(seg(r, 1.0, 1.45)); + if (r < 0.45) return [rx + (st[0] - rx) * a, ry + (st[1] - ry) * a, -75 * a]; + if (r < 0.75) return [st[0], st[1] + (dip[1] - st[1]) * dn, -75]; + if (r < 1.0) return [st[0], dip[1] + (st[1] - dip[1]) * up, -75]; + return [st[0] + (rx - st[0]) * back, st[1] + (ry - st[1]) * back, -75 * (1 - back)]; + }; + return (t, now) => { + const T = extra > 0 ? 8.6 : t; + let n = 0, f = lvl(0); + S.forEach((s0, j) => { if (T >= s0 + 0.62) n = j + 1; }); + S.forEach((s0, j) => { const r = T - s0; if (r >= 0.55 && r < 0.8) f = lvl(j) + (lvl(j + 1) - lvl(j)) * oc(seg(r, 0.55, 0.8)); }); + if (!S.some((s0) => T - s0 >= 0.55 && T - s0 < 0.8)) f = lvl(n); + let busy = -1, rr = 0; + if (extra > 0) { + const r = now - tapAt, m = 3 + extra; + busy = (extra - 1) % 3; rr = r; + f = r < 0.55 ? lvl(m - 1) : lvl(m - 1) + (lvl(m) - lvl(m - 1)) * oc(seg(r, 0.55, 0.8)); + } + const y = LY(f); + goo.setAttribute("y", y); surf.setAttribute("d", `M205 ${y} H435`); + spoons.forEach((sp, j) => { + let r = T - S[j], has = r >= 0.75; + if (busy === j) { r = rr; has = true; } + const [x, yy, a] = r > 0 && r < 1.45 ? pose(j, r, y) : REST[j].concat(0); + tf(sp.g, x, yy, 1, a); + sp.scoop.setAttribute("opacity", has ? 1 : 0); + }); + const k = io(seg(T, FLIP, FLIP + 0.6)), c = Math.cos(Math.PI * k), back = c < 0; + tag.setAttribute("transform", `translate(122 0) scale(${Math.max(0.02, Math.abs(c))} 1) translate(-122 0)`); + tagR.setAttribute("fill", back ? MINT : YEL); + tagT.textContent = back ? "None" : "Extra charge"; + }; + }, + }; + + /* ---- title card, 16 by 9 ---- */ + SC.hero = { + w: 960, h: 540, dur: 6, still: 5.6, + build(svg) { + el("rect", { x: 64, y: 62, width: 84, height: 40, rx: 20, fill: YEL, stroke: INK, "stroke-width": 2.5 }, svg); + txt(svg, 106, 89, "App", { "text-anchor": "middle", "font-size": 22, "font-weight": 700 }); + txt(svg, 64, 194, "Claude Duet app", { "font-size": 84, "font-weight": 700 }); + el("path", { d: "M66 222 H560", stroke: YEL, "stroke-width": 14, "stroke-linecap": "round" }, svg); + txt(svg, 64, 278, "iPhone, Android and web", { "font-size": 30, "font-weight": 500, fill: MUTE }); + txt(svg, 64, 320, "Checked 23 Oct 2026", { "font-size": 20, "font-weight": 500, fill: CAP }); + el("path", { d: "M150 436 H820", stroke: SOFT, "stroke-width": 22, "stroke-linecap": "round" }, svg); + const links = [[178, 262], [318, 400], [620, 730]].map(([a, b]) => el("path", { d: `M${a} 436 H${b}`, stroke: INK, "stroke-width": 2.5, "stroke-dasharray": "2 12", "stroke-linecap": "round" }, svg)); + const screens = []; + [150, 290].forEach((x) => { + el("rect", { x: x - 28, y: 384, width: 56, height: 104, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + screens.push(el("rect", { x: x - 20, y: 396, width: 40, height: 76, rx: 4, fill: SOFT }, svg)); + el("path", { d: `M${x - 7} 480 H${x + 7}`, stroke: INK, "stroke-width": 2.5, "stroke-linecap": "round" }, svg); + }); + el("rect", { x: 400, y: 380, width: 220, height: 112, rx: 12, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + el("path", { d: "M400 404 H620", stroke: INK, "stroke-width": 2.5 }, svg); + [418, 432, 446].forEach((x) => el("circle", { cx: x, cy: 392, r: 4, fill: SOFT, stroke: INK, "stroke-width": 1.5 }, svg)); + screens.push(el("rect", { x: 412, y: 414, width: 196, height: 66, rx: 6, fill: SOFT }, svg)); + const card = el("rect", { x: 730, y: 404, width: 180, height: 66, rx: 14, fill: "#fff", stroke: INK, "stroke-width": 3 }, svg); + txt(svg, 820, 445, "Claude Duet", { "text-anchor": "middle", "font-family": MONO, "font-size": 24, "font-weight": 700 }); + const ON = [0.6, 1.6, 2.6], CARDT = 3.6, COL = [SKY, MINT, LIL]; + return (t) => { + screens.forEach((s, i) => s.setAttribute("fill", t >= ON[i] ? COL[i] : SOFT)); + links.forEach((l, i) => l.setAttribute("stroke", t >= (i < 2 ? ON[i + 1] : CARDT) ? BLUE : INK)); + card.setAttribute("fill", t >= CARDT ? YEL : "#fff"); + }; + }, + }; + + /* ---- 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-duet-app/parcel.png b/blog/src/assets/media/claude-duet-app/parcel.png new file mode 100644 index 0000000000..8ce556985e Binary files /dev/null and b/blog/src/assets/media/claude-duet-app/parcel.png differ diff --git a/blog/src/assets/media/claude-duet-app/stands.png b/blog/src/assets/media/claude-duet-app/stands.png new file mode 100644 index 0000000000..bf2139946d Binary files /dev/null and b/blog/src/assets/media/claude-duet-app/stands.png differ diff --git a/blog/src/posts/agent-tools-today-2026-10-22.md b/blog/src/posts/agent-tools-today-2026-10-22.md index f4ca898059..5a614c7c61 100644 --- a/blog/src/posts/agent-tools-today-2026-10-22.md +++ b/blog/src/posts/agent-tools-today-2026-10-22.md @@ -47,7 +47,7 @@ New repos can gain stars quickly for reasons other than use, so treat these as n ## Top 3 things people are arguing about 1. **How the LiteLLM release was poisoned.** BerriAI's incident report says the attacker used GitHub CLI OAuth tokens from three engineers, which it believes were read from the logs of an internal LiteLLM deployment open to the internet. It adds that in specific situations "Claude Code will embed a user's GitHub CLI OAuth token into a response body." SafeDep's analysis of the release passed 450 points on Hacker News. [Source](https://docs.litellm.ai/blog/security-incident-oct-21-2026) -2. **Is PHP2Shell as bad as it sounds?** Wiz says frontier AI helped find remote code execution and memory disclosure flaws in core PHP, with nearly every version back to 5.4 hit by at least one; PHP shipped fixes on 21 Oct. Elias Taylor replied that there is no easy exploit and that the public proofs of concept "do not work against a vanilla PHP instance." More than 430 and 240 points on Hacker News. [Source](https://www.wiz.io/blog/php2shell-php-rce) +2. **Is PHP2Shell as bad as it sounds?** Wiz says frontier AI helped find remote code execution and memory disclosure flaws in core PHP, with nearly every version back to 5.4 hit by at least one; PHP shipped fixes on 21 Oct. Elias Taylor replied that there is no easy exploit and that the public proofs of concept "do not work against a vanilla PHP instance." More than 430 and 240 points on Hacker News. Fixed versions are in our [PHP2Shell guide](/blog/black-october-php2shell/). [Source](https://www.wiz.io/blog/php2shell-php-rce) 3. **Claude shrank what strangers can reach in OpenSSH.** Anthropic's Frontier Red Team post of 20 Oct reports 493 issues across 25 open source projects with more than five million lines, using three attack surface methods. In OpenSSH, its fix removed roughly a fifth of the code an unauthenticated client could reach in a default build, shipped in 10.6. More than 130 points on Hacker News. [Source](https://www.anthropic.com/research/attack-surface-reduction) ## How this list is built diff --git a/blog/src/posts/agent-tools-today-2026-10-23.md b/blog/src/posts/agent-tools-today-2026-10-23.md new file mode 100644 index 0000000000..ca825afbd0 --- /dev/null +++ b/blog/src/posts/agent-tools-today-2026-10-23.md @@ -0,0 +1,59 @@ +--- +title: "Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell" +seoTitle: "Agent Tools Today (23 Oct 2026): Launches, Skills, MCP Servers" +description: "The top launches, skills and MCP servers, multi agent moves, rising repos and arguments in coding agents on 23 Oct 2026, each with its source." +date: 2026-10-23 +category: news +categoryLabel: News +type: Non-technical +series: agent-tools-today +primaryKeyword: "agent tools today 23 oct 2026" +secondaryKeywords: ["claude duet", "openai cli", "nvidia openshell", "omnara", "copilot cli 1.0.103", "openclaw 2026.10.6"] +tags: ["Daily Brief", "Claude Code", "MCP", "Open Source"] +--- + +Agent tools today, 23 Oct 2026, in one line: Anthropic opened a waitlist for Claude Duet, a proactive agent app, OpenAI shipped an official CLI for its API, and Nvidia's OpenShell sandbox reached the Hacker News front page. Every number was checked on 23 Oct. The last edition is [22 Oct](/blog/agent-tools-today-2026-10-22/). + +[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. It is not ranked in the lists below. + +
Animation. On a white background, a small square labelled Agent sits inside a box labelled Sandbox. The square sends three requests outward: one to a file, one to a package site and one to a new host. A gate labelled Policy lets the first two through with a green tick and adds a key only to the approved one. The third request stops at the gate with a label that reads Waiting for review. Tap Approve to let it through.
How OpenShell checks each agent request against a policy, drawn from its README on 23 Oct 2026.
+ +## Top 3 launches + +1. **Claude Duet.** Anthropic opened a research preview of "a new app where Claude works as a proactive agent": it sends a morning brief across your connected apps, takes on tasks, keeps working when you step away and checks with you before it acts for you. Built on Claude Opus 5.5, by waitlist, starting with US Pro and Max plans. Announced 21 Oct, more than 540 points and 500 comments on Hacker News. Our [waitlist guide](/blog/claude-duet-waitlist/) covers who gets in first. [Source](https://claude.com/resources/articles/claude-duet), and [Anthropic's post on X](https://x.com/claudeai/status/2112984664256241837) +2. **OpenAI CLI.** The official command line tool for the OpenAI REST API: generate images, upload files and run batches from a shell script. Its README says to use Codex CLI instead when you want a coding agent to work on a project. More than 230 points on Hacker News on 22 Oct, v1.40.5 on 23 Oct, Apache 2.0, more than 1,050 stars. [Source](https://github.com/openai/openai-cli) +3. **Omnara.** A Show HN for an open source managed agent platform and a self hosted alternative to Claude Managed Agents and OpenAI's Agents API, with tools, secrets and permissions built in. 110 points on Hacker News on 21 Oct, a release on 23 Oct, Apache 2.0, more than 3,150 stars. [Source](https://github.com/omnara-ai/omnara) + +## Top 3 skills, MCP servers and plugins + +1. **Co-op Anything.** An MCP server and playbook: point your coding agent at a single player PC game and it reverse engineers the game, builds the multiplayer, tests it with bots inside the game and packages it for friends to join over Steam. Created 16 Oct. MIT, more than 840 stars on 23 Oct. [Source](https://github.com/cranebeaker/coop-anything) +2. **NS game Apple ARM port.** An instruction skill, written in Chinese, for porting Nintendo Switch games to Apple silicon on macOS, iPadOS and iOS with a Rust compatibility layer and Metal. It asks for real run evidence before a port counts as done. Created 18 Oct. MIT, more than 470 stars on 23 Oct. [Source](https://github.com/realskyrin/ns-game-apple-arm-port) +3. **Archestra 1.4.** A gateway for LLM calls and MCP servers that gives Claude Code, Codex and Cursor one token, with cost limits and guardrails. Version 1.4 swaps in OpenAPPA guardrails and adds a remote agent runtime that takes work off your laptop and hands it back; v1.4.2 followed on 23 Oct. AGPL 3.0 or an enterprise licence, more than 4,350 stars. [Source](https://github.com/archestra-ai/archestra/releases/tag/platform-v1.4.0) + +## Top 3 moves from multi agent tools + +1. **GitHub Copilot CLI 1.0.103.** Published 22 Oct. ACP sessions now honour saved sandbox settings and the `--sandbox` flags, including on resume, cloud replies keep their full text after a dropped connection, and cached MCP tool lists are shared across Git worktrees of one repository. See [Copilot CLI vs Claude Code](/blog/github-copilot-cli-vs-claude-code-for-hive-work/). [Source](https://github.com/github/copilot-cli/releases/tag/v1.0.103) +2. **OpenClaw 2026.10.6.** Published 22 Oct, a patch with "focused reliability fixes only" across 293 pull requests. It now requires a patched Node runtime, 24.22.1 or 26.12.1 and up, which rejects TLS chains signed with the certificate keys exposed in the October Node security releases, and `openclaw update` refuses to install on an older Node. Compare it in [Hermes Agent vs OpenClaw](/blog/hermes-agent-vs-openclaw/). [Source](https://github.com/openclaw/openclaw/releases/tag/v2026.10.6) +3. **OpenCode v1.19.4.** Published 22 Oct. It bundles Bun 1.4.4, which carries security fixes for Brotli, TLS, HTTP, WebSocket and `fetch`, and updates root certificates. [Source](https://github.com/anomalyco/opencode/releases/tag/v1.19.4) + +Also out: [Claude Code v2.1.308](https://github.com/anthropics/claude-code/releases/tag/v2.1.308), published 22 Oct, with notes that say only "Bug fixes and reliability improvements." + +## Top 3 rising repos + +1. **earshot.** "Ears for coding agents": it turns sound into evidence an agent can reason about, and lets an agent make sound effects in code. Runs locally on CPU as a CLI, a Python API and an MCP server. Created 15 Oct. MIT, more than 2,200 stars on 23 Oct. [Source](https://github.com/Leonxlnx/earshot) +2. **PandaCode.** A native desktop workspace that runs Claude Code, Codex, OpenCode, Copilot CLI and other agent CLIs side by side, with a task board and an architect with up to three workers. Built on Tauri, with no account and no telemetry. Created 18 Oct. More than 500 stars on 23 Oct under the PandaCode License, MIT with a rule that public or commercial forks must say so. [Source](https://github.com/aicodeking/pandacode) +3. **OpenGCUA.** Tongyi's open stack for generalist computer use agents that read the accessibility tree, call app APIs and run shell commands as well as click, with a paper and released trajectories from eight models. Created 15 Oct. Apache 2.0, more than 430 stars on 23 Oct. [Source](https://github.com/Tongyi-MAI/OpenGCUA) + +New repos can gain stars quickly for reasons other than use, so treat these as names to watch, not recommendations. + +## Top 3 things people are arguing about + +1. **Should an agent sandbox send telemetry by default?** Nvidia's OpenShell runs each agent in an isolated sandbox, checks every file access and network call against a policy, adds credentials only to requests bound for approved endpoints, and uses formal verification to hold risky policy changes for review. Its README also says it collects anonymous operational telemetry unless you turn it off, and that drew the sharpest replies. More than 320 points on Hacker News on 22 Oct; v0.1.5 shipped 20 Oct. Our [sandboxing guide](/blog/agent-security-and-sandboxing/) covers the wider choice. [Source](https://github.com/NVIDIA/OpenShell) +2. **Can Claude prove code safe, or only the model of it?** Anthropic and Theorem report that Claude proved security properties at the RISC V assembly level in more than 100 open source libraries and found bugs that years of testing missed. Replies asked how anyone knows the proof matches the real program, and whether agents can scale the method past small libraries. More than 180 points on Hacker News on 22 Oct. [Source](https://www.anthropic.com/research/formal-verification) +3. **Why every model gives the same "controversial" take.** ModelCompare asked 12 models from ten labs for their most controversial take on humans, three times each. All of them said at least once that people pick what their group believes and then invent the reasons; 7 said it every time. More than 270 points and 320 comments on Hacker News on 22 Oct. [Source](https://www.modelcompare.ai/blog/ai-models-controversial-take-on-humans) + +## How this list is built + +Each day our agents read GitHub releases and new repos, vendor posts, official accounts on X and the Hacker News front page about coding agents. A name only makes a list with a link that was opened and a signal that can be shown: points, stars or a dated release. Repos are checked against the GitHub API before they appear, and a claim that exists only as a social post is left out. New here? Start with the [install guide](/blog/how-to-install-and-use-munder-difflin/). + + diff --git a/blog/src/posts/ai-agent-office-github.md b/blog/src/posts/ai-agent-office-github.md new file mode 100644 index 0000000000..42071d7794 --- /dev/null +++ b/blog/src/posts/ai-agent-office-github.md @@ -0,0 +1,108 @@ +--- +title: "Open Source AI Agent Office Apps on GitHub (Oct 2026)" +description: "Open source AI agent office apps on GitHub, checked 23 Oct 2026: ChatDev, Munder Difflin, OpenHands, CrewAI, AG2 and three more, with licences." +date: 2026-10-23 +category: comparisons +categoryLabel: Comparisons +type: Non-technical +primaryKeyword: "ai agent office github" +secondaryKeywords: ["agent office github", "ai agent office", "open source ai agent office", "ai office agents open source", "virtual office ai agents github"] +tags: ["Comparisons", "Open Source", "AI Agents"] +faq: + - q: "What is an AI agent office?" + a: "A desktop or web app that runs several AI agents as one team. Each agent has a role, the tasks sit on a shared board, and the agents pass messages to each other and to you. The work can be code, research, writing or any other knowledge work." + - q: "Is there a free, open source AI agent office app?" + a: "Yes. All eight projects on this list carry an open source licence on GitHub, checked on 23 Oct 2026. The software is free; you still pay for the model behind it, unless you run a local one. AgentOffice, for example, runs on local models through Ollama." + - q: "Is MetaGPT still maintained?" + a: "Not actively, as far as GitHub shows. On 23 Oct 2026 its last commit was from 21 Jan 2026 and its last release, v0.8.1, was from April 2024. The code is still MIT licensed and usable." + - q: "What happened to AutoGen?" + a: "Microsoft's AutoGen README now says the project is archived and no longer maintained, and points new users to Microsoft Agent Framework. AG2, which describes itself as formerly AutoGen, is still active and Apache 2.0 licensed." + - q: "Can an AI agent office do work that is not coding?" + a: "Yes, if the app is built for it. ChatDev 2.0 describes itself as a platform for developing everything, and Munder Difflin runs agents for all kinds of knowledge work, coding included. OpenHands, Pixel Agents and Claude Squad are built around coding agents." +--- + +An AI agent office is an app that runs several AI agents as one team, with roles, a shared board and messages. The open source ones on GitHub worth a look are ChatDev, [Munder Difflin](https://harnessmd.com/download), OpenHands, CrewAI, AG2, Pixel Agents, AgentOffice and Claude Squad. Checked 23 Oct 2026. + +Licences and dates come from GitHub, read on 23 Oct 2026. This list sits in our [Comparisons hub](/blog/topics/comparisons/), next to our wider pick of [open source AI agents](/blog/open-source-ai-agents/). + +## What is an AI agent office app? + +It is a desktop or web app where several AI agents work as one team on your jobs. Three parts make it an office rather than a chat window: + +- **Roles.** Each agent has a job, such as researcher, writer or reviewer. +- **A shared board.** Tasks sit where you and every agent can see them. +- **Messages.** Agents hand work to each other and ask you when they are stuck. + +The work does not have to be code; research, reports and spreadsheets fit the same shape. Our [multi agent harness explainer](/blog/what-is-a-multi-agent-harness/) covers the idea, and [Run an office of AI agents](/blog/run-an-office-of-ai-agents/) shows a working day. + +
Animation. On a white desk, three stacked trays are labelled Researcher, Writer and Reviewer. A corkboard labelled Shared board holds cards that slide from a To do column to a Done column. A row of pigeonholes labelled Messages fills and empties as small envelopes pass between the trays.
The three parts of an AI agent office: roles, a shared board and messages.
+ +## Which open source agent office apps are on GitHub? + +Eight projects pass three tests: an open source licence, a commit in the last three months, and a README that describes several agents working together. + +**1. ChatDev.** [ChatDev](https://github.com/OpenBMB/ChatDev) from OpenBMB began as a "Virtual Software Company" with agents such as CEO, CTO and Programmer. Its README calls version 2.0, released on 7 Jan 2026, "a Zero-Code Multi-Agent Platform for Developing Everything", run from a web console. Apache 2.0. Best for: designing an agent team without code. + +**2. Munder Difflin.** This is what we make. [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. MIT licence, code at [HarnessMD/munder-difflin](https://github.com/HarnessMD/munder-difflin). A Cloud Office and a mobile app are coming soon. The [install guide](/blog/how-to-install-and-use-munder-difflin/) goes from download to a first agent. Best for: a ready office for technical and non technical work. + +**3. OpenHands.** [OpenHands](https://github.com/OpenHands/OpenHands) now leads its README with Agent Canvas, which "turns your coding agents into a self-hosted, always-on engineering team". It runs its own agent or Claude Code, Codex, Gemini, Pi and OpenCode, on a laptop or a server. MIT. Best for: a coding team that keeps working with your laptop shut. + +**4. CrewAI.** [CrewAI](https://github.com/crewAIInc/crewAI) is "an open-source Python framework" for multi agent workflows, with "role-based AI agents". It is a library you code against, not an app; a paid AMP Suite adds a control plane. MIT. Best for: Python developers building their own office. + +**5. AG2.** [AG2](https://github.com/ag2ai/ag2), formerly AutoGen, is "an open-source programming framework for building AI agents and facilitating cooperation among multiple agents". Apache 2.0. Best for: AutoGen style teams that still get updates. + +**6. Pixel Agents.** [Pixel Agents](https://github.com/pixel-agents-hq/pixel-agents) turns the coding agents in your terminals into "animated pixel-art characters working in a tiny office", in VS Code or a browser. Claude Code is the agent it supports today, and its README lists a task board as a plan: "Most of this is still ahead." MIT, v1.5.0 on 17 Oct 2026. Best for: seeing which agent is busy. + +**7. AgentOffice.** [AgentOffice](https://github.com/harishkotra/agent-office) promises "Self-growing AI teams in a pixel-art virtual office" on local models through Ollama. Agents talk, take tasks from a TaskBoard and can hire new team members on their own, which is more autonomy than most interns get. MIT, last commit 25 Sep 2026. Best for: tinkering with a local, visual team. + +**8. Claude Squad.** [Claude Squad](https://github.com/smtg-ai/claude-squad) is "a terminal app that manages multiple" agents such as Claude Code, Codex and Gemini, each task in "its own isolated git workspace". No roles or board; it is the lean option. AGPL 3.0, v1.0.20 on 20 Aug 2026. See [Claude Squad vs Munder Difflin](/blog/claude-squad-vs-munder-difflin/). + +
Animation. Eight plain boxes slide one by one onto a long white shelf, labelled ChatDev, Munder Difflin, OpenHands, CrewAI, AG2, Pixel Agents, AgentOffice and Claude Squad. As each box lands, a small tag drops onto it with its licence: Apache 2.0, MIT or AGPL 3.0.
Eight open source agent office projects, licences checked on 23 Oct 2026.
+ +## Agent office apps compared, checked 23 Oct 2026 + +| Project | Kind | Licence | Latest activity | +| --- | --- | --- | --- | +| ChatDev | Web app | Apache 2.0 | Commit 16 Oct 2026 | +| Munder Difflin | Desktop app | MIT | Commit 22 Oct 2026 | +| OpenHands | Self hosted app | MIT | Commit 23 Oct 2026 | +| CrewAI | Python framework | MIT | Commit 23 Oct 2026 | +| AG2 | Python framework | Apache 2.0 | Commit 22 Oct 2026 | +| Pixel Agents | Viewer | MIT | v1.5.0, 17 Oct 2026 | +| AgentOffice | Browser app | MIT | Commit 25 Sep 2026 | +| Claude Squad | Terminal app | AGPL 3.0 | v1.0.20, 20 Aug 2026 | + +Licences come from the GitHub API, dates from commit histories and releases. The kinds are our labels. + +## Is an agent framework the same as an agent office app? + +No. A framework is a library you write code against; an office app is something you open and use. ChatDev, Munder Difflin, OpenHands and AgentOffice are apps you run. CrewAI and AG2 are what you would build one with. Our view: if you do not want to write Python, start with an app. [How to choose a multi-agent tool](/blog/how-to-choose-a-multi-agent-tool/) has a checklist, and [CrewAI and AutoGen vs a local agent harness](/blog/crewai-autogen-vs-a-local-agent-harness/) covers the split. + +
Animation. Three white bins stand in a row, labelled Apps you run, Frameworks you code and Viewers you watch. Name cards drop from above: ChatDev, Munder Difflin and OpenHands land in Apps you run, CrewAI and AG2 in Frameworks you code, and Pixel Agents in Viewers you watch.
Apps you run, frameworks you code against, and a viewer, as their READMEs described them on 23 Oct 2026.
+ +## Which projects did we leave out, and why? + +Six well known names failed the check on 23 Oct 2026: + +- **MetaGPT.** MIT and famous for its AI software company, but its last commit was 21 Jan 2026 and its last release, v0.8.1, is from April 2024. +- **AutoGen.** Its [README](https://github.com/microsoft/autogen) says "Project archived and no longer maintained" and points to Microsoft Agent Framework. AG2 carries the AutoGen line on. +- **AI Town.** MIT, but it is "a virtual town where AI characters live, chat and socialize". Nobody files a report. +- **Claw Empire** and **Star Office UI.** Pixel offices with no commit since March 2026; GitHub could not match Star Office UI's licence to a standard one. +- **AionUi.** Apache 2.0 and busy, but we read it as a cowork app with many agents in one window, not a team with roles and a board. + +
Animation. A row of white desk lamps sits on a long table. Lamps labelled MetaGPT, last commit 21 Jan 2026, and AutoGen, archived, dim to grey one after the other. A card labelled AG2 slides under the AutoGen lamp and its bulb glows blue again.
MetaGPT has had no commit since 21 Jan 2026 and AutoGen's README says it is archived, checked on 23 Oct 2026.
+ +## Which agent office app should you pick? + +Pick by whether you want to write code and what work the team does. + +- **ChatDev** to design an agent team without code. +- **Munder Difflin** for a ready desktop office for any knowledge work, coding included. +- **OpenHands** for an always on coding team on a server. +- **CrewAI** or **AG2** to build your own in Python. +- **Pixel Agents** or **AgentOffice** to watch agents as pixel art. +- **Claude Squad** for parallel coding sessions in the terminal. + +For Claude Code in particular, see [the best Claude Code multi agent tools](/blog/best-claude-code-multi-agent-tools/). + + diff --git a/blog/src/posts/black-october-php2shell.md b/blog/src/posts/black-october-php2shell.md new file mode 100644 index 0000000000..635850f16f --- /dev/null +++ b/blog/src/posts/black-october-php2shell.md @@ -0,0 +1,95 @@ +--- +title: "Black October: What PHP2Shell Is and How to Patch (Oct 2026)" +description: "Black October is the scramble around PHP2Shell, two critical PHP flaws fixed on 21 Oct 2026. Who found them, the fixed versions and how to patch." +date: 2026-10-23 +category: news +categoryLabel: News +type: Technical +primaryKeyword: "black october php2shell" +secondaryKeywords: ["php2shell", "cve-2026-109618", "cve-2026-109619", "php vulnerability october 2026", "php 8.4.27", "black october cyber"] +tags: ["Security", "Open Source"] +faq: + - q: "Which PHP version fixes PHP2Shell?" + a: "PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, all released on 21 Oct 2026, according to php.net. PHP 8.1 and older get no release from the PHP project; some distributions and long term support vendors backport the fix." + - q: "Is my WordPress site affected by PHP2Shell?" + a: "It can be, because the flaws are in PHP itself, not in WordPress. The WordPress project said on 21 Oct 2026 that no WordPress update is required or planned, and that you can see your PHP version under Tools > Site Health > Info > Server." + - q: "Did AI find PHP2Shell?" + a: "AI helped. The PHP Foundation says Calif reported the root cause on 11 Aug 2026, OpenAI reported it independently, and Wiz added attack vectors on 24 Aug. Wiz says frontier models made building the exploit practical. A PHP core developer first spotted signs of the problem in 2024." + - q: "Is PHP2Shell being exploited?" + a: "Attempts are widespread. Cloudflare's CEO reported millions of attempts, and AWS saw one as early as 17 Oct 2026. The PHP Foundation told Wired that, to its knowledge, no attempt at remote code execution had succeeded." + - q: "Why is it called Black October?" + a: "Axios reported on 22 Oct 2026 that security professionals were calling the recent series of attacks Black October because of their volume and risk. PHP2Shell is the best known item in it, but not the only one." +--- + +Black October is the name some security people gave this month's run of hacks, bugs and emergency patches, with PHP2Shell at its centre: two critical flaws in PHP itself, CVE-2026-109618 and CVE-2026-109619, fixed on 21 Oct 2026. If you run PHP, update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Checked 23 Oct 2026. + +Every source below was checked on 23 Oct 2026. We first flagged the story in our [22 Oct brief](/blog/agent-tools-today-2026-10-22/). + +## What is Black October? + +It is a nickname for a bad few weeks, not one bug. [Axios](https://www.axios.com/2026/10/22/php-vulnerability-security-update-websites) (we read its [Yahoo copy](https://www.yahoo.com/news/world/articles/black-october-why-bugs-discovered-180131955.html)) reported on 22 Oct that cyber pros use the name "due to the sheer volume and the level of risk." [404 Media](https://www.404media.co/php2shell-black-october-ai-is-changing-everything-about-hacking-no-one-knows-what-happens-next/) wrote the same day that infosec experts took it up after PHP2Shell, alongside incidents at GitLab, Plex, Railway and OpenCode. Axios added that many organisations had so far avoided massive real world harm. + +
Animation. A white wall calendar shows October 2026. A yellow ring draws itself around the 21st, labelled Fixes released. A tag reading PHP2Shell, 2 CVEs, CVSS 9.2 slides in beside it, and the header of the page changes to read Black October.
PHP fixed CVE-2026-109618 and CVE-2026-109619, both rated CVSS 9.2, on 21 Oct 2026 (php.net).
+ +## What is PHP2Shell, in plain words? + +PHP2Shell is two memory bugs in the PHP engine that can let an attacker run code on a server without logging in. The [php.net advisory](https://www.php.net/archive/2026.php#2026-10-21-5) explains both. If a request hits PHP's memory limit, or another fatal error, at the wrong moment, PHP can leave half built data or a pointer to freed memory behind. The cleanup step, or the next request on the same worker, then trusts it. + +Both rate 9.2 on CVSS v4.0. Because the bug is in PHP, not the app, [Wiz](https://www.wiz.io/blog/php2shell-php-rce) says even a "Hello World" page can be vulnerable; it ran code on the latest WordPress, Drupal and Joomla container images. php.net puts mod_php at highest risk, with PHP-FPM, php-cgi, FrankenPHP and LiteSpeed also exposed. + +
Animation. On a white conveyor belt, a tray labelled Request 1 fills until a gauge labelled Memory limit reaches its top and the tray stops. It leaves a yellow sticky note labelled Stale state on the belt. The next tray, labelled Request 2, rolls up and carries the note away as if it were its own.
Both PHP2Shell bugs leave bad state behind after a fatal error, which the cleanup step or a later request then trusts, from php.net's advisory of 21 Oct 2026.
+ +## Who found it, and what did AI do? + +Several teams, close together. The [PHP Foundation](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/) says a PHP core developer spotted signs of the memory problem in 2024, but no exploit path turned up and the fix stalled. Calif began auditing PHP on 30 Jul 2026 under OpenAI's Patch the Planet program and reported remote code execution on 11 Aug; OpenAI independently reported the same root cause. Wiz added attack vectors on 24 Aug. + +Wiz says it used Gemini 4 Argon and GPT-5.6 Sol, and that frontier models made the exploit chain practical. [Calif's write up](https://calif.io/php2shell/) describes its agent tracing the bug through PHP's source. OpenAI, quoted by Axios: "To our knowledge, frontier AI found these vulnerabilities before attackers did." Our view: AI shortened the road from a known weak spot to a working exploit. People had noticed the weak spot two years earlier. + +## Which PHP versions are affected and fixed? + +Every supported branch got a fix on 21 Oct 2026. Wiz and Cloudflare say the flaws reach back to PHP 5.4. Our selection of rows from the php.net advisory and release notes: + +| Branch | Fixed release, 21 Oct 2026 | Note from php.net | +| --- | --- | --- | +| 8.5 | 8.5.12 | | +| 8.4 | 8.4.27 | | +| 8.3 | 8.3.36 | | +| 8.2 | 8.2.35 | End of life 31 Dec 2026 | +| 8.1, 8.0, 7.4 | None | Source backports only; ask your vendor | +| Older | None | Upgrade | + +## How did governments and sites respond? + +Fast, and often by switching things off. The PHP Foundation says the CVE ids leaked on 20 Oct with a wrong score of 10.0, so the release moved up a day to 21 Oct, 14:00 UTC. It says Chainguard broke the embargo by publishing patched images more than six hours early. + +Axios reports the NSA told operators to take sites down if they could not patch in time, and the UK's [NCSC](https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php) gave similar advice. Wikimedia took its Cloud Services offline on 20 Oct as a precaution. 404 Media says 4chan and Archive.org went down briefly too, and Axios counted dozens of sites in the US, UK and elsewhere. + +## Is anyone exploiting PHP2Shell? + +Attempts, yes. Confirmed breaches, not in any source we read. Cloudflare's CEO Matthew Prince posted, per 404 Media, that Cloudflare saw thousands of attempts in the 10 hours before the announcement, tens of thousands in the 45 minutes after, hundreds of thousands within 2 hours, then millions. [AWS](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/) logged a honeypot attempt on 17 Oct, four days early. The PHP Foundation's executive director told [Wired](https://www.wired.com/story/hackers-exploited-the-php-vulnerabilities-days-before-they-were-patched/) there had been "no successful attempt at remote code execution" to its knowledge. + +
Animation. A grey mechanical tally counter on a white desk clicks forward through four labelled stops: 10 hours before, thousands; 45 minutes after, tens of thousands; 2 hours after, hundreds of thousands; later, millions. A yellow tag under it reads Attempts, not confirmed breaches.
Exploit attempts Cloudflare saw around disclosure, as its CEO posted and 404 Media reported on 22 Oct 2026.
+ +## Do the public exploits even work? + +Many do not, says the loudest dissent. Elias Taylor [wrote on 21 Oct](https://eliastaylor.com/noexploit/) that public proofs of concept "do not work against a vanilla PHP instance", and that many need an existing file inclusion hole. PHP's security team also found no working one at release. Taylor still says patch. + +The other side: Wiz and Calif showed their own exploits on stock setups, and Trail of Bits' CEO posted that his firm built one in a day with Codex, 404 Media reported. Our view: a broken script on GitHub is no reason to wait. + +## What should you do now? + +Update PHP today, then check you were not hit first. + +1. Run `php -v` on servers you manage. In WordPress, look under Tools > Site Health > Info > Server, as the [WordPress project](https://wordpress.org/news/2026/10/critical-php-security-release/) says. +2. Update to the fixed release for your branch. php.net says distributions and LTS vendors got the patch early, so check your vendor's packages, then restart PHP. +3. On managed hosting, ask your host. [Cloudflare](https://blog.cloudflare.com/php-vulnerabilities-waf-protection) says updating site code or plugins alone does not fix this; WordPress plans no update. +4. On 8.1 or older, upgrade. WordPress says that if you manage your own hosting, consider taking the server offline until you can; the NCSC says the same for any system you cannot patch or mitigate in time. +5. Cannot patch today? php.net lists interim settings such as `memory_limit=-1` and `session.upload_progress.enabled=0`, which reduce exposure but do not make you safe. +6. Cloudflare's WAF rules have protected free and paid WAF users by default since 03:02 UTC on 21 Oct; paid plans should check the Managed Ruleset is in block mode. Keep them on while you patch, not instead. +7. Look back from at least 17 Oct, as AWS advises. Wiz lists signs: odd multipart requests followed by memory limit errors or repeated HTTP 500s, and malformed PHPSESSID cookies. + +
Animation. A white clipboard holds a four line checklist: Run php -v, Update to the fixed release, Keep the WAF on, Check logs from 17 Oct. A green tick lands in each box in turn, and a small yellow note at the bottom reads Plugins alone do not fix it.
What php.net, WordPress, Cloudflare and AWS told site owners to do, 21 Oct 2026.
+ +If you look after many servers, agents in a [Munder Difflin](https://harnessmd.com/download) office (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) can be pointed at keeping that list patched; see our [install guide](/blog/how-to-install-and-use-munder-difflin/). Before giving agents server access, read our [security guide for AI coding agents](/blog/security-for-ai-coding-agents/) and [running AI agents safely](/blog/agent-security-and-sandboxing/). + + diff --git a/blog/src/posts/claude-duet-app.md b/blog/src/posts/claude-duet-app.md new file mode 100644 index 0000000000..deb468e61d --- /dev/null +++ b/blog/src/posts/claude-duet-app.md @@ -0,0 +1,98 @@ +--- +title: "Claude Duet App: iPhone, Android and Web, and How to Get It" +description: "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." +date: 2026-10-23 +category: guides +categoryLabel: Guides +type: Non-technical +primaryKeyword: "claude duet app" +secondaryKeywords: ["claude duet download", "claude duet ios", "claude duet android", "claude duet desktop", "claude duet apply", "claude duet access"] +tags: ["Guides", "AI Agents"] +faq: + - q: "Is there a Claude Duet app for Mac or Windows?" + a: "Anthropic does not name one. Its Claude Duet product page says that on a computer you open Duet on the web at claude.ai. That was the wording when we checked on 23 Oct 2026." + - q: "Where do I download the Claude Duet app?" + a: "Through your invitation. Anthropic's help article says to open the invitation email on your phone and follow the setup steps for iPhone or Android, using the Apple or Google account linked to the email you gave when you joined the waitlist." + - q: "Is the Claude Duet app the same as the Claude app?" + a: "No. Anthropic's announcement says that for now Claude Duet lives in its own app, separate from the main Claude app. It uses the same Claude account, including memory, skills and connectors." + - q: "Can I apply for Claude Duet outside the US?" + a: "Not yet. Anthropic's help article lists a Pro or Max plan, a US Claude account and an invitation from the waitlist. Anthropic gives no date for other countries." + - q: "Does the Claude Duet app cost extra?" + a: "Not during the research preview. Anthropic says Claude Duet is included in Pro and Max plans and shares your plan's usage limits with the rest of Claude, and its background checks count toward those limits." +--- + +Yes, there is a Claude Duet app: a standalone app for iOS and Android, and on a computer you open it on the web at claude.ai. To get it you need a Pro or Max plan in the US and an invitation from the waitlist. Checked 23 Oct 2026. + +Claude Duet is Anthropic's proactive agent app, in research preview since 21 Oct 2026. Our [explainer](/blog/what-is-claude-duet/) covers what it does; this page covers the app itself. Sources: Anthropic's [product page](https://claude.com/product/claude-duet), its [announcement](https://claude.com/resources/articles/claude-duet) and its help article [Get started with Claude Duet](https://support.claude.com/en/articles/17631495-get-started-with-claude-duet), all opened on 23 Oct 2026. + +## Is there a Claude Duet app? + +Yes. The product page says, word for word: "Claude Duet is a standalone app for iOS and Android." The announcement of 21 Oct 2026 puts it the same way: "It's available as a standalone app on iOS and Android, and on claude.ai." + +It is a separate app, not a tab inside the Claude app you may already have. The announcement says: "For now, Claude Duet lives in its own app, separate from the main Claude app." It still runs on your Claude account. The product page says Duet uses "your Claude account, including memory, skills, and connectors." + +
Animation. Two hooks on a white wall are labelled Claude app and Claude Duet. A key ring labelled Your Claude account, carrying three tags for Memory, Skills and Connectors, swings from the first hook to the second. Both hooks stay on the wall, side by side.
Claude Duet is its own app, separate from the main Claude app, on the same Claude account (Anthropic, 21 Oct 2026).
+ +## Which phones does it run on? + +iPhone and Android. Those are the only two phones Anthropic names, and the help article asks you to choose "your phone type (iPhone or Android)" when you join the waitlist. Anthropic's Duet pages do not list minimum iOS or Android versions, so we will not guess them. + +| Platform, checked 23 Oct 2026 | What Anthropic says | +| --- | --- | +| iPhone (iOS) | Standalone app | +| Android | Standalone app | +| Any computer | "open it on the web at claude.ai" | +| Mac or Windows app | Not named on Anthropic's Duet pages | +| Minimum iOS or Android version | Not listed | + +The first three rows are Anthropic's own words; the last two are our selection of things readers ask about that Anthropic does not cover. + +
Animation. Four stands sit on a white shelf. A small lamp lights over each in turn: a phone stand labelled iPhone, a phone stand labelled Android and a laptop stand labelled Web at claude.ai. The fourth stand, labelled Mac or Windows app, stays empty with a card reading Not named by Anthropic.
Where Claude Duet runs, from Anthropic's product page on 23 Oct 2026.
+ +## Can I use Claude Duet on a Mac or PC? + +Yes, in a browser. The product page says: "On a computer, you can open it on the web at claude.ai." We read that as any computer with a browser, Mac or Windows alike, since it is a web page rather than an install. Anthropic does not name a Mac or Windows download for Duet, and on 23 Oct 2026 the Duet banner on Claude's [download page](https://claude.com/download) only led to the waitlist. + +Your computer does not have to stay on, either. The product page says that when you hand it a task, "it works on its own computer in the cloud, using apps you connect." The announcement adds that its virtual machine means it "doesn't require your laptop or phone to be on." + +## How do I download it? + +Through your invitation, not a store search. Anthropic's help article gives this order: + +1. Open the [Claude Duet page](https://claude.com/product/claude-duet), press Join the waitlist, and enter your name and the email on your Claude account. +2. Choose your Claude plan and your phone type, iPhone or Android. +3. Wait for the email. "Invitations go out in waves, so joining the waitlist doesn't guarantee a specific date." +4. When it is your turn, "open the invitation email on your phone and follow the setup steps for iPhone or Android", using "the Apple or Google account linked to the email address you gave us." +5. Open Claude Duet and sign in with your Claude account. It "can take a few minutes for the app to become available after you accept the invitation." + +Anthropic's Duet pages link no App Store or Google Play listing, so we do not link one either. Our view: if a store search turns up an app called Duet without an invitation, check who made it before you install anything. + +
Animation. A parcel labelled Invitation email lands on a doormat and its lid lifts to show a tag reading iPhone or Android. A key labelled Your Claude account turns in a lock labelled Sign in. A small sand timer labelled A few minutes runs out, and a desk lamp labelled Ready switches on.
From invitation to first sign in, as Anthropic's help article described it on 23 Oct 2026.
+ +## Who can get it? + +US subscribers on Pro or Max, once invited. The help article lists three things you need: a Pro or Max plan, a US Claude account and an invitation from the waitlist. The product page adds: "Team and Enterprise plans are not included yet." Free plans are not on the list. + +Anthropic says it built Duet "for professionals and small business owners with more on their plates than they can track." Our [waitlist guide](/blog/claude-duet-waitlist/) covers the wait itself: what Anthropic says about timing (very little) and what to set up before your invite arrives. + +## Does the app cost extra? + +No, not during the research preview. The announcement says "Claude Duet is included in Pro and Max plans," and the product page says it "shares your plan's usage limits with the rest of Claude." The catch is in the next line: "Its background checks count toward those limits," and larger tasks, "like building a financial model or a deck, require more model turns and background work." So there is no extra charge, but Duet spends the same allowance as your chats. + +
Animation. One glass jar labelled Your plan's usage limits sits on a white table. Three spoons labelled Chats, Background checks and Large tasks dip in and lift out scoops in turn, and the level drops a little each time. A price tag on the lid labelled Extra charge flips over to read None in the preview.
Claude Duet is included in Pro and Max and shares your plan's usage limits during the preview, from Anthropic's pages on 23 Oct 2026.
+ +## What does the app do once you are in? + +It messages you first. You talk to it "through text or voice", the product page says it is "Powered by Opus 5.5", and the announcement says it "messages you only when something needs your attention." Before it sends a message, makes a payment, signs a document or deletes anything, "it shows you exactly what it's about to do and waits for your approval." The [explainer](/blog/what-is-claude-duet/) covers the morning brief and the approval rules in full. + +## What if you cannot get it yet? + +Use something with no waitlist. If you are outside the US, on a Team plan, or simply still waiting, three places to start: + +1. [Claude Duet alternatives](/blog/claude-duet-alternatives/): our list of proactive agents you can use today. +2. [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 Cloud Office and a mobile app are coming soon. Setup is in our [install guide](/blog/how-to-install-and-use-munder-difflin/). Unlike Duet, it runs on your computer, so that computer has to be on while it works. +3. [Build your own Claude Duet](/blog/build-your-own-claude-duet/): a daily brief you set up yourself. + +More guides like this one sit on our [guides page](/blog/topics/guides/). + + diff --git a/blog/src/posts/open-source-ai-agents.md b/blog/src/posts/open-source-ai-agents.md index adc4e580fe..9d95f0fd84 100644 --- a/blog/src/posts/open-source-ai-agents.md +++ b/blog/src/posts/open-source-ai-agents.md @@ -94,7 +94,7 @@ Most top results list frameworks, and some have aged. As of 1 Oct 2026, Modal's ## Which open source AI agent should you pick? -Match the agent to the job. +Match the agent to the job. For agent teams, see [agent office apps on GitHub](/blog/ai-agent-office-github/). * Pick **OpenClaw** if you want one assistant in your chat apps. * Pick **Munder Difflin** if you want several named agents with roles, each on the CLI you already use. diff --git a/blog/src/posts/what-is-claude-duet.md b/blog/src/posts/what-is-claude-duet.md index 3a2ee082fa..05ff061471 100644 --- a/blog/src/posts/what-is-claude-duet.md +++ b/blog/src/posts/what-is-claude-duet.md @@ -39,6 +39,8 @@ Claude Duet is Anthropic's new app in which Claude acts as a proactive agent. An | Where | Standalone app on iOS and Android, and on claude.ai | | Apps it can use | "over 6,000 connectors and plugins in the Claude Marketplace" | +For the phones it runs on, using it on a computer and how the download works, see our [Claude Duet app guide](/blog/claude-duet-app/). + ## How does Claude Duet work? It starts from your Claude account. Your memory, skills and connected apps carry over, and you talk to Duet in one ongoing thread, by typing or by voice. Each morning it sends a short brief on the most important items across your connected apps. During the day it checks those apps every few hours and messages you only when something needs you. Anthropic says it does not check nonstop "because checking nonstop would use up your plan's limits much faster," and you can tell it what you want to hear about and how often. diff --git a/docs/blog/agent-security-and-sandboxing/index.html b/docs/blog/agent-security-and-sandboxing/index.html index 0ccb5e3832..8ebed71313 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

- +
- + - 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.

+

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.

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

Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

-

How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

+

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.

Guides - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-03/index.html b/docs/blog/agent-tools-today-2026-10-03/index.html index 967ba82e7a..d2d85f3d7c 100644 --- a/docs/blog/agent-tools-today-2026-10-03/index.html +++ b/docs/blog/agent-tools-today-2026-10-03/index.html @@ -249,50 +249,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

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

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-05/index.html b/docs/blog/agent-tools-today-2026-10-05/index.html index 00f06ed94b..08bec70038 100644 --- a/docs/blog/agent-tools-today-2026-10-05/index.html +++ b/docs/blog/agent-tools-today-2026-10-05/index.html @@ -246,50 +246,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

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

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-07/index.html b/docs/blog/agent-tools-today-2026-10-07/index.html index 401512c466..048f3b5c34 100644 --- a/docs/blog/agent-tools-today-2026-10-07/index.html +++ b/docs/blog/agent-tools-today-2026-10-07/index.html @@ -247,50 +247,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

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

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-09/index.html b/docs/blog/agent-tools-today-2026-10-09/index.html index 8678c64b76..02d017552b 100644 --- a/docs/blog/agent-tools-today-2026-10-09/index.html +++ b/docs/blog/agent-tools-today-2026-10-09/index.html @@ -247,50 +247,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

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

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

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

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

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

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-13/index.html b/docs/blog/agent-tools-today-2026-10-13/index.html index 3fe004ce04..8f2fab2127 100644 --- a/docs/blog/agent-tools-today-2026-10-13/index.html +++ b/docs/blog/agent-tools-today-2026-10-13/index.html @@ -243,50 +243,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-15/index.html b/docs/blog/agent-tools-today-2026-10-15/index.html index f432ace596..cc6d6307ee 100644 --- a/docs/blog/agent-tools-today-2026-10-15/index.html +++ b/docs/blog/agent-tools-today-2026-10-15/index.html @@ -242,50 +242,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-16/index.html b/docs/blog/agent-tools-today-2026-10-16/index.html index a06f753838..7b06c0c8bf 100644 --- a/docs/blog/agent-tools-today-2026-10-16/index.html +++ b/docs/blog/agent-tools-today-2026-10-16/index.html @@ -241,50 +241,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-17/index.html b/docs/blog/agent-tools-today-2026-10-17/index.html index d820d831a3..411e2c6100 100644 --- a/docs/blog/agent-tools-today-2026-10-17/index.html +++ b/docs/blog/agent-tools-today-2026-10-17/index.html @@ -240,50 +240,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-18/index.html b/docs/blog/agent-tools-today-2026-10-18/index.html index b3d3f1c9f2..6175054ee4 100644 --- a/docs/blog/agent-tools-today-2026-10-18/index.html +++ b/docs/blog/agent-tools-today-2026-10-18/index.html @@ -240,50 +240,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-19/index.html b/docs/blog/agent-tools-today-2026-10-19/index.html index b12aa06eb5..104dfabe5a 100644 --- a/docs/blog/agent-tools-today-2026-10-19/index.html +++ b/docs/blog/agent-tools-today-2026-10-19/index.html @@ -240,50 +240,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 18 Oct 2026: LeanREA Takes On REA, FLUX 3 Image Klein, Hy Memory

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-21/index.html b/docs/blog/agent-tools-today-2026-10-21/index.html index 6e253cac88..f91391834e 100644 --- a/docs/blog/agent-tools-today-2026-10-21/index.html +++ b/docs/blog/agent-tools-today-2026-10-21/index.html @@ -240,50 +240,50 @@

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

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

-

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

+

Agent Tools Today, 23 Oct 2026: Claude Duet, OpenAI CLI, Nvidia OpenShell

+

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

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

Agent Tools Today, 19 Oct 2026: DeepSeek Search API, Sphinx, OpenClaw 2026.10.5

-

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

+

Black October: What PHP2Shell Is and How to Patch (Oct 2026)

+

Black October is the scramble around PHP2Shell, two critical PHP flaws fixed on 21 Oct 2026. Who found them, the fixed versions and how to patch.

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

Agent Tools Today, 18 Oct 2026: LeanREA Takes On REA, FLUX 3 Image Klein, Hy Memory

-

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

+

Agent Tools Today, 22 Oct 2026: Claude API 2026-10-21, LayerFS 1.0, PHP2Shell

+

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

News - Read → + Read →
diff --git a/docs/blog/agent-tools-today-2026-10-22/index.html b/docs/blog/agent-tools-today-2026-10-22/index.html index 99b10db0ba..258de42e96 100644 --- a/docs/blog/agent-tools-today-2026-10-22/index.html +++ b/docs/blog/agent-tools-today-2026-10-22/index.html @@ -203,7 +203,7 @@

Top 3 rising repos Top 3 things people are arguing about

  1. How the LiteLLM release was poisoned. BerriAI’s incident report says the attacker used GitHub CLI OAuth tokens from three engineers, which it believes were read from the logs of an internal LiteLLM deployment open to the internet. It adds that in specific situations “Claude Code will embed a user’s GitHub CLI OAuth token into a response body.” SafeDep’s analysis of the release passed 450 points on Hacker News. Source
  2. -
  3. Is PHP2Shell as bad as it sounds? Wiz says frontier AI helped find remote code execution and memory disclosure flaws in core PHP, with nearly every version back to 5.4 hit by at least one; PHP shipped fixes on 21 Oct. Elias Taylor replied that there is no easy exploit and that the public proofs of concept “do not work against a vanilla PHP instance.” More than 430 and 240 points on Hacker News. Source
  4. +
  5. Is PHP2Shell as bad as it sounds? Wiz says frontier AI helped find remote code execution and memory disclosure flaws in core PHP, with nearly every version back to 5.4 hit by at least one; PHP shipped fixes on 21 Oct. Elias Taylor replied that there is no easy exploit and that the public proofs of concept “do not work against a vanilla PHP instance.” More than 430 and 240 points on Hacker News. Fixed versions are in our PHP2Shell guide. Source
  6. Claude shrank what strangers can reach in OpenSSH. Anthropic’s Frontier Red Team post of 20 Oct reports 493 issues across 25 open source projects with more than five million lines, using three attack surface methods. In OpenSSH, its fix removed roughly a fifth of the code an unauthenticated client could reach in a default build, shipped in 10.6. More than 130 points on Hacker News. Source

How this list is built

@@ -234,56 +234,56 @@

How this list is built + + + + + + + +
+
- -

- - - - - - - - -
-
-
+
diff --git a/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html b/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html index 25a15b51af..141e41fbaf 100644 --- a/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html +++ b/docs/blog/indian-open-source-ai-projects-vs-big-tech/index.html @@ -628,50 +628,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/kilo-code-vs-cline/index.html b/docs/blog/kilo-code-vs-cline/index.html index 2429b9cd4f..d594324d2f 100644 --- a/docs/blog/kilo-code-vs-cline/index.html +++ b/docs/blog/kilo-code-vs-cline/index.html @@ -334,50 +334,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/litellm-1-107-0-compromise/index.html b/docs/blog/litellm-1-107-0-compromise/index.html index 7794e7d712..864a089b05 100644 --- a/docs/blog/litellm-1-107-0-compromise/index.html +++ b/docs/blog/litellm-1-107-0-compromise/index.html @@ -386,50 +386,50 @@

FAQ

Related in Guides

- +
- + - 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.

+

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.

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

Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

-

How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

+

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.

Guides - Read → + Read →
diff --git a/docs/blog/local-first-vs-cloud-agent-sdks/index.html b/docs/blog/local-first-vs-cloud-agent-sdks/index.html index d830bfa88b..9366a9a0e5 100644 --- a/docs/blog/local-first-vs-cloud-agent-sdks/index.html +++ b/docs/blog/local-first-vs-cloud-agent-sdks/index.html @@ -368,50 +368,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/manage-multiple-claude-code-sessions/index.html b/docs/blog/manage-multiple-claude-code-sessions/index.html index 127e08f4e7..31e9eba8a0 100644 --- a/docs/blog/manage-multiple-claude-code-sessions/index.html +++ b/docs/blog/manage-multiple-claude-code-sessions/index.html @@ -324,50 +324,50 @@

FAQ

Related in Guides

- +
- + - 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.

+

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.

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

Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

-

How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

+

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.

Guides - Read → + Read →
diff --git a/docs/blog/mcp-and-skills-in-a-hive/index.html b/docs/blog/mcp-and-skills-in-a-hive/index.html index c8434228c6..0fb3b8569c 100644 --- a/docs/blog/mcp-and-skills-in-a-hive/index.html +++ b/docs/blog/mcp-and-skills-in-a-hive/index.html @@ -339,50 +339,50 @@

FAQ

Related in Guides

- +
- + - 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.

+

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.

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

Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

-

How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

+

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.

Guides - Read → + Read →
diff --git a/docs/blog/mcp-security-tool-poisoning/index.html b/docs/blog/mcp-security-tool-poisoning/index.html index 4a407fc3bb..4d59f0224b 100644 --- a/docs/blog/mcp-security-tool-poisoning/index.html +++ b/docs/blog/mcp-security-tool-poisoning/index.html @@ -328,50 +328,50 @@

FAQ

Related in Guides

- +
- + - 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.

+

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.

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

Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

-

How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

+

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.

Guides - Read → + Read →
diff --git a/docs/blog/meta-muse-alternatives/index.html b/docs/blog/meta-muse-alternatives/index.html index 0568a33761..f0b9410b9f 100644 --- a/docs/blog/meta-muse-alternatives/index.html +++ b/docs/blog/meta-muse-alternatives/index.html @@ -345,50 +345,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/meta-muse-grok-bot-privacy/index.html b/docs/blog/meta-muse-grok-bot-privacy/index.html index 7f660bd2e9..c407774306 100644 --- a/docs/blog/meta-muse-grok-bot-privacy/index.html +++ b/docs/blog/meta-muse-grok-bot-privacy/index.html @@ -332,50 +332,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/meta-muse-vs-grok-bot/index.html b/docs/blog/meta-muse-vs-grok-bot/index.html index 2d75c47b4c..d6d95e9fea 100644 --- a/docs/blog/meta-muse-vs-grok-bot/index.html +++ b/docs/blog/meta-muse-vs-grok-bot/index.html @@ -336,50 +336,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html b/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html index 5e89de9e1c..395762a624 100644 --- a/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html +++ b/docs/blog/munder-difflin-v0-2-4-feature-walkthrough/index.html @@ -304,50 +304,50 @@

FAQ

Related in Guides

- +
- + - 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.

+

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.

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

Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

-

How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

+

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.

Guides - Read → + Read →
diff --git a/docs/blog/munder-difflin-vs-chatgpt-dots/index.html b/docs/blog/munder-difflin-vs-chatgpt-dots/index.html index 7c83c2e77c..ef861b0039 100644 --- a/docs/blog/munder-difflin-vs-chatgpt-dots/index.html +++ b/docs/blog/munder-difflin-vs-chatgpt-dots/index.html @@ -358,50 +358,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/muse-code-vs-claude-code/index.html b/docs/blog/muse-code-vs-claude-code/index.html index c45d278888..0a91b8cf83 100644 --- a/docs/blog/muse-code-vs-claude-code/index.html +++ b/docs/blog/muse-code-vs-claude-code/index.html @@ -331,50 +331,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/omniagent-alternatives/index.html b/docs/blog/omniagent-alternatives/index.html index a38f864bef..5c36838410 100644 --- a/docs/blog/omniagent-alternatives/index.html +++ b/docs/blog/omniagent-alternatives/index.html @@ -307,50 +307,50 @@

FAQ

Related in Comparisons

- +
- + - 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 →
- +
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 →
- +
4 min
-

Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

-

Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

+

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.

Comparisons - Read → + Read →
diff --git a/docs/blog/open-source-ai-agents/index.html b/docs/blog/open-source-ai-agents/index.html index e2b7a4d617..b929a7a4d9 100644 --- a/docs/blog/open-source-ai-agents/index.html +++ b/docs/blog/open-source-ai-agents/index.html @@ -303,7 +303,7 @@

Which open sou

Why do other open source AI agent lists look different?

Most top results list frameworks, and some have aged. As of 1 Oct 2026, Modal’s page from September 2024 still leads with Devika and smol developer. AIMultiple, updated 16 Aug 2026, skips OpenClaw and Hermes Agent. Eesel’s list, edited 8 Sep 2026, is mostly frameworks and builders, including Flowise, which that page says was archived in August 2026.

Which open source AI agent should you pick?

-

Match the agent to the job.

+

Match the agent to the job. For agent teams, see agent office apps on GitHub.

  • Pick OpenClaw if you want one assistant in your chat apps.
  • Pick Munder Difflin if you want several named agents with roles, each on the CLI you already use.
  • @@ -374,50 +374,50 @@

    FAQ

    Related in Comparisons

    - +
    - + - 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 →
    - +
    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 →
    - +
    4 min
    -

    Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

    -

    Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

    +

    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.

    Comparisons - Read → + Read →
    diff --git a/docs/blog/openclaw-alternatives/index.html b/docs/blog/openclaw-alternatives/index.html index 23e488d1a4..e12f51e04a 100644 --- a/docs/blog/openclaw-alternatives/index.html +++ b/docs/blog/openclaw-alternatives/index.html @@ -359,50 +359,50 @@

    FAQ

    Related in Comparisons

    - +
    - + - 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 →
    - +
    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 →
    - +
    4 min
    -

    Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

    -

    Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

    +

    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.

    Comparisons - Read → + Read →
    diff --git a/docs/blog/opencode-alternatives/index.html b/docs/blog/opencode-alternatives/index.html index e9664a7ff4..2f205bc1dc 100644 --- a/docs/blog/opencode-alternatives/index.html +++ b/docs/blog/opencode-alternatives/index.html @@ -359,50 +359,50 @@

    FAQ

    Related in Comparisons

    - +
    - + - 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 →
    - +
    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 →
    - +
    4 min
    -

    Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

    -

    Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

    +

    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.

    Comparisons - Read → + Read →
    diff --git a/docs/blog/opencode-vs-claude-code/index.html b/docs/blog/opencode-vs-claude-code/index.html index d4c5a04fe4..fcfca07904 100644 --- a/docs/blog/opencode-vs-claude-code/index.html +++ b/docs/blog/opencode-vs-claude-code/index.html @@ -341,50 +341,50 @@

    FAQ

    Related in Comparisons

    - +
    - + - 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 →
    - +
    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 →
    - +
    4 min
    -

    Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

    -

    Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

    +

    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.

    Comparisons - Read → + Read →
    diff --git a/docs/blog/orca-vs-munder-difflin/index.html b/docs/blog/orca-vs-munder-difflin/index.html index c036c45f3b..5c8b02e8f5 100644 --- a/docs/blog/orca-vs-munder-difflin/index.html +++ b/docs/blog/orca-vs-munder-difflin/index.html @@ -346,50 +346,50 @@

    FAQ

    Related in Comparisons

    - +
    - + - 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 →
    - +
    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 →
    - +
    4 min
    -

    Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

    -

    Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

    +

    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.

    Comparisons - Read → + Read →
    diff --git a/docs/blog/prompt-caching-for-ai-agents/index.html b/docs/blog/prompt-caching-for-ai-agents/index.html index 84f531aba2..fe1ed2ba91 100644 --- a/docs/blog/prompt-caching-for-ai-agents/index.html +++ b/docs/blog/prompt-caching-for-ai-agents/index.html @@ -358,50 +358,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/qm-vs-munder-difflin/index.html b/docs/blog/qm-vs-munder-difflin/index.html index 7c8829f5f6..5cd2878476 100644 --- a/docs/blog/qm-vs-munder-difflin/index.html +++ b/docs/blog/qm-vs-munder-difflin/index.html @@ -357,50 +357,50 @@

    FAQ

    Related in Comparisons

    - +
    - + - 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 →
    - +
    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 →
    - +
    4 min
    -

    Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

    -

    Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

    +

    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.

    Comparisons - Read → + Read →
    diff --git a/docs/blog/run-a-mixed-engine-office/index.html b/docs/blog/run-a-mixed-engine-office/index.html index 56dd410171..89c3c80924 100644 --- a/docs/blog/run-a-mixed-engine-office/index.html +++ b/docs/blog/run-a-mixed-engine-office/index.html @@ -286,50 +286,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/run-ai-agent-hive-from-slack-setup/index.html b/docs/blog/run-ai-agent-hive-from-slack-setup/index.html index dd53aa6b70..eff5e421e7 100644 --- a/docs/blog/run-ai-agent-hive-from-slack-setup/index.html +++ b/docs/blog/run-ai-agent-hive-from-slack-setup/index.html @@ -385,50 +385,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/run-munder-difflin-on-a-mac-mini/index.html b/docs/blog/run-munder-difflin-on-a-mac-mini/index.html index f45dc14a9f..892cde3399 100644 --- a/docs/blog/run-munder-difflin-on-a-mac-mini/index.html +++ b/docs/blog/run-munder-difflin-on-a-mac-mini/index.html @@ -419,50 +419,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/run-munder-difflin-on-open-models/index.html b/docs/blog/run-munder-difflin-on-open-models/index.html index 55ebf7c474..7defedf6ac 100644 --- a/docs/blog/run-munder-difflin-on-open-models/index.html +++ b/docs/blog/run-munder-difflin-on-open-models/index.html @@ -535,50 +535,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/security-for-ai-coding-agents/index.html b/docs/blog/security-for-ai-coding-agents/index.html index dc151f9142..b101ad5c75 100644 --- a/docs/blog/security-for-ai-coding-agents/index.html +++ b/docs/blog/security-for-ai-coding-agents/index.html @@ -323,50 +323,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/small-team-ai-agents-playbook/index.html b/docs/blog/small-team-ai-agents-playbook/index.html index 1717df2c3c..6bd35a7386 100644 --- a/docs/blog/small-team-ai-agents-playbook/index.html +++ b/docs/blog/small-team-ai-agents-playbook/index.html @@ -328,50 +328,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/tags/ai-agents/index.html b/docs/blog/tags/ai-agents/index.html index eac2025edb..a617120829 100644 --- a/docs/blog/tags/ai-agents/index.html +++ b/docs/blog/tags/ai-agents/index.html @@ -105,11 +105,43 @@

    Tag

    #AI Agents

    -

    39 posts tagged AI Agents.

    +

    41 posts tagged AI Agents.

    -
    + + +
    diff --git a/docs/blog/tags/claude-code/index.html b/docs/blog/tags/claude-code/index.html index 5dfd6e7bfa..ded6f11e12 100644 --- a/docs/blog/tags/claude-code/index.html +++ b/docs/blog/tags/claude-code/index.html @@ -105,11 +105,27 @@

    Tag

    #Claude Code

    -

    99 posts tagged Claude Code.

    +

    100 posts tagged Claude Code.

    +
    diff --git a/docs/blog/tags/comparisons/index.html b/docs/blog/tags/comparisons/index.html index 766dc19337..496e63f133 100644 --- a/docs/blog/tags/comparisons/index.html +++ b/docs/blog/tags/comparisons/index.html @@ -105,11 +105,27 @@

    Tag

    #Comparisons

    -

    52 posts tagged Comparisons.

    +

    53 posts tagged Comparisons.

    +
    diff --git a/docs/blog/tags/daily-brief/index.html b/docs/blog/tags/daily-brief/index.html index 951b3670cc..a9ca129b12 100644 --- a/docs/blog/tags/daily-brief/index.html +++ b/docs/blog/tags/daily-brief/index.html @@ -105,11 +105,27 @@

    Tag

    #Daily Brief

    -

    18 posts tagged Daily Brief.

    +

    19 posts tagged Daily Brief.

    +
    diff --git a/docs/blog/tags/guides/index.html b/docs/blog/tags/guides/index.html index b10858d205..e5246347c1 100644 --- a/docs/blog/tags/guides/index.html +++ b/docs/blog/tags/guides/index.html @@ -105,11 +105,27 @@

    Tag

    #Guides

    -

    45 posts tagged Guides.

    +

    46 posts tagged Guides.

    +
    diff --git a/docs/blog/tags/mcp/index.html b/docs/blog/tags/mcp/index.html index 193b7ed05e..2534b4039f 100644 --- a/docs/blog/tags/mcp/index.html +++ b/docs/blog/tags/mcp/index.html @@ -105,11 +105,27 @@

    Tag

    #MCP

    -

    14 posts tagged MCP.

    +

    15 posts tagged MCP.

    +
    diff --git a/docs/blog/tags/open-source/index.html b/docs/blog/tags/open-source/index.html index a9c4896bec..2158c9b190 100644 --- a/docs/blog/tags/open-source/index.html +++ b/docs/blog/tags/open-source/index.html @@ -105,11 +105,59 @@

    Tag

    #Open Source

    -

    82 posts tagged Open Source.

    +

    85 posts tagged Open Source.

    + + +
    diff --git a/docs/blog/tags/security/index.html b/docs/blog/tags/security/index.html index 9e6c4a6a3a..d17ff743ed 100644 --- a/docs/blog/tags/security/index.html +++ b/docs/blog/tags/security/index.html @@ -105,11 +105,27 @@

    Tag

    #Security

    -

    14 posts tagged Security.

    +

    15 posts tagged Security.

    -
    + +
    diff --git a/docs/blog/the-lethal-trifecta-for-coding-agents/index.html b/docs/blog/the-lethal-trifecta-for-coding-agents/index.html index e296af92f4..76a4fb6eb9 100644 --- a/docs/blog/the-lethal-trifecta-for-coding-agents/index.html +++ b/docs/blog/the-lethal-trifecta-for-coding-agents/index.html @@ -346,50 +346,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/the-multi-agent-cost-playbook/index.html b/docs/blog/the-multi-agent-cost-playbook/index.html index 0932689aca..fd3f062de8 100644 --- a/docs/blog/the-multi-agent-cost-playbook/index.html +++ b/docs/blog/the-multi-agent-cost-playbook/index.html @@ -328,50 +328,50 @@

    FAQ

    Related in Guides

    - +
    - + - 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.

    +

    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.

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

    Claude Duet Waitlist: Who Gets In First, and What to Use Meanwhile

    -

    How to join the Claude Duet waitlist, who qualifies first (US Pro and Max), what Anthropic says about timing, and three things you can use while you wait.

    +

    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.

    Guides - Read → + Read →
    diff --git a/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html b/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html index 1401788162..b3be82cdb5 100644 --- a/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html +++ b/docs/blog/tmux-and-scripts-vs-an-agent-harness/index.html @@ -283,50 +283,50 @@

    FAQ

    Related in Comparisons

    - +
    - + - 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 →
    - +
    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 →
    - +
    4 min
    -

    Claude Duet vs Munder Difflin: One Agent in the Cloud or a Team on Your Computer

    -

    Claude Duet is one proactive agent in Anthropic's cloud. Munder Difflin gives you a free team of AI agents for all kinds of work. How they differ, side by side.

    +

    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.

    Comparisons - Read → + Read →
    diff --git a/docs/blog/topics/comparisons/index.html b/docs/blog/topics/comparisons/index.html index 706a9d0bd7..fa2b2cf0e8 100644 --- a/docs/blog/topics/comparisons/index.html +++ b/docs/blog/topics/comparisons/index.html @@ -114,18 +114,18 @@

    comparisons

    Honest side-by-sides with the other ways to

    +
    diff --git a/docs/blog/topics/concepts/index.html b/docs/blog/topics/concepts/index.html index d920c95f8c..80ab479101 100644 --- a/docs/blog/topics/concepts/index.html +++ b/docs/blog/topics/concepts/index.html @@ -114,18 +114,18 @@

    concepts

    The ideas underneath the product — harnesses,

    +
    diff --git a/docs/blog/topics/index.html b/docs/blog/topics/index.html index 02a54631c3..323849f6a6 100644 --- a/docs/blog/topics/index.html +++ b/docs/blog/topics/index.html @@ -113,19 +113,19 @@

    Topics

    - - + +
    -

    concepts

    -

    56 posts.

    +

    guides

    +

    57 posts.

    View topic →
    - - + +
    -

    guides

    +

    concepts

    56 posts.

    View topic →
    @@ -135,7 +135,16 @@

    guides

    comparisons

    -

    52 posts.

    +

    53 posts.

    +
    View topic →
    +
    +
    + + + +
    +

    news

    +

    20 posts.

    View topic →
    @@ -158,15 +167,6 @@

    story

    - - -
    -

    news

    -

    18 posts.

    -
    View topic →
    -
    -
    -
    @@ -200,19 +200,19 @@

    memory

    Tags - Claude Code99 + Claude Code100 Multi-Agent90 - Open Source82 + Open Source85 Concepts53 - Comparisons52 + Comparisons53 - Guides45 + Guides46 - AI Agents39 + AI Agents41 Internals26 @@ -232,17 +232,17 @@

    memory

    Engines19 - Daily Brief18 + Daily Brief19 Automation17 Release16 - MCP14 + MCP15 - Skills14 + Security15 - Security14 + Skills14 Tools12 diff --git a/docs/blog/topics/internals/index.html b/docs/blog/topics/internals/index.html index 1d1caeedae..4301d2ca22 100644 --- a/docs/blog/topics/internals/index.html +++ b/docs/blog/topics/internals/index.html @@ -114,18 +114,18 @@

    internals

    How the machine actually works: postmortems, a