Unified · Split
| @@ -5,6 +5,7 @@ import { unstable_readConfig } from "wrangler"; · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | import tailwindcss from "@tailwindcss/vite"; | 6 | import tailwindcss from "@tailwindcss/vite"; |
| 7 | import react from "@astrojs/react"; | 7 | import react from "@astrojs/react"; |
| 8 | import mdx from "@astrojs/mdx"; | ||
| 8 | 9 | ||
| 9 | import cloudflare from "@astrojs/cloudflare"; | 10 | import cloudflare from "@astrojs/cloudflare"; |
| 10 | 11 | ||
| @@ -29,7 +30,7 @@ const wranglerPublicDefine = () => { · 18 unmodified lines | |||
| 29 | export default defineConfig({ | 30 | export default defineConfig({ |
| 30 | site: "https://executor.sh", | 31 | site: "https://executor.sh", |
| 31 | output: "server", | 32 | output: "server", |
| 32 | integrations: [react()], | 33 | integrations: [react(), mdx()], |
| 33 | vite: { | 34 | vite: { |
| 34 | plugins: [tailwindcss()], | 35 | plugins: [tailwindcss()], |
| 35 | define: wranglerPublicDefine(), | 36 | define: wranglerPublicDefine(), |
| @@ -15,10 +15,11 @@ · 14 unmodified lines | |||
| 15 | }, | 15 | }, |
| 16 | "dependencies": { | 16 | "dependencies": { |
| 17 | "@astrojs/cloudflare": "^13.0.0", | 17 | "@astrojs/cloudflare": "^13.0.0", |
| 18 | "@astrojs/mdx": "^6.0.3", | ||
| 18 | "@astrojs/react": "^5.0.4", | 19 | "@astrojs/react": "^5.0.4", |
| 19 | "@executor-js/react": "workspace:*", | 20 | "@executor-js/react": "workspace:*", |
| 20 | "@tailwindcss/vite": "^4.2.2", | 21 | "@tailwindcss/vite": "^4.2.2", |
| 21 | "astro": "^6.1.3", | 22 | "astro": "^6.4.8", |
| 22 | "clsx": "^2.1.1", | 23 | "clsx": "^2.1.1", |
| 23 | "motion": "^12.38.0", | 24 | "motion": "^12.38.0", |
| 24 | "posthog-js": "^1.372.5", | 25 | "posthog-js": "^1.372.5", |
| @@ -23,6 +23,11 @@ Run it your way: local CLI, a desktop app, hosted Executor Cloud, or self-hosted · 22 unmodified lines | |||
| 23 | - [Pricing](https://executor.sh/#pricing): plans for Executor Cloud. | 23 | - [Pricing](https://executor.sh/#pricing): plans for Executor Cloud. |
| 24 | - [Install](https://executor.sh/#install): install the CLI and connect your first agent. | 24 | - [Install](https://executor.sh/#install): install the CLI and connect your first agent. |
| 25 | 25 | ||
| 26 | ## Blog | ||
| 27 | |||
| 28 | - [MCPs, CLIs, and APIs are the same thing](https://executor.sh/blog/mcp-cli-same-thing): interactive tour of tool-calling envelopes, why context bloat is a client problem, and what the MCP-vs-CLI paper actually measured. | ||
| 29 | - [Why MCP had so many growing pains](https://executor.sh/blog/why-mcp-had-growing-pains): what went wrong in MCP's first year and where it goes from here. | ||
| 30 | |||
| 26 | ## Source | 31 | ## Source |
| 27 | 32 | ||
| 28 | - [GitHub](https://github.com/UsefulSoftwareCo/executor): source for the integration layer, plugins, and hosts. | 33 | - [GitHub](https://github.com/UsefulSoftwareCo/executor): source for the integration layer, plugins, and hosts. |
| @@ -0,0 +1,180 @@ | |||
| 1 | "use client"; | ||
| 2 | |||
| 3 | /* eslint-disable react/forbid-elements -- blog widgets use bespoke styled | ||
| 4 | controls; the product design-system <Button> does not model them. */ | ||
| 5 | |||
| 6 | import { useState } from "react"; | ||
| 7 | import { fmt, useAnimatedNumber } from "./shared"; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * The centerpiece: context cost is a 2×2, not a protocol property. One axis | ||
| 11 | * picks the interface (CLI or MCP), the other picks the loading strategy | ||
| 12 | * (on demand or everything up front). Flipping the interface barely moves the | ||
| 13 | * number; flipping the loading strategy moves it ~20x. | ||
| 14 | * | ||
| 15 | * Figures are illustrative but anchored: the arXiv paper everyone cited | ||
| 16 | * measured the official GitHub MCP server at 44 tools, eager clients resending | ||
| 17 | * the whole catalog every request, and ~22k tokens for 27 built-in tool | ||
| 18 | * schemas (~800 tokens per schema). | ||
| 19 | */ | ||
| 20 | |||
| 21 | type Interface_ = "cli" | "mcp"; | ||
| 22 | type Loading = "lazy" | "eager"; | ||
| 23 | |||
| 24 | const SYSTEM_TOK = 1200; | ||
| 25 | |||
| 26 | type Segment = { readonly label: string; readonly tok: number; readonly flood?: boolean }; | ||
| 27 | |||
| 28 | const CELLS: Record<Interface_, Record<Loading, ReadonlyArray<Segment>>> = { | ||
| 29 | cli: { | ||
| 30 | lazy: [{ label: "one bash tool definition", tok: 320 }], | ||
| 31 | eager: [ | ||
| 32 | { label: "one bash tool definition", tok: 320 }, | ||
| 33 | { label: "--help for every gh subcommand, inlined", tok: 29800, flood: true }, | ||
| 34 | ], | ||
| 35 | }, | ||
| 36 | mcp: { | ||
| 37 | lazy: [{ label: "gateway pair: search tools + invoke tool", tok: 640 }], | ||
| 38 | eager: [{ label: "44 tool schemas × ~800 tokens", tok: 35200, flood: true }], | ||
| 39 | }, | ||
| 40 | }; | ||
| 41 | |||
| 42 | const cellTotal = (i: Interface_, l: Loading) => | ||
| 43 | SYSTEM_TOK + CELLS[i][l].reduce((s, seg) => s + seg.tok, 0); | ||
| 44 | |||
| 45 | const MAX_TOTAL = Math.max( | ||
| 46 | cellTotal("cli", "eager"), | ||
| 47 | cellTotal("mcp", "eager"), | ||
| 48 | cellTotal("cli", "lazy"), | ||
| 49 | cellTotal("mcp", "lazy"), | ||
| 50 | ); | ||
| 51 | |||
| 52 | function Seg({ | ||
| 53 | value, | ||
| 54 | onPick, | ||
| 55 | options, | ||
| 56 | label, | ||
| 57 | }: { | ||
| 58 | readonly value: string; | ||
| 59 | readonly onPick: (v: string) => void; | ||
| 60 | readonly options: ReadonlyArray<{ readonly id: string; readonly label: string }>; | ||
| 61 | readonly label: string; | ||
| 62 | }) { | ||
| 63 | return ( | ||
| 64 | <div className="bw-axis"> | ||
| 65 | <span className="bw-axis__label">{label}</span> | ||
| 66 | <div className="bw-seg" role="group" aria-label={label}> | ||
| 67 | {options.map((o) => ( | ||
| 68 | <button | ||
| 69 | key={o.id} | ||
| 70 | type="button" | ||
| 71 | className="bw-seg__btn" | ||
| 72 | data-on={value === o.id ? "true" : undefined} | ||
| 73 | aria-pressed={value === o.id} | ||
| 74 | onClick={() => onPick(o.id)} | ||
| 75 | > | ||
| 76 | {o.label} | ||
| 77 | </button> | ||
| 78 | ))} | ||
| 79 | </div> | ||
| 80 | </div> | ||
| 81 | ); | ||
| 82 | } | ||
| 83 | |||
| 84 | export function ContextFloodDemo() { | ||
| 85 | const [iface, setIface] = useState<Interface_>("mcp"); | ||
| 86 | const [loading, setLoading] = useState<Loading>("eager"); | ||
| 87 | |||
| 88 | const segments: ReadonlyArray<Segment> = [ | ||
| 89 | { label: "system prompt", tok: SYSTEM_TOK }, | ||
| 90 | ...CELLS[iface][loading], | ||
| 91 | ]; | ||
| 92 | const total = cellTotal(iface, loading); | ||
| 93 | const totalDisplay = useAnimatedNumber(total); | ||
| 94 | const flooding = segments.some((s) => s.flood); | ||
| 95 | |||
| 96 | return ( | ||
| 97 | <div className="bw"> | ||
| 98 | <p className="sr-only" aria-live="polite"> | ||
| 99 | {iface === "cli" ? "CLI" : "MCP"} with tools loaded{" "} | ||
| 100 | {loading === "lazy" ? "on demand" : "up front"}: about {fmt(total)} tokens spent before your | ||
| 101 | first message. | ||
| 102 | </p> | ||
| 103 | |||
| 104 | <div className="bw-head bw-head--stack"> | ||
| 105 | <Seg | ||
| 106 | label="Interface" | ||
| 107 | value={iface} | ||
| 108 | onPick={(v) => setIface(v as Interface_)} | ||
| 109 | options={[ | ||
| 110 | { id: "cli", label: "CLI" }, | ||
| 111 | { id: "mcp", label: "MCP server" }, | ||
| 112 | ]} | ||
| 113 | /> | ||
| 114 | <Seg | ||
| 115 | label="Tool loading" | ||
| 116 | value={loading} | ||
| 117 | onPick={(v) => setLoading(v as Loading)} | ||
| 118 | options={[ | ||
| 119 | { id: "lazy", label: "On demand" }, | ||
| 120 | { id: "eager", label: "Everything up front" }, | ||
| 121 | ]} | ||
| 122 | /> | ||
| 123 | </div> | ||
| 124 | |||
| 125 | <div className="bw-flood"> | ||
| 126 | <div className="bw-flood__rows"> | ||
| 127 | {segments.map((s) => ( | ||
| 128 | <div key={s.label} className="bw-flood__row"> | ||
| 129 | <div className="bw-flood__meta"> | ||
| 130 | <span className="bw-flood__name">{s.label}</span> | ||
| 131 | <span className="bw-flood__tok">~{fmt(s.tok)} tok</span> | ||
| 132 | </div> | ||
| 133 | <div className="bw-flood__track"> | ||
| 134 | <div | ||
| 135 | className="bw-flood__fill" | ||
| 136 | data-flood={s.flood ? "true" : undefined} | ||
| 137 | style={{ width: `${Math.max(1.5, (s.tok / MAX_TOTAL) * 100)}%` }} | ||
| 138 | /> | ||
| 139 | </div> | ||
| 140 | </div> | ||
| 141 | ))} | ||
| 142 | </div> | ||
| 143 | <div className="bw-flood__total"> | ||
| 144 | <div className="bw-flood__num">~{fmt(totalDisplay)}</div> | ||
| 145 | <div className="bw-flood__cap">tokens before your first message</div> | ||
| 146 | <div className="bw-badge" data-show={flooding ? "true" : undefined}> | ||
| 147 | resent with every request | ||
| 148 | </div> | ||
| 149 | </div> | ||
| 150 | </div> | ||
| 151 | |||
| 152 | <div className="bw-map" role="group" aria-label="All four combinations"> | ||
| 153 | {(["cli", "mcp"] as const).flatMap((i) => | ||
| 154 | (["lazy", "eager"] as const).map((l) => ( | ||
| 155 | <button | ||
| 156 | key={`${i}-${l}`} | ||
| 157 | type="button" | ||
| 158 | className="bw-map__cell" | ||
| 159 | data-on={i === iface && l === loading ? "true" : undefined} | ||
| 160 | onClick={() => { | ||
| 161 | setIface(i); | ||
| 162 | setLoading(l); | ||
| 163 | }} | ||
| 164 | > | ||
| 165 | <span className="bw-map__label"> | ||
| 166 | {i === "cli" ? "CLI" : "MCP"} · {l === "lazy" ? "on demand" : "up front"} | ||
| 167 | </span> | ||
| 168 | <span className="bw-map__val">~{fmt(cellTotal(i, l))}</span> | ||
| 169 | </button> | ||
| 170 | )), | ||
| 171 | )} | ||
| 172 | </div> | ||
| 173 | |||
| 174 | <div className="bw-foot"> | ||
| 175 | Flip the interface: the number barely moves. Flip the loading strategy: ~20x. The bloat | ||
| 176 | lives on one axis, and it is not the protocol axis. | ||
| 177 | </div> | ||
| 178 | </div> | ||
| 179 | ); | ||
| 180 | } | ||
| @@ -0,0 +1,158 @@ | |||
| 1 | "use client"; | ||
| 2 | |||
| 3 | /* eslint-disable react/forbid-elements -- blog widgets use bespoke styled | ||
| 4 | controls; the product design-system <Button> does not model them. */ | ||
| 5 | |||
| 6 | import { useState } from "react"; | ||
| 7 | import { fmt } from "./shared"; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * What the paper actually measured. Stage one shows Table 2 — median input | ||
| 11 | * tokens per completed run for seven harnesses on the same GitHub task — and | ||
| 12 | * asks the reader which runs had an MCP server attached. The reveal: none of | ||
| 13 | * them; the 28x spread is pure harness overhead. Stage two shows Table 3 — | ||
| 14 | * what happened when the MCP server *was* attached — where four of five | ||
| 15 | * harnesses got cheaper. | ||
| 16 | * | ||
| 17 | * Data: arXiv 2608.08654, Tables 2 and 3 (median input tokens, completed runs). | ||
| 18 | */ | ||
| 19 | |||
| 20 | type Row = { readonly name: string; readonly tokens: number }; | ||
| 21 | |||
| 22 | const TABLE2: ReadonlyArray<Row> = [ | ||
| 23 | { name: "pi", tokens: 14660 }, | ||
| 24 | { name: "Tau", tokens: 16459 }, | ||
| 25 | { name: "Codex", tokens: 82378 }, | ||
| 26 | { name: "Hermes", tokens: 83954 }, | ||
| 27 | { name: "opencode", tokens: 137800 }, | ||
| 28 | { name: "qwen-code", tokens: 297649 }, | ||
| 29 | { name: "Claude Code", tokens: 410797 }, | ||
| 30 | ]; | ||
| 31 | const MAX2 = 410797; | ||
| 32 | |||
| 33 | type Paired = { readonly name: string; readonly ratio: number; readonly note?: string }; | ||
| 34 | |||
| 35 | // Table 3: cost with MCP attached ÷ cost without, per harness. | ||
| 36 | const TABLE3: ReadonlyArray<Paired> = [ | ||
| 37 | { name: "Claude Code", ratio: 0.57 }, | ||
| 38 | { name: "qwen-code", ratio: 0.74 }, | ||
| 39 | { name: "Hermes", ratio: 0.84 }, | ||
| 40 | { name: "opencode", ratio: 0.95 }, | ||
| 41 | { name: "Codex", ratio: 16.09, note: "n=2" }, | ||
| 42 | ]; | ||
| 43 | |||
| 44 | // Position on a log scale from 0.4x to 20x, so 1.0x sits at a fixed line and | ||
| 45 | // the Codex outlier stays on the chart without flattening everything else. | ||
| 46 | const logPos = (r: number) => { | ||
| 47 | const min = Math.log(0.4); | ||
| 48 | const max = Math.log(20); | ||
| 49 | return ((Math.log(r) - min) / (max - min)) * 100; | ||
| 50 | }; | ||
| 51 | |||
| 52 | type Stage = "guess" | "revealed" | "paired"; | ||
| 53 | |||
| 54 | export function HarnessSpreadDemo() { | ||
| 55 | const [stage, setStage] = useState<Stage>("guess"); | ||
| 56 | |||
| 57 | return ( | ||
| 58 | <div className="bw"> | ||
| 59 | <p className="sr-only" aria-live="polite"> | ||
| 60 | {stage === "guess" | ||
| 61 | ? "Seven harnesses ran the same GitHub task. Median input tokens range from 14,660 to 410,797." | ||
| 62 | : stage === "revealed" | ||
| 63 | ? "Reveal: none of these runs had an MCP server attached. The 28x spread is harness overhead." | ||
| 64 | : "With the MCP server attached, four of five harnesses got cheaper. Median paired ratio 0.93."} | ||
| 65 | </p> | ||
| 66 | |||
| 67 | {stage !== "paired" ? ( | ||
| 68 | <> | ||
| 69 | <div className="bw-head"> | ||
| 70 | <span className="bw-eyebrow">Seven harnesses, one task, median input tokens</span> | ||
| 71 | </div> | ||
| 72 | <div className="bw-bars"> | ||
| 73 | {TABLE2.map((r) => ( | ||
| 74 | <div key={r.name} className="bw-bars__row"> | ||
| 75 | <span className="bw-bars__name">{r.name}</span> | ||
| 76 | <div className="bw-bars__track"> | ||
| 77 | <div | ||
| 78 | className="bw-bars__fill" | ||
| 79 | style={{ width: `${Math.max(2, (r.tokens / MAX2) * 100)}%` }} | ||
| 80 | /> | ||
| 81 | </div> | ||
| 82 | <span className="bw-bars__val">{fmt(r.tokens)}</span> | ||
| 83 | </div> | ||
| 84 | ))} | ||
| 85 | </div> | ||
| 86 | {stage === "guess" ? ( | ||
| 87 | <div className="bw-quiz"> | ||
| 88 | <span>Which of these runs had an MCP server attached?</span> | ||
| 89 | <button type="button" className="bw-btn" onClick={() => setStage("revealed")}> | ||
| 90 | Reveal | ||
| 91 | </button> | ||
| 92 | </div> | ||
| 93 | ) : ( | ||
| 94 | <div className="bw-quiz bw-quiz--answer"> | ||
| 95 | <span> | ||
| 96 | <strong>None of them.</strong> No MCP server was attached to any run in this chart — | ||
| 97 | the 28x spread between the cheapest and most expensive harness is pure harness | ||
| 98 | overhead. | ||
| 99 | </span> | ||
| 100 | <button type="button" className="bw-btn" onClick={() => setStage("paired")}> | ||
| 101 | So what happened when they attached it? | ||
| 102 | </button> | ||
| 103 | </div> | ||
| 104 | )} | ||
| 105 | </> | ||
| 106 | ) : ( | ||
| 107 | <> | ||
| 108 | <div className="bw-head"> | ||
| 109 | <span className="bw-eyebrow">Cost with MCP attached ÷ cost without, per harness</span> | ||
| 110 | </div> | ||
| 111 | <div className="bw-ratio" style={{ "--one-x": `${logPos(1)}%` } as React.CSSProperties}> | ||
| 112 | <div className="bw-ratio__row bw-ratio__row--axis" aria-hidden="true"> | ||
| 113 | <span className="bw-ratio__name" /> | ||
| 114 | <div className="bw-ratio__axis"> | ||
| 115 | <span style={{ left: `${logPos(0.5)}%` }}>0.5×</span> | ||
| 116 | <span style={{ left: `${logPos(1)}%` }}>1×</span> | ||
| 117 | <span style={{ left: `${logPos(2)}%` }}>2×</span> | ||
| 118 | <span style={{ left: `${logPos(5)}%` }}>5×</span> | ||
| 119 | <span style={{ left: `${logPos(16)}%` }}>16×</span> | ||
| 120 | </div> | ||
| 121 | </div> | ||
| 122 | {TABLE3.map((p) => ( | ||
| 123 | <div key={p.name} className="bw-ratio__row"> | ||
| 124 | <span className="bw-ratio__name"> | ||
| 125 | {p.name}{" "} | ||
| 126 | <span className="bw-ratio__val"> | ||
| 127 | {p.ratio}×{p.note ? ` (${p.note})` : ""} | ||
| 128 | </span> | ||
| 129 | </span> | ||
| 130 | <div className="bw-ratio__track"> | ||
| 131 | <span | ||
| 132 | className="bw-ratio__marker" | ||
| 133 | data-cheaper={p.ratio < 1 ? "true" : undefined} | ||
| 134 | style={{ left: `${logPos(p.ratio)}%` }} | ||
| 135 | /> | ||
| 136 | </div> | ||
| 137 | </div> | ||
| 138 | ))} | ||
| 139 | <div className="bw-ratio__legend"> | ||
| 140 | <span>← cheaper with MCP</span> | ||
| 141 | <span>more expensive with MCP →</span> | ||
| 142 | </div> | ||
| 143 | </div> | ||
| 144 | <div className="bw-quiz bw-quiz--answer"> | ||
| 145 | <span> | ||
| 146 | Four of five harnesses got <strong>cheaper</strong> with the MCP server attached. | ||
| 147 | Across the thirteen strictly paired runs the median ratio is <strong>0.93</strong> — | ||
| 148 | the authors call the comparison inconclusive. | ||
| 149 | </span> | ||
| 150 | <button type="button" className="bw-btn" onClick={() => setStage("guess")}> | ||
| 151 | Back to the spread | ||
| 152 | </button> | ||
| 153 | </div> | ||
| 154 | </> | ||
| 155 | )} | ||
| 156 | </div> | ||
| 157 | ); | ||
| 158 | } | ||
| @@ -0,0 +1,134 @@ | |||
| 1 | "use client"; | ||
| 2 | |||
| 3 | /* eslint-disable react/forbid-elements -- blog widgets use bespoke styled | ||
| 4 | controls; the product design-system <Button> does not model them. */ | ||
| 5 | |||
| 6 | import { useEffect, useRef, useState } from "react"; | ||
| 7 | import { usePrefersReducedMotion } from "./shared"; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Adding a new capability mid-session, three ways. Press Play and the lanes | ||
| 11 | * advance a step at a time: a CLI is usable immediately, a typical MCP client | ||
| 12 | * makes you restart (and lose the session), and a client that implements | ||
| 13 | * dynamic loading matches the CLI experience. Deterministic — every reader | ||
| 14 | * sees the same run. Under reduced motion all steps render at once. | ||
| 15 | */ | ||
| 16 | |||
| 17 | type Step = { readonly text: string; readonly bad?: boolean; readonly done?: boolean }; | ||
| 18 | |||
| 19 | type Lane = { | ||
| 20 | readonly id: string; | ||
| 21 | readonly title: string; | ||
| 22 | readonly sub: string; | ||
| 23 | readonly steps: ReadonlyArray<Step>; | ||
| 24 | }; | ||
| 25 | |||
| 26 | const LANES: ReadonlyArray<Lane> = [ | ||
| 27 | { | ||
| 28 | id: "cli", | ||
| 29 | title: "CLI", | ||
| 30 | sub: "any harness with bash", | ||
| 31 | steps: [{ text: "$ bun add -g @linear/cli" }, { text: "$ linear issue list", done: true }], | ||
| 32 | }, | ||
| 33 | { | ||
| 34 | id: "mcp-typical", | ||
| 35 | title: "MCP", | ||
| 36 | sub: "a typical client today", | ||
| 37 | steps: [ | ||
| 38 | { text: "add server to mcp.json" }, | ||
| 39 | { text: "restart the client", bad: true }, | ||
| 40 | { text: "conversation state: gone", bad: true }, | ||
| 41 | { text: "re-authenticate" }, | ||
| 42 | { text: "call the tool", done: true }, | ||
| 43 | ], | ||
| 44 | }, | ||
| 45 | { | ||
| 46 | id: "mcp-dynamic", | ||
| 47 | title: "MCP", | ||
| 48 | sub: "a client with dynamic loading", | ||
| 49 | steps: [ | ||
| 50 | { text: "connect server mid-session" }, | ||
| 51 | { text: "tool list updates in place" }, | ||
| 52 | { text: "call the tool", done: true }, | ||
| 53 | ], | ||
| 54 | }, | ||
| 55 | ]; | ||
| 56 | |||
| 57 | const STEP_MS = 800; | ||
| 58 | const MAX_STEPS = Math.max(...LANES.map((l) => l.steps.length)); | ||
| 59 | |||
| 60 | export function InstallRaceDemo() { | ||
| 61 | const reduced = usePrefersReducedMotion(); | ||
| 62 | // -1 = not started; otherwise the number of ticks elapsed. | ||
| 63 | const [tick, setTick] = useState(-1); | ||
| 64 | const timerRef = useRef<number | null>(null); | ||
| 65 | |||
| 66 | const playing = tick >= 0 && tick < MAX_STEPS; | ||
| 67 | const finished = tick >= MAX_STEPS; | ||
| 68 | |||
| 69 | useEffect(() => { | ||
| 70 | if (!playing) return; | ||
| 71 | timerRef.current = window.setTimeout(() => setTick((t) => t + 1), STEP_MS); | ||
| 72 | return () => { | ||
| 73 | if (timerRef.current) clearTimeout(timerRef.current); | ||
| 74 | }; | ||
| 75 | }, [tick, playing]); | ||
| 76 | |||
| 77 | const play = () => setTick(reduced ? MAX_STEPS : 0); | ||
| 78 | const visibleSteps = (lane: Lane) => (tick < 0 ? 0 : Math.min(tick + 1, lane.steps.length)); | ||
| 79 | |||
| 80 | return ( | ||
| 81 | <div className="bw"> | ||
| 82 | <p className="sr-only" aria-live="polite"> | ||
| 83 | {finished | ||
| 84 | ? "Finished: the CLI took 2 steps with no restart. A typical MCP client took 5 steps including a restart that lost the session. A dynamic-loading MCP client took 3 steps with no restart." | ||
| 85 | : "Press play to race adding a new capability mid-session across three setups."} | ||
| 86 | </p> | ||
| 87 | <div className="bw-head"> | ||
| 88 | <span className="bw-eyebrow">Mid-session: “now also talk to Linear”</span> | ||
| 89 | <button type="button" className="bw-btn" onClick={play} disabled={playing}> | ||
| 90 | {tick < 0 ? "Play" : playing ? "Running…" : "Replay"} | ||
| 91 | </button> | ||
| 92 | </div> | ||
| 93 | |||
| 94 | <div className="bw-lanes"> | ||
| 95 | {LANES.map((lane) => { | ||
| 96 | const shown = visibleSteps(lane); | ||
| 97 | const laneDone = shown >= lane.steps.length && tick >= 0; | ||
| 98 | return ( | ||
| 99 | <div key={lane.id} className="bw-lane" data-done={laneDone ? "true" : undefined}> | ||
| 100 | <div className="bw-lane__head"> | ||
| 101 | <span className="bw-lane__title">{lane.title}</span> | ||
| 102 | <span className="bw-lane__sub">{lane.sub}</span> | ||
| 103 | </div> | ||
| 104 | <div className="bw-lane__steps"> | ||
| 105 | {lane.steps.map((s, idx) => ( | ||
| 106 | <div | ||
| 107 | key={s.text} | ||
| 108 | className="bw-step" | ||
| 109 | data-state={idx < shown ? (s.bad ? "bad" : s.done ? "done" : "on") : "off"} | ||
| 110 | > | ||
| 111 | <span className="bw-step__dot" aria-hidden="true" /> | ||
| 112 | <span className="bw-step__text">{s.text}</span> | ||
| 113 | {s.done && idx < shown ? <span className="bw-step__ok">working</span> : null} | ||
| 114 | </div> | ||
| 115 | ))} | ||
| 116 | </div> | ||
| 117 | <div className="bw-lane__tally"> | ||
| 118 | {laneDone | ||
| 119 | ? `${lane.steps.length} steps · ${lane.steps.some((s) => s.bad) ? "1 restart, session lost" : "no restart"}` | ||
| 120 | : " "} | ||
| 121 | </div> | ||
| 122 | </div> | ||
| 123 | ); | ||
| 124 | })} | ||
| 125 | </div> | ||
| 126 | |||
| 127 | <div className="bw-foot"> | ||
| 128 | The restart is not in the protocol. MCP lets a client connect to a new server whenever it | ||
| 129 | wants and <span className="bw-mono-inline">notifications/tools/list_changed</span> exists so | ||
| 130 | the tool list can update in place. The middle lane is a client implementation choice. | ||
| 131 | </div> | ||
| 132 | </div> | ||
| 133 | ); | ||
| 134 | } | ||
| @@ -0,0 +1,155 @@ | |||
| 1 | "use client"; | ||
| 2 | |||
| 3 | /* eslint-disable react/forbid-elements -- blog widgets use bespoke styled | ||
| 4 | controls; the product design-system <Button> does not model them. */ | ||
| 5 | |||
| 6 | import { useEffect, useRef, useState } from "react"; | ||
| 7 | import { usePrefersReducedMotion } from "./shared"; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Compile an MCP server's tool catalog into a CLI. The left pane is the | ||
| 11 | * GitHub MCP server's catalog (44 tools, per the official server); pressing | ||
| 12 | * "Compile to CLI" builds the right pane subcommand-by-subcommand from the | ||
| 13 | * same rows. Nothing about the tools changes — count, names, schemas, and | ||
| 14 | * annotations all survive — which is the whole point. | ||
| 15 | */ | ||
| 16 | |||
| 17 | type Tool = { | ||
| 18 | readonly slug: string; | ||
| 19 | readonly cli: string; | ||
| 20 | readonly help: string; | ||
| 21 | readonly destructive?: boolean; | ||
| 22 | }; | ||
| 23 | |||
| 24 | // A representative slice of the official GitHub MCP server's catalog. | ||
| 25 | const TOOLS: ReadonlyArray<Tool> = [ | ||
| 26 | { slug: "issues_create", cli: "issues create", help: "Create an issue" }, | ||
| 27 | { slug: "issues_list", cli: "issues list", help: "List issues" }, | ||
| 28 | { slug: "pulls_create", cli: "pulls create", help: "Open a pull request" }, | ||
| 29 | { slug: "pulls_merge", cli: "pulls merge", help: "Merge a pull request", destructive: true }, | ||
| 30 | { slug: "branches_create", cli: "branches create", help: "Create a branch" }, | ||
| 31 | { slug: "repos_get", cli: "repos get", help: "Get a repository" }, | ||
| 32 | ]; | ||
| 33 | const REST = 38; // the rest of the 44-tool catalog | ||
| 34 | |||
| 35 | export function McpToCliDemo() { | ||
| 36 | const [compiled, setCompiled] = useState(false); | ||
| 37 | // How many rows of the CLI pane are visible; staggers up after compiling. | ||
| 38 | const [built, setBuilt] = useState(0); | ||
| 39 | const reduced = usePrefersReducedMotion(); | ||
| 40 | const timerRef = useRef<number | null>(null); | ||
| 41 | |||
| 42 | const totalRows = TOOLS.length + 1; // + the "… 38 more" row | ||
| 43 | |||
| 44 | useEffect(() => { | ||
| 45 | if (!compiled) { | ||
| 46 | setBuilt(0); | ||
| 47 | return; | ||
| 48 | } | ||
| 49 | if (reduced) { | ||
| 50 | setBuilt(totalRows); | ||
| 51 | return; | ||
| 52 | } | ||
| 53 | let n = 0; | ||
| 54 | timerRef.current = window.setInterval(() => { | ||
| 55 | n += 1; | ||
| 56 | setBuilt(n); | ||
| 57 | if (n >= totalRows && timerRef.current) clearInterval(timerRef.current); | ||
| 58 | }, 140); | ||
| 59 | return () => { | ||
| 60 | if (timerRef.current) clearInterval(timerRef.current); | ||
| 61 | }; | ||
| 62 | }, [compiled, reduced, totalRows]); | ||
| 63 | |||
| 64 | const done = built >= totalRows; | ||
| 65 | |||
| 66 | return ( | ||
| 67 | <div className="bw"> | ||
| 68 | <p className="sr-only" aria-live="polite"> | ||
| 69 | {compiled | ||
| 70 | ? "Compiled the 44-tool MCP server into a CLI with 44 subcommands. The tools are unchanged." | ||
| 71 | : "An MCP server with 44 tools, not yet compiled to a CLI."} | ||
| 72 | </p> | ||
| 73 | <div className="bw-head"> | ||
| 74 | <span className="bw-eyebrow">The same catalog, twice</span> | ||
| 75 | <button | ||
| 76 | type="button" | ||
| 77 | className="bw-btn" | ||
| 78 | onClick={() => setCompiled((c) => !c)} | ||
| 79 | aria-pressed={compiled} | ||
| 80 | > | ||
| 81 | {compiled ? "Reset" : "Compile to CLI"} | ||
| 82 | </button> | ||
| 83 | </div> | ||
| 84 | |||
| 85 | <div className="bw-pair"> | ||
| 86 | <div className="bw-pane" data-dim={compiled && done ? "true" : undefined}> | ||
| 87 | <div className="bw-pane__title"> | ||
| 88 | MCP server <span className="bw-mono-note">github · 44 tools</span> | ||
| 89 | </div> | ||
| 90 | <div className="bw-rows"> | ||
| 91 | {TOOLS.map((t) => ( | ||
| 92 | <div key={t.slug} className="bw-row"> | ||
| 93 | <span className="bw-row__name">{t.slug}</span> | ||
| 94 | <span className="bw-row__desc">{t.help}</span> | ||
| 95 | {t.destructive ? <span className="bw-tag">destructive</span> : null} | ||
| 96 | </div> | ||
| 97 | ))} | ||
| 98 | <div className="bw-row bw-row--rest">… {REST} more</div> | ||
| 99 | </div> | ||
| 100 | </div> | ||
| 101 | |||
| 102 | <div className="bw-pane__arrow" aria-hidden="true"> | ||
| 103 | → | ||
| 104 | </div> | ||
| 105 | |||
| 106 | <div className="bw-pane"> | ||
| 107 | <div className="bw-pane__title"> | ||
| 108 | Generated CLI{" "} | ||
| 109 | <span className="bw-mono-note">{done ? "github · 44 commands" : "—"}</span> | ||
| 110 | </div> | ||
| 111 | <div className="code-window bw-window bw-window--fill"> | ||
| 112 | <div className="code-window__bar"> | ||
| 113 | <span className="code-window__dots"> | ||
| 114 | <i /> | ||
| 115 | <i /> | ||
| 116 | <i /> | ||
| 117 | </span> | ||
| 118 | </div> | ||
| 119 | <pre className="code-window__body bw-code bw-code--term"> | ||
| 120 | <code> | ||
| 121 | <span className="bw-dim">{"$ "}</span> | ||
| 122 | {"github --help\n"} | ||
| 123 | {compiled ? ( | ||
| 124 | <> | ||
| 125 | {"Commands:\n"} | ||
| 126 | {TOOLS.slice(0, Math.min(built, TOOLS.length)).map((t) => ( | ||
| 127 | <span key={t.slug} className="bw-fade-in"> | ||
| 128 | {" "} | ||
| 129 | {t.cli.padEnd(17)} | ||
| 130 | <span className="bw-dim">{t.help}</span> | ||
| 131 | {t.destructive ? <span className="bw-dim"> [destructive]</span> : null} | ||
| 132 | {"\n"} | ||
| 133 | </span> | ||
| 134 | ))} | ||
| 135 | {built >= totalRows ? ( | ||
| 136 | <span className="bw-fade-in bw-dim">{` … ${REST} more\n`}</span> | ||
| 137 | ) : null} | ||
| 138 | </> | ||
| 139 | ) : ( | ||
| 140 | <span className="bw-dim">{"\n(nothing here yet — press Compile to CLI)"}</span> | ||
| 141 | )} | ||
| 142 | </code> | ||
| 143 | </pre> | ||
| 144 | </div> | ||
| 145 | </div> | ||
| 146 | </div> | ||
| 147 | |||
| 148 | <div className="bw-foot" aria-hidden={!done}> | ||
| 149 | {done | ||
| 150 | ? "44 tools in, 44 commands out. Names, schemas, descriptions, and the destructive flag all survived. The only thing that changed is how you invoke them." | ||
| 151 | : " "} | ||
| 152 | </div> | ||
| 153 | </div> | ||
| 154 | ); | ||
| 155 | } | ||
| @@ -0,0 +1,173 @@ | |||
| 1 | "use client"; | ||
| 2 | |||
| 3 | /* eslint-disable react/forbid-elements -- blog widgets use bespoke styled | ||
| 4 | controls (segmented toggles, mono labels); the product design-system | ||
| 5 | <Button> does not model them. */ | ||
| 6 | |||
| 7 | import { useEffect, useRef, useState } from "react"; | ||
| 8 | import { useInView, usePrefersReducedMotion } from "./shared"; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * The hero widget: one operation (create a GitHub issue) rendered as an MCP | ||
| 12 | * tool call, a CLI invocation, and a raw HTTP request. The facts that never | ||
| 13 | * change (which operation, which repo, which title, which label) carry a | ||
| 14 | * highlight so the eye can track them across envelopes. Cycles on its own | ||
| 15 | * until the reader touches it. | ||
| 16 | */ | ||
| 17 | |||
| 18 | type Surface = "mcp" | "cli" | "http"; | ||
| 19 | |||
| 20 | const SURFACES: ReadonlyArray<{ readonly id: Surface; readonly label: string }> = [ | ||
| 21 | { id: "mcp", label: "MCP tool call" }, | ||
| 22 | { id: "cli", label: "CLI command" }, | ||
| 23 | { id: "http", label: "HTTP request" }, | ||
| 24 | ]; | ||
| 25 | |||
| 26 | // The invariant facts, highlighted identically on every surface. | ||
| 27 | function Hl({ children }: { readonly children: React.ReactNode }) { | ||
| 28 | return <mark className="bw-hl">{children}</mark>; | ||
| 29 | } | ||
| 30 | |||
| 31 | function McpBody() { | ||
| 32 | return ( | ||
| 33 | <> | ||
| 34 | <span className="bw-dim">{"→ tools/call\n"}</span> | ||
| 35 | {"{\n"} | ||
| 36 | {' "name": '} | ||
| 37 | <Hl>{'"issues_create"'}</Hl> | ||
| 38 | {",\n"} | ||
| 39 | {' "arguments": {\n'} | ||
| 40 | {' "repo": '} | ||
| 41 | <Hl>{'"acme/api"'}</Hl> | ||
| 42 | {",\n"} | ||
| 43 | {' "title": '} | ||
| 44 | <Hl>{'"Exports time out after 30s"'}</Hl> | ||
| 45 | {",\n"} | ||
| 46 | {' "labels": ['} | ||
| 47 | <Hl>{'"bug"'}</Hl> | ||
| 48 | {"]\n"} | ||
| 49 | {" }\n"} | ||
| 50 | {"}"} | ||
| 51 | </> | ||
| 52 | ); | ||
| 53 | } | ||
| 54 | |||
| 55 | function CliBody() { | ||
| 56 | return ( | ||
| 57 | <> | ||
| 58 | <span className="bw-dim">{"$ "}</span> | ||
| 59 | {"gh "} | ||
| 60 | <Hl>issue create</Hl> | ||
| 61 | {" \\\n"} | ||
| 62 | {" --repo "} | ||
| 63 | <Hl>acme/api</Hl> | ||
| 64 | {" \\\n"} | ||
| 65 | {" --title "} | ||
| 66 | <Hl>{'"Exports time out after 30s"'}</Hl> | ||
| 67 | {" \\\n"} | ||
| 68 | {" --label "} | ||
| 69 | <Hl>bug</Hl> | ||
| 70 | </> | ||
| 71 | ); | ||
| 72 | } | ||
| 73 | |||
| 74 | function HttpBody() { | ||
| 75 | return ( | ||
| 76 | <> | ||
| 77 | <Hl>POST</Hl> | ||
| 78 | {" /repos/"} | ||
| 79 | <Hl>acme/api</Hl> | ||
| 80 | {"/"} | ||
| 81 | <Hl>issues</Hl> | ||
| 82 | {" HTTP/1.1\n"} | ||
| 83 | <span className="bw-dim"> | ||
| 84 | {"authorization: Bearer •••\ncontent-type: application/json\n\n"} | ||
| 85 | </span> | ||
| 86 | {'{ "title": '} | ||
| 87 | <Hl>{'"Exports time out after 30s"'}</Hl> | ||
| 88 | {', "labels": ['} | ||
| 89 | <Hl>{'"bug"'}</Hl> | ||
| 90 | {"] }"} | ||
| 91 | </> | ||
| 92 | ); | ||
| 93 | } | ||
| 94 | |||
| 95 | const BODIES: Record<Surface, () => React.ReactNode> = { | ||
| 96 | mcp: McpBody, | ||
| 97 | cli: CliBody, | ||
| 98 | http: HttpBody, | ||
| 99 | }; | ||
| 100 | |||
| 101 | export function SameCallDemo() { | ||
| 102 | const [surface, setSurface] = useState<Surface>("mcp"); | ||
| 103 | const [touched, setTouched] = useState(false); | ||
| 104 | const rootRef = useRef<HTMLDivElement>(null); | ||
| 105 | const inView = useInView(rootRef); | ||
| 106 | const reduced = usePrefersReducedMotion(); | ||
| 107 | |||
| 108 | // Cycle surfaces until the reader takes over; never offscreen, never under | ||
| 109 | // reduced motion. | ||
| 110 | useEffect(() => { | ||
| 111 | if (touched || reduced || !inView) return; | ||
| 112 | const t = setInterval(() => { | ||
| 113 | setSurface((s) => { | ||
| 114 | const i = SURFACES.findIndex((x) => x.id === s); | ||
| 115 | const next = SURFACES[(i + 1) % SURFACES.length]; | ||
| 116 | return next ? next.id : s; | ||
| 117 | }); | ||
| 118 | }, 3200); | ||
| 119 | return () => clearInterval(t); | ||
| 120 | }, [touched, reduced, inView]); | ||
| 121 | |||
| 122 | const pick = (s: Surface) => { | ||
| 123 | setTouched(true); | ||
| 124 | setSurface(s); | ||
| 125 | }; | ||
| 126 | |||
| 127 | const Body = BODIES[surface]; | ||
| 128 | |||
| 129 | return ( | ||
| 130 | <div className="bw" ref={rootRef}> | ||
| 131 | <p className="sr-only" aria-live="polite"> | ||
| 132 | Showing the create-issue operation as {SURFACES.find((s) => s.id === surface)?.label}. The | ||
| 133 | operation, repository, title, and label are identical on every surface. | ||
| 134 | </p> | ||
| 135 | <div className="bw-head"> | ||
| 136 | <span className="bw-eyebrow">One operation, three envelopes</span> | ||
| 137 | <div className="bw-seg" role="group" aria-label="Choose a surface"> | ||
| 138 | {SURFACES.map((s) => ( | ||
| 139 | <button | ||
| 140 | key={s.id} | ||
| 141 | type="button" | ||
| 142 | className="bw-seg__btn" | ||
| 143 | data-on={surface === s.id ? "true" : undefined} | ||
| 144 | aria-pressed={surface === s.id} | ||
| 145 | onClick={() => pick(s.id)} | ||
| 146 | > | ||
| 147 | {s.label} | ||
| 148 | </button> | ||
| 149 | ))} | ||
| 150 | </div> | ||
| 151 | </div> | ||
| 152 | <div className="code-window bw-window"> | ||
| 153 | <div className="code-window__bar"> | ||
| 154 | <span className="code-window__dots"> | ||
| 155 | <i /> | ||
| 156 | <i /> | ||
| 157 | <i /> | ||
| 158 | </span> | ||
| 159 | <span className="bw-mono-note">create an issue on acme/api</span> | ||
| 160 | </div> | ||
| 161 | <pre className="code-window__body bw-code" aria-hidden={false}> | ||
| 162 | <code> | ||
| 163 | <Body /> | ||
| 164 | </code> | ||
| 165 | </pre> | ||
| 166 | </div> | ||
| 167 | <div className="bw-foot"> | ||
| 168 | The <mark className="bw-hl">highlighted parts</mark> are the operation. Everything else is | ||
| 169 | the envelope. | ||
| 170 | </div> | ||
| 171 | </div> | ||
| 172 | ); | ||
| 173 | } | ||
| @@ -0,0 +1,72 @@ | |||
| 1 | import { useEffect, useRef, useState } from "react"; | ||
| 2 | |||
| 3 | /** Formats 12345 as "12,345". */ | ||
| 4 | export const fmt = (n: number) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | ||
| 5 | |||
| 6 | export function usePrefersReducedMotion(): boolean { | ||
| 7 | const [reduced, setReduced] = useState(false); | ||
| 8 | useEffect(() => { | ||
| 9 | const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); | ||
| 10 | const sync = () => setReduced(mq.matches); | ||
| 11 | sync(); | ||
| 12 | mq.addEventListener("change", sync); | ||
| 13 | return () => mq.removeEventListener("change", sync); | ||
| 14 | }, []); | ||
| 15 | return reduced; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** Eases a displayed integer toward `target` with requestAnimationFrame. */ | ||
| 19 | export function useAnimatedNumber(target: number): number { | ||
| 20 | const reduced = usePrefersReducedMotion(); | ||
| 21 | const [display, setDisplay] = useState(target); | ||
| 22 | const fromRef = useRef(target); | ||
| 23 | const rafRef = useRef<number | null>(null); | ||
| 24 | |||
| 25 | useEffect(() => { | ||
| 26 | if (reduced) { | ||
| 27 | setDisplay(target); | ||
| 28 | fromRef.current = target; | ||
| 29 | return; | ||
| 30 | } | ||
| 31 | const from = fromRef.current; | ||
| 32 | if (from === target) return; | ||
| 33 | const start = performance.now(); | ||
| 34 | const dur = 450; | ||
| 35 | const tick = (now: number) => { | ||
| 36 | const t = Math.min(1, (now - start) / dur); | ||
| 37 | const eased = 1 - Math.pow(1 - t, 3); | ||
| 38 | setDisplay(Math.round(from + (target - from) * eased)); | ||
| 39 | if (t < 1) { | ||
| 40 | rafRef.current = requestAnimationFrame(tick); | ||
| 41 | } else { | ||
| 42 | fromRef.current = target; | ||
| 43 | } | ||
| 44 | }; | ||
| 45 | rafRef.current = requestAnimationFrame(tick); | ||
| 46 | return () => { | ||
| 47 | if (rafRef.current) cancelAnimationFrame(rafRef.current); | ||
| 48 | fromRef.current = target; | ||
| 49 | }; | ||
| 50 | }, [target, reduced]); | ||
| 51 | |||
| 52 | return display; | ||
| 53 | } | ||
| 54 | |||
| 55 | /** True while `ref`'s element is at least partially on screen. Autoplaying | ||
| 56 | * widgets gate their timers on this so nothing animates offscreen. */ | ||
| 57 | export function useInView(ref: React.RefObject<Element | null>): boolean { | ||
| 58 | const [inView, setInView] = useState(false); | ||
| 59 | useEffect(() => { | ||
| 60 | const el = ref.current; | ||
| 61 | if (!el || typeof IntersectionObserver === "undefined") { | ||
| 62 | setInView(true); | ||
| 63 | return; | ||
| 64 | } | ||
| 65 | const io = new IntersectionObserver(([entry]) => setInView(entry?.isIntersecting ?? false), { | ||
| 66 | threshold: 0.2, | ||
| 67 | }); | ||
| 68 | io.observe(el); | ||
| 69 | return () => io.disconnect(); | ||
| 70 | }, [ref]); | ||
| 71 | return inView; | ||
| 72 | } | ||
| @@ -1,11 +1,12 @@ | |||
| 1 | import { defineCollection, z } from "astro:content"; | 1 | import { defineCollection, z } from "astro:content"; |
| 2 | import { glob } from "astro/loaders"; | 2 | import { glob } from "astro/loaders"; |
| 3 | 3 | ||
| 4 | // Blog posts authored as Markdown under src/content/blog. The glob loader | 4 | // Blog posts authored as Markdown or MDX under src/content/blog. The glob |
| 5 | // turns each file into a collection entry whose `id` is the slug used in the | 5 | // loader turns each file into a collection entry whose `id` is the slug used |
| 6 | // /blog/[...slug] route (filename without extension). | 6 | // in the /blog/[...slug] route (filename without extension). MDX is for posts |
| 7 | // that embed interactive island components. | ||
| 7 | const blog = defineCollection({ | 8 | const blog = defineCollection({ |
| 8 | loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }), | 9 | loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/blog" }), |
| 9 | schema: z.object({ | 10 | schema: z.object({ |
| 10 | title: z.string(), | 11 | title: z.string(), |
| 11 | description: z.string(), | 12 | description: z.string(), |
| @@ -0,0 +1,94 @@ | |||
| 1 | --- | ||
| 2 | title: "MCPs, CLIs, and APIs are the same thing" | ||
| 3 | description: "You can compile an MCP server into a CLI without changing a single tool. An interactive look at what tool calling actually is, why context bloat is a client problem, and what that viral paper really measured." | ||
| 4 | date: 2026-08-23 | ||
| 5 | author: "Rhys Sullivan" | ||
| 6 | --- | ||
| 7 | |||
| 8 | import { SameCallDemo } from "../../components/blog/same-call-demo"; | ||
| 9 | import { McpToCliDemo } from "../../components/blog/mcp-to-cli-demo"; | ||
| 10 | import { ContextFloodDemo } from "../../components/blog/context-flood-demo"; | ||
| 11 | import { HarnessSpreadDemo } from "../../components/blog/harness-spread-demo"; | ||
| 12 | import { InstallRaceDemo } from "../../components/blog/install-race-demo"; | ||
| 13 | |||
| 14 | Last week a [paper](https://arxiv.org/abs/2608.08654) made the rounds claiming that agents finish software tasks just as reliably without MCP, and 5 to 28 times cheaper. The replies sorted themselves into the usual camps: MCP is dead, CLIs won, protocols were a mistake. | ||
| 15 | |||
| 16 | The whole argument is built on a category error. MCP servers, CLIs, and APIs are not three competing technologies. They are three envelopes around the same thing: an agent invoking a tool. | ||
| 17 | |||
| 18 | Here is one operation — create a GitHub issue — in all three envelopes. Flip between them and watch what actually changes. | ||
| 19 | |||
| 20 | <SameCallDemo client:load /> | ||
| 21 | |||
| 22 | Same operation, same arguments, same GitHub on the other end. What changes is the delivery mechanism: a JSON-RPC message from a connected client, a process spawned in a shell, an HTTP request with a bearer token. Every take of the form "CLIs beat MCP" is a claim about envelopes, not about capabilities. | ||
| 23 | |||
| 24 | And because they are just envelopes, you can convert between them freely. Let's do that. | ||
| 25 | |||
| 26 | ## Compile an MCP server into a CLI | ||
| 27 | |||
| 28 | The official GitHub MCP server publishes 44 tools. Each one has a name, a JSON schema for its arguments, a description, and annotations like whether it is destructive. Press `Compile to CLI`. | ||
| 29 | |||
| 30 | <McpToCliDemo client:visible /> | ||
| 31 | |||
| 32 | Nothing about the tools changed. Not the count, not the schemas, not the descriptions. What changed is the execution environment you need to invoke them: on the left, a client that speaks JSON-RPC; on the right, a shell, a filesystem, a `PATH`, and a process per invocation. | ||
| 33 | |||
| 34 | Note the direction of that arrow, though. MCP to CLI is mechanical, because the catalog declares its entire action space up front. The reverse is not. A CLI declares nothing — the only way to find out what `gh` can do is to recursively run `--help` and hope the help text is complete. Keep that asymmetry in mind; it comes back later. | ||
| 35 | |||
| 36 | If context bloat were a property of MCP, this compile step would be a miracle cure: same 44 tools, bloat gone. That is obviously absurd, which tells you the bloat was never coming from the protocol. | ||
| 37 | |||
| 38 | ## Context bloat is a loading strategy | ||
| 39 | |||
| 40 | When someone says "MCP floods your context window," the thing being described is a client that takes every tool schema from every connected server and pastes all of them into every request. That is a decision the client made. The protocol did not make it. | ||
| 41 | |||
| 42 | There are two independent choices hiding in every setup: which interface you use, and how tools get loaded into context. Try both axes and watch which one the number follows. | ||
| 43 | |||
| 44 | <ContextFloodDemo client:visible /> | ||
| 45 | |||
| 46 | The number follows the loading axis. A CLI feels cheap because no one would ever dream of inlining the help text for every `gh` subcommand up front — the model runs `--help` for the two commands it needs, when it needs them. Then we built MCP clients that do exactly the thing nobody would do to a CLI, and blamed the protocol for the result. | ||
| 47 | |||
| 48 | This is not a hypothetical; it is in the paper's own data. Against the same 44-tool GitHub server, the harnesses that resent the full catalog with every request spent a median of 216,986 tokens per completed run. The one harness that exposed a two-tool gateway — list tools, invoke tool — and fetched descriptions on demand spent 70,836. A 3.1x difference the authors themselves call "a property of the agent scaffolding rather than of the protocol." They also cite [Anthropic's write-up on code execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp) as establishing that eager transmission "is a design choice rather than a requirement of the protocol." | ||
| 49 | |||
| 50 | ## What the paper actually measured | ||
| 51 | |||
| 52 | So where did 5 to 28x come from? Here is the chart behind the headline: seven harnesses, the same GitHub task, median input tokens per completed run. Before reading on, answer the question under the chart. | ||
| 53 | |||
| 54 | <HarnessSpreadDemo client:visible /> | ||
| 55 | |||
| 56 | The 28x is a comparison between harnesses with **no MCP server attached to any run**. That is not my gotcha — it is the abstract's own phrasing: the two lean harnesses were "5.0× to 28× cheaper than the five scaffoldings that do support MCP, comparing CLI runs alone with no MCP server attached anywhere." Claude Code starts around 22,000 tokens before the task says anything, because it ships 27 built-in tool schemas with every request. pi finishes the _entire task_ in 14,660. That spread — the headline number — is pure harness overhead, measured in a room MCP never entered. | ||
| 57 | |||
| 58 | When the authors did attach the server, four of five harnesses got _cheaper_, the median paired ratio was 0.93, and both arms failed the same number of times. Their own stated noise floor is a factor of two: anything smaller "cannot be distinguished from run-to-run variation." | ||
| 59 | |||
| 60 | To be fair to the authors, they know this — the paper is literally titled _The Scaffolding Matters More Than the Interface_. My complaints are that it tests one task against one server, nearly every cell is a single run, and it still got passed around as "MCP costs 28x more." The measured content of the paper is one of the better arguments for the thesis of this post. | ||
| 61 | |||
| 62 | ## The restart is not in the spec | ||
| 63 | |||
| 64 | The strongest real advantage CLIs have today has nothing to do with tokens. It is this: | ||
| 65 | |||
| 66 | <InstallRaceDemo client:visible /> | ||
| 67 | |||
| 68 | Tell an agent to install a CLI and it is using it seconds later, mid-conversation. Add an MCP server to most clients and you are editing a config file, restarting the client, losing your session, and re-authenticating. That friction is real and I will not defend it. | ||
| 69 | |||
| 70 | But locate it precisely. The protocol lets a client connect to a new server at any point in a session, and `notifications/tools/list_changed` exists so the tool list can update in place. The middle lane is a client implementation gap — the same species of gap as eager loading. Bash had 37 years of polish behind it when agents arrived; most MCP clients were built in months. That is an argument about maturity, not about which envelope is correct. | ||
| 71 | |||
| 72 | ## So when do you reach for each? | ||
| 73 | |||
| 74 | If they are all the same thing underneath, choosing between them is about what each envelope buys you. | ||
| 75 | |||
| 76 | **MCP** is the right envelope for interactions that are _about_ the agent session: elicitation (ask the human before doing something), MCP Apps (render UI inside the chat), triggers and notifications (push state changes back into a running session). None of that maps onto a CLI or a bare API. You also get the enumerable action space and destructive-action annotations that made the compile step above possible. | ||
| 77 | |||
| 78 | **APIs** are right for raw data access and bulk work, and they are the fastest way to make an existing product agent-accessible. The OpenAPI spec already carries descriptions and marks what is destructive. If your MCP server is a thin mirror of your API, the spec alone was probably enough. | ||
| 79 | |||
| 80 | **CLIs** are the best debugging experience an agent can have today, because bash composability — chain commands, grep the output, retry — is unmatched. The price is a shell and a sandbox per agent, plus an action space you cannot enumerate. Over a long enough horizon I think CLIs are for humans; today they are often the pragmatic choice. | ||
| 81 | |||
| 82 | I wrote more about how MCP ended up with its scar tissue in [Why MCP had so many growing pains](/blog/why-mcp-had-growing-pains). | ||
| 83 | |||
| 84 | ## It's all tool calling | ||
| 85 | |||
| 86 | You can convert an API into an MCP server, the MCP server into a CLI, and the CLI back into API calls, and at no point did the agent's actual capabilities change. What changed is the dependencies you carry — a client, a shell, an HTTP stack — and the interaction patterns you unlock. | ||
| 87 | |||
| 88 | That is the bet behind [Executor](https://executor.sh): the harness should not care which envelope you bring. MCP server, OpenAPI spec, GraphQL, CLI — it all lands in one catalog, loaded on demand, behind one `execute` tool. My personal instance has around 10,000 tools in it, a mix of MCP servers and raw APIs, and agents work through it comfortably — not because the envelopes are exotic, but because the loading strategy is right. | ||
| 89 | |||
| 90 | Stop arguing about envelopes. Fix the client. | ||
| 91 | |||
| 92 | --- | ||
| 93 | |||
| 94 | _Numbers in this post are from [arXiv:2608.08654](https://arxiv.org/abs/2608.08654), Tables 2, 3, and 5: median input tokens per completed run. Token figures in the loading-strategy widget are illustrative, anchored to the paper's measurements (~800 tokens per tool schema, 44 tools on the official GitHub MCP server)._ | ||
| @@ -1673,3 +1673,671 @@ body { · 1672 unmodified lines | |||
| 1673 | .blog-prose h3 + p { | 1673 | .blog-prose h3 + p { |
| 1674 | margin-top: 0; | 1674 | margin-top: 0; |
| 1675 | } | 1675 | } |
| 1676 | |||
| 1677 | /* ══ Blog interactive widgets (bw-*) ══════════════════════════════ | ||
| 1678 | Shared frame + primitives for the interactive diagrams embedded in | ||
| 1679 | MDX posts (src/components/blog/*). Grayscale only, per design.md: | ||
| 1680 | hierarchy comes from tone, hatching, and mono labels, never hue. */ | ||
| 1681 | .bw { | ||
| 1682 | border: 1px solid var(--color-rule); | ||
| 1683 | border-radius: 14px; | ||
| 1684 | background: white; | ||
| 1685 | padding: 1.1rem 1.2rem 1rem; | ||
| 1686 | font-size: 13.5px; | ||
| 1687 | line-height: 1.5; | ||
| 1688 | color: var(--color-ink); | ||
| 1689 | } | ||
| 1690 | .blog-prose .bw { | ||
| 1691 | margin: 2.25rem 0; | ||
| 1692 | } | ||
| 1693 | /* Reset the editorial pre/code treatment inside widgets so .code-window | ||
| 1694 | panes keep their own chrome. */ | ||
| 1695 | .blog-prose .bw pre { | ||
| 1696 | margin: 0; | ||
| 1697 | padding: 1.1rem 1.2rem; | ||
| 1698 | background: transparent; | ||
| 1699 | border: 0; | ||
| 1700 | border-radius: 0; | ||
| 1701 | font-size: 12.5px; | ||
| 1702 | line-height: 1.7; | ||
| 1703 | } | ||
| 1704 | .blog-prose .bw code { | ||
| 1705 | background: transparent; | ||
| 1706 | border: 0; | ||
| 1707 | padding: 0; | ||
| 1708 | font-size: inherit; | ||
| 1709 | } | ||
| 1710 | |||
| 1711 | .bw-head { | ||
| 1712 | display: flex; | ||
| 1713 | align-items: center; | ||
| 1714 | justify-content: space-between; | ||
| 1715 | gap: 0.75rem; | ||
| 1716 | flex-wrap: wrap; | ||
| 1717 | margin-bottom: 0.9rem; | ||
| 1718 | } | ||
| 1719 | .bw-head--stack { | ||
| 1720 | justify-content: flex-start; | ||
| 1721 | gap: 1.5rem; | ||
| 1722 | } | ||
| 1723 | .bw-eyebrow { | ||
| 1724 | font-family: var(--font-mono); | ||
| 1725 | font-size: 11px; | ||
| 1726 | font-weight: 500; | ||
| 1727 | text-transform: uppercase; | ||
| 1728 | letter-spacing: 0.08em; | ||
| 1729 | color: var(--color-ink-3); | ||
| 1730 | } | ||
| 1731 | .bw-foot { | ||
| 1732 | margin-top: 0.85rem; | ||
| 1733 | padding-top: 0.75rem; | ||
| 1734 | border-top: 1px solid var(--color-rule); | ||
| 1735 | font-size: 12.5px; | ||
| 1736 | line-height: 1.55; | ||
| 1737 | color: var(--color-ink-2); | ||
| 1738 | min-height: 2em; | ||
| 1739 | } | ||
| 1740 | .bw-mono-note { | ||
| 1741 | font-family: var(--font-mono); | ||
| 1742 | font-size: 11px; | ||
| 1743 | color: var(--color-ink-3); | ||
| 1744 | white-space: nowrap; | ||
| 1745 | } | ||
| 1746 | .bw-mono-inline { | ||
| 1747 | font-family: var(--font-mono); | ||
| 1748 | font-size: 0.92em; | ||
| 1749 | } | ||
| 1750 | .bw-dim { | ||
| 1751 | color: var(--color-ink-3); | ||
| 1752 | } | ||
| 1753 | /* <mark> reset: the invariant-fact highlight. */ | ||
| 1754 | .bw-hl { | ||
| 1755 | background: color-mix(in srgb, var(--color-accent) 7%, transparent); | ||
| 1756 | color: var(--color-ink); | ||
| 1757 | font-weight: 600; | ||
| 1758 | border-radius: 3px; | ||
| 1759 | padding: 0 2px; | ||
| 1760 | } | ||
| 1761 | |||
| 1762 | /* Segmented control */ | ||
| 1763 | .bw-seg { | ||
| 1764 | display: inline-flex; | ||
| 1765 | gap: 2px; | ||
| 1766 | padding: 2px; | ||
| 1767 | border: 1px solid var(--color-rule); | ||
| 1768 | border-radius: 8px; | ||
| 1769 | background: var(--color-surface-2); | ||
| 1770 | } | ||
| 1771 | .bw-seg__btn { | ||
| 1772 | font-family: var(--font-mono); | ||
| 1773 | font-size: 11.5px; | ||
| 1774 | padding: 4px 10px; | ||
| 1775 | border-radius: 6px; | ||
| 1776 | border: 1px solid transparent; | ||
| 1777 | color: var(--color-ink-2); | ||
| 1778 | cursor: pointer; | ||
| 1779 | white-space: nowrap; | ||
| 1780 | transition: | ||
| 1781 | color 0.15s ease, | ||
| 1782 | background 0.15s ease; | ||
| 1783 | } | ||
| 1784 | .bw-seg__btn:hover { | ||
| 1785 | color: var(--color-ink); | ||
| 1786 | } | ||
| 1787 | .bw-seg__btn[data-on="true"] { | ||
| 1788 | background: white; | ||
| 1789 | border-color: var(--color-rule); | ||
| 1790 | color: var(--color-ink); | ||
| 1791 | box-shadow: 0 1px 2px rgba(13, 13, 16, 0.05); | ||
| 1792 | } | ||
| 1793 | .bw-seg__btn:focus-visible, | ||
| 1794 | .bw-btn:focus-visible, | ||
| 1795 | .bw-map__cell:focus-visible { | ||
| 1796 | outline: 2px solid var(--color-accent); | ||
| 1797 | outline-offset: 2px; | ||
| 1798 | } | ||
| 1799 | |||
| 1800 | /* Secondary action button */ | ||
| 1801 | .bw-btn { | ||
| 1802 | font-size: 13px; | ||
| 1803 | font-weight: 500; | ||
| 1804 | padding: 0 13px; | ||
| 1805 | height: 30px; | ||
| 1806 | border: 1px solid var(--color-rule-strong); | ||
| 1807 | border-radius: 8px; | ||
| 1808 | background: white; | ||
| 1809 | color: var(--color-ink); | ||
| 1810 | cursor: pointer; | ||
| 1811 | white-space: nowrap; | ||
| 1812 | transition: | ||
| 1813 | border-color 0.15s ease, | ||
| 1814 | background 0.15s ease; | ||
| 1815 | } | ||
| 1816 | .bw-btn:hover { | ||
| 1817 | border-color: var(--color-ink-3); | ||
| 1818 | background: var(--color-surface-2); | ||
| 1819 | } | ||
| 1820 | .bw-btn:disabled { | ||
| 1821 | opacity: 0.5; | ||
| 1822 | cursor: default; | ||
| 1823 | } | ||
| 1824 | |||
| 1825 | /* Code-window pane inside a widget: drop the heavy marketing shadow. */ | ||
| 1826 | .bw-window { | ||
| 1827 | box-shadow: none; | ||
| 1828 | border-radius: 10px; | ||
| 1829 | } | ||
| 1830 | .bw-code { | ||
| 1831 | white-space: pre-wrap; | ||
| 1832 | word-break: break-word; | ||
| 1833 | } | ||
| 1834 | .bw-code--term { | ||
| 1835 | min-height: 200px; | ||
| 1836 | } | ||
| 1837 | |||
| 1838 | /* ── Compile pane pair (mcp-to-cli) ── */ | ||
| 1839 | .bw-pair { | ||
| 1840 | display: grid; | ||
| 1841 | grid-template-columns: 1fr; | ||
| 1842 | gap: 0.75rem; | ||
| 1843 | align-items: stretch; | ||
| 1844 | } | ||
| 1845 | @media (min-width: 720px) { | ||
| 1846 | .bw-pair { | ||
| 1847 | grid-template-columns: 1fr auto 1fr; | ||
| 1848 | } | ||
| 1849 | } | ||
| 1850 | .bw-pane { | ||
| 1851 | min-width: 0; | ||
| 1852 | display: flex; | ||
| 1853 | flex-direction: column; | ||
| 1854 | transition: opacity 0.3s ease; | ||
| 1855 | } | ||
| 1856 | .bw-pane[data-dim="true"] { | ||
| 1857 | opacity: 0.6; | ||
| 1858 | } | ||
| 1859 | .bw-pane__title { | ||
| 1860 | display: flex; | ||
| 1861 | align-items: baseline; | ||
| 1862 | justify-content: space-between; | ||
| 1863 | gap: 0.5rem; | ||
| 1864 | font-size: 13px; | ||
| 1865 | font-weight: 600; | ||
| 1866 | margin-bottom: 0.5rem; | ||
| 1867 | } | ||
| 1868 | .bw-pane__arrow { | ||
| 1869 | align-self: center; | ||
| 1870 | color: var(--color-ink-3); | ||
| 1871 | font-size: 18px; | ||
| 1872 | padding: 0 0.2rem; | ||
| 1873 | transform: rotate(90deg); | ||
| 1874 | } | ||
| 1875 | @media (min-width: 720px) { | ||
| 1876 | .bw-pane__arrow { | ||
| 1877 | transform: none; | ||
| 1878 | } | ||
| 1879 | } | ||
| 1880 | .bw-window--fill { | ||
| 1881 | flex: 1; | ||
| 1882 | } | ||
| 1883 | .bw-rows { | ||
| 1884 | border: 1px solid var(--color-rule); | ||
| 1885 | border-radius: 10px; | ||
| 1886 | overflow: hidden; | ||
| 1887 | flex: 1; | ||
| 1888 | } | ||
| 1889 | .bw-row { | ||
| 1890 | display: flex; | ||
| 1891 | align-items: baseline; | ||
| 1892 | gap: 0.6rem; | ||
| 1893 | padding: 0.42rem 0.75rem; | ||
| 1894 | border-top: 1px solid var(--color-rule); | ||
| 1895 | min-width: 0; | ||
| 1896 | } | ||
| 1897 | .bw-row:first-child { | ||
| 1898 | border-top: 0; | ||
| 1899 | } | ||
| 1900 | .bw-row__name { | ||
| 1901 | font-family: var(--font-mono); | ||
| 1902 | font-size: 12px; | ||
| 1903 | color: var(--color-ink); | ||
| 1904 | white-space: nowrap; | ||
| 1905 | } | ||
| 1906 | .bw-row__desc { | ||
| 1907 | flex: 1; | ||
| 1908 | min-width: 0; | ||
| 1909 | font-size: 12px; | ||
| 1910 | color: var(--color-ink-2); | ||
| 1911 | overflow: hidden; | ||
| 1912 | text-overflow: ellipsis; | ||
| 1913 | white-space: nowrap; | ||
| 1914 | } | ||
| 1915 | .bw-row--rest { | ||
| 1916 | font-family: var(--font-mono); | ||
| 1917 | font-size: 11.5px; | ||
| 1918 | color: var(--color-ink-3); | ||
| 1919 | } | ||
| 1920 | .bw-tag { | ||
| 1921 | font-family: var(--font-mono); | ||
| 1922 | font-size: 10px; | ||
| 1923 | text-transform: uppercase; | ||
| 1924 | letter-spacing: 0.06em; | ||
| 1925 | color: var(--color-ink-2); | ||
| 1926 | border: 1px solid var(--color-rule-strong); | ||
| 1927 | border-radius: 4px; | ||
| 1928 | padding: 1px 5px; | ||
| 1929 | white-space: nowrap; | ||
| 1930 | } | ||
| 1931 | .bw-fade-in { | ||
| 1932 | animation: bw-fade 0.25s ease both; | ||
| 1933 | } | ||
| 1934 | @keyframes bw-fade { | ||
| 1935 | from { | ||
| 1936 | opacity: 0; | ||
| 1937 | } | ||
| 1938 | to { | ||
| 1939 | opacity: 1; | ||
| 1940 | } | ||
| 1941 | } | ||
| 1942 | |||
| 1943 | /* ── Context flood (2×2) ── */ | ||
| 1944 | .bw-axis { | ||
| 1945 | display: flex; | ||
| 1946 | flex-direction: column; | ||
| 1947 | gap: 0.35rem; | ||
| 1948 | } | ||
| 1949 | .bw-axis__label { | ||
| 1950 | font-family: var(--font-mono); | ||
| 1951 | font-size: 11px; | ||
| 1952 | font-weight: 500; | ||
| 1953 | text-transform: uppercase; | ||
| 1954 | letter-spacing: 0.08em; | ||
| 1955 | color: var(--color-ink-3); | ||
| 1956 | } | ||
| 1957 | .bw-flood { | ||
| 1958 | display: grid; | ||
| 1959 | grid-template-columns: 1fr; | ||
| 1960 | gap: 1.25rem; | ||
| 1961 | align-items: center; | ||
| 1962 | margin-bottom: 1rem; | ||
| 1963 | } | ||
| 1964 | @media (min-width: 640px) { | ||
| 1965 | .bw-flood { | ||
| 1966 | grid-template-columns: 1fr 200px; | ||
| 1967 | } | ||
| 1968 | } | ||
| 1969 | .bw-flood__rows { | ||
| 1970 | display: flex; | ||
| 1971 | flex-direction: column; | ||
| 1972 | gap: 0.7rem; | ||
| 1973 | min-width: 0; | ||
| 1974 | } | ||
| 1975 | .bw-flood__meta { | ||
| 1976 | display: flex; | ||
| 1977 | align-items: baseline; | ||
| 1978 | justify-content: space-between; | ||
| 1979 | gap: 0.6rem; | ||
| 1980 | margin-bottom: 0.3rem; | ||
| 1981 | font-size: 12.5px; | ||
| 1982 | } | ||
| 1983 | .bw-flood__name { | ||
| 1984 | color: var(--color-ink-2); | ||
| 1985 | } | ||
| 1986 | .bw-flood__tok { | ||
| 1987 | font-family: var(--font-mono); | ||
| 1988 | font-size: 11px; | ||
| 1989 | color: var(--color-ink-3); | ||
| 1990 | white-space: nowrap; | ||
| 1991 | } | ||
| 1992 | .bw-flood__track { | ||
| 1993 | height: 12px; | ||
| 1994 | border-radius: 3px; | ||
| 1995 | background: var(--color-surface-2); | ||
| 1996 | border: 1px solid var(--color-rule); | ||
| 1997 | overflow: hidden; | ||
| 1998 | } | ||
| 1999 | .bw-flood__fill { | ||
| 2000 | height: 100%; | ||
| 2001 | min-width: 3px; | ||
| 2002 | background: rgba(13, 13, 16, 0.45); | ||
| 2003 | transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1); | ||
| 2004 | } | ||
| 2005 | .bw-flood__fill[data-flood="true"] { | ||
| 2006 | background: repeating-linear-gradient( | ||
| 2007 | -45deg, | ||
| 2008 | rgba(13, 13, 16, 0.55) 0 3px, | ||
| 2009 | rgba(13, 13, 16, 0.18) 3px 7px | ||
| 2010 | ); | ||
| 2011 | } | ||
| 2012 | .bw-flood__total { | ||
| 2013 | text-align: center; | ||
| 2014 | } | ||
| 2015 | .bw-flood__num { | ||
| 2016 | font-family: var(--font-mono); | ||
| 2017 | font-size: 27px; | ||
| 2018 | font-weight: 600; | ||
| 2019 | font-variant-numeric: tabular-nums; | ||
| 2020 | letter-spacing: -0.02em; | ||
| 2021 | color: var(--color-ink); | ||
| 2022 | } | ||
| 2023 | .bw-flood__cap { | ||
| 2024 | font-size: 12px; | ||
| 2025 | color: var(--color-ink-2); | ||
| 2026 | margin-top: 0.15rem; | ||
| 2027 | } | ||
| 2028 | .bw-badge { | ||
| 2029 | display: inline-block; | ||
| 2030 | margin-top: 0.55rem; | ||
| 2031 | font-family: var(--font-mono); | ||
| 2032 | font-size: 10.5px; | ||
| 2033 | text-transform: uppercase; | ||
| 2034 | letter-spacing: 0.07em; | ||
| 2035 | color: var(--color-ink-2); | ||
| 2036 | border: 1px solid var(--color-rule-strong); | ||
| 2037 | border-radius: 999px; | ||
| 2038 | padding: 2px 9px; | ||
| 2039 | visibility: hidden; | ||
| 2040 | } | ||
| 2041 | .bw-badge[data-show="true"] { | ||
| 2042 | visibility: visible; | ||
| 2043 | } | ||
| 2044 | .bw-map { | ||
| 2045 | display: grid; | ||
| 2046 | grid-template-columns: repeat(2, minmax(0, 1fr)); | ||
| 2047 | gap: 0.5rem; | ||
| 2048 | } | ||
| 2049 | .bw-map__cell { | ||
| 2050 | display: flex; | ||
| 2051 | align-items: baseline; | ||
| 2052 | justify-content: space-between; | ||
| 2053 | gap: 0.5rem; | ||
| 2054 | padding: 0.5rem 0.7rem; | ||
| 2055 | border: 1px solid var(--color-rule); | ||
| 2056 | border-radius: 8px; | ||
| 2057 | background: white; | ||
| 2058 | cursor: pointer; | ||
| 2059 | text-align: left; | ||
| 2060 | transition: | ||
| 2061 | border-color 0.15s ease, | ||
| 2062 | background 0.15s ease; | ||
| 2063 | } | ||
| 2064 | .bw-map__cell:hover { | ||
| 2065 | border-color: var(--color-rule-strong); | ||
| 2066 | } | ||
| 2067 | .bw-map__cell[data-on="true"] { | ||
| 2068 | border-color: var(--color-ink); | ||
| 2069 | background: var(--color-surface-2); | ||
| 2070 | } | ||
| 2071 | .bw-map__label { | ||
| 2072 | font-size: 12px; | ||
| 2073 | font-weight: 500; | ||
| 2074 | color: var(--color-ink); | ||
| 2075 | } | ||
| 2076 | .bw-map__val { | ||
| 2077 | font-family: var(--font-mono); | ||
| 2078 | font-size: 11px; | ||
| 2079 | color: var(--color-ink-3); | ||
| 2080 | white-space: nowrap; | ||
| 2081 | } | ||
| 2082 | |||
| 2083 | /* ── Harness spread bars + paired ratios ── */ | ||
| 2084 | .bw-bars { | ||
| 2085 | display: flex; | ||
| 2086 | flex-direction: column; | ||
| 2087 | gap: 0.45rem; | ||
| 2088 | } | ||
| 2089 | .bw-bars__row { | ||
| 2090 | display: grid; | ||
| 2091 | grid-template-columns: 92px 1fr 64px; | ||
| 2092 | align-items: center; | ||
| 2093 | gap: 0.6rem; | ||
| 2094 | } | ||
| 2095 | .bw-bars__name { | ||
| 2096 | font-family: var(--font-mono); | ||
| 2097 | font-size: 11.5px; | ||
| 2098 | color: var(--color-ink); | ||
| 2099 | overflow: hidden; | ||
| 2100 | text-overflow: ellipsis; | ||
| 2101 | white-space: nowrap; | ||
| 2102 | } | ||
| 2103 | .bw-bars__track { | ||
| 2104 | height: 13px; | ||
| 2105 | background: var(--color-surface-2); | ||
| 2106 | border-radius: 3px; | ||
| 2107 | overflow: hidden; | ||
| 2108 | } | ||
| 2109 | .bw-bars__fill { | ||
| 2110 | height: 100%; | ||
| 2111 | background: rgba(13, 13, 16, 0.55); | ||
| 2112 | border-radius: 3px; | ||
| 2113 | } | ||
| 2114 | .bw-bars__val { | ||
| 2115 | font-family: var(--font-mono); | ||
| 2116 | font-size: 11px; | ||
| 2117 | font-variant-numeric: tabular-nums; | ||
| 2118 | color: var(--color-ink-3); | ||
| 2119 | text-align: right; | ||
| 2120 | white-space: nowrap; | ||
| 2121 | } | ||
| 2122 | .bw-quiz { | ||
| 2123 | display: flex; | ||
| 2124 | align-items: center; | ||
| 2125 | justify-content: space-between; | ||
| 2126 | gap: 0.9rem; | ||
| 2127 | flex-wrap: wrap; | ||
| 2128 | margin-top: 1rem; | ||
| 2129 | padding-top: 0.85rem; | ||
| 2130 | border-top: 1px solid var(--color-rule); | ||
| 2131 | font-size: 13.5px; | ||
| 2132 | color: var(--color-ink-2); | ||
| 2133 | } | ||
| 2134 | .bw-quiz--answer { | ||
| 2135 | color: var(--color-ink-2); | ||
| 2136 | } | ||
| 2137 | .bw-quiz strong { | ||
| 2138 | color: var(--color-ink); | ||
| 2139 | font-weight: 600; | ||
| 2140 | } | ||
| 2141 | .bw-quiz > span { | ||
| 2142 | flex: 1; | ||
| 2143 | min-width: 240px; | ||
| 2144 | } | ||
| 2145 | .bw-ratio { | ||
| 2146 | display: flex; | ||
| 2147 | flex-direction: column; | ||
| 2148 | gap: 0.55rem; | ||
| 2149 | } | ||
| 2150 | .bw-ratio__row { | ||
| 2151 | display: grid; | ||
| 2152 | grid-template-columns: 150px 1fr; | ||
| 2153 | align-items: center; | ||
| 2154 | gap: 0.6rem; | ||
| 2155 | } | ||
| 2156 | .bw-ratio__row--axis { | ||
| 2157 | margin-bottom: 0.1rem; | ||
| 2158 | } | ||
| 2159 | .bw-ratio__name { | ||
| 2160 | font-family: var(--font-mono); | ||
| 2161 | font-size: 11.5px; | ||
| 2162 | color: var(--color-ink); | ||
| 2163 | white-space: nowrap; | ||
| 2164 | overflow: hidden; | ||
| 2165 | text-overflow: ellipsis; | ||
| 2166 | } | ||
| 2167 | .bw-ratio__val { | ||
| 2168 | color: var(--color-ink-3); | ||
| 2169 | } | ||
| 2170 | .bw-ratio__axis { | ||
| 2171 | position: relative; | ||
| 2172 | height: 14px; | ||
| 2173 | } | ||
| 2174 | .bw-ratio__axis span { | ||
| 2175 | position: absolute; | ||
| 2176 | transform: translateX(-50%); | ||
| 2177 | font-family: var(--font-mono); | ||
| 2178 | font-size: 10px; | ||
| 2179 | color: var(--color-ink-3); | ||
| 2180 | white-space: nowrap; | ||
| 2181 | } | ||
| 2182 | .bw-ratio__track { | ||
| 2183 | position: relative; | ||
| 2184 | height: 18px; | ||
| 2185 | background: var(--color-surface-2); | ||
| 2186 | border-radius: 3px; | ||
| 2187 | } | ||
| 2188 | /* The 1× line every track shares, positioned by --one-x on .bw-ratio. */ | ||
| 2189 | .bw-ratio__track::after { | ||
| 2190 | content: ""; | ||
| 2191 | position: absolute; | ||
| 2192 | top: -2px; | ||
| 2193 | bottom: -2px; | ||
| 2194 | left: var(--one-x); | ||
| 2195 | width: 1px; | ||
| 2196 | background: var(--color-rule-strong); | ||
| 2197 | } | ||
| 2198 | .bw-ratio__marker { | ||
| 2199 | position: absolute; | ||
| 2200 | top: 50%; | ||
| 2201 | transform: translate(-50%, -50%); | ||
| 2202 | width: 9px; | ||
| 2203 | height: 9px; | ||
| 2204 | border-radius: 999px; | ||
| 2205 | background: white; | ||
| 2206 | border: 2px solid var(--color-ink-3); | ||
| 2207 | z-index: 1; | ||
| 2208 | } | ||
| 2209 | .bw-ratio__marker[data-cheaper="true"] { | ||
| 2210 | background: var(--color-ink); | ||
| 2211 | border-color: var(--color-ink); | ||
| 2212 | } | ||
| 2213 | .bw-ratio__legend { | ||
| 2214 | display: flex; | ||
| 2215 | justify-content: space-between; | ||
| 2216 | font-family: var(--font-mono); | ||
| 2217 | font-size: 10px; | ||
| 2218 | text-transform: uppercase; | ||
| 2219 | letter-spacing: 0.06em; | ||
| 2220 | color: var(--color-ink-3); | ||
| 2221 | margin-top: 0.2rem; | ||
| 2222 | } | ||
| 2223 | |||
| 2224 | /* ── Install race lanes ── */ | ||
| 2225 | .bw-lanes { | ||
| 2226 | display: grid; | ||
| 2227 | grid-template-columns: 1fr; | ||
| 2228 | gap: 0.75rem; | ||
| 2229 | } | ||
| 2230 | @media (min-width: 720px) { | ||
| 2231 | .bw-lanes { | ||
| 2232 | grid-template-columns: repeat(3, minmax(0, 1fr)); | ||
| 2233 | } | ||
| 2234 | } | ||
| 2235 | .bw-lane { | ||
| 2236 | border: 1px solid var(--color-rule); | ||
| 2237 | border-radius: 10px; | ||
| 2238 | padding: 0.75rem 0.85rem 0.6rem; | ||
| 2239 | display: flex; | ||
| 2240 | flex-direction: column; | ||
| 2241 | transition: border-color 0.2s ease; | ||
| 2242 | } | ||
| 2243 | .bw-lane[data-done="true"] { | ||
| 2244 | border-color: var(--color-rule-strong); | ||
| 2245 | } | ||
| 2246 | .bw-lane__head { | ||
| 2247 | display: flex; | ||
| 2248 | align-items: baseline; | ||
| 2249 | gap: 0.5rem; | ||
| 2250 | margin-bottom: 0.65rem; | ||
| 2251 | } | ||
| 2252 | .bw-lane__title { | ||
| 2253 | font-family: var(--font-mono); | ||
| 2254 | font-size: 12px; | ||
| 2255 | font-weight: 600; | ||
| 2256 | text-transform: uppercase; | ||
| 2257 | letter-spacing: 0.06em; | ||
| 2258 | } | ||
| 2259 | .bw-lane__sub { | ||
| 2260 | font-size: 11.5px; | ||
| 2261 | color: var(--color-ink-3); | ||
| 2262 | } | ||
| 2263 | .bw-lane__steps { | ||
| 2264 | display: flex; | ||
| 2265 | flex-direction: column; | ||
| 2266 | gap: 0.42rem; | ||
| 2267 | flex: 1; | ||
| 2268 | } | ||
| 2269 | .bw-step { | ||
| 2270 | display: flex; | ||
| 2271 | align-items: baseline; | ||
| 2272 | gap: 0.5rem; | ||
| 2273 | transition: opacity 0.25s ease; | ||
| 2274 | } | ||
| 2275 | .bw-step[data-state="off"] { | ||
| 2276 | opacity: 0.32; | ||
| 2277 | } | ||
| 2278 | .bw-step__dot { | ||
| 2279 | width: 7px; | ||
| 2280 | height: 7px; | ||
| 2281 | border-radius: 999px; | ||
| 2282 | flex-shrink: 0; | ||
| 2283 | border: 1.5px solid var(--color-rule-strong); | ||
| 2284 | background: white; | ||
| 2285 | align-self: center; | ||
| 2286 | } | ||
| 2287 | .bw-step[data-state="on"] .bw-step__dot, | ||
| 2288 | .bw-step[data-state="done"] .bw-step__dot { | ||
| 2289 | background: var(--color-ink); | ||
| 2290 | border-color: var(--color-ink); | ||
| 2291 | } | ||
| 2292 | .bw-step[data-state="bad"] .bw-step__dot { | ||
| 2293 | background: white; | ||
| 2294 | border-color: var(--color-ink); | ||
| 2295 | } | ||
| 2296 | .bw-step__text { | ||
| 2297 | font-family: var(--font-mono); | ||
| 2298 | font-size: 11.5px; | ||
| 2299 | color: var(--color-ink-2); | ||
| 2300 | min-width: 0; | ||
| 2301 | } | ||
| 2302 | .bw-step[data-state="bad"] .bw-step__text { | ||
| 2303 | text-decoration: underline; | ||
| 2304 | text-decoration-style: wavy; | ||
| 2305 | text-decoration-color: var(--color-ink-3); | ||
| 2306 | text-underline-offset: 3px; | ||
| 2307 | } | ||
| 2308 | .bw-step__ok { | ||
| 2309 | margin-left: auto; | ||
| 2310 | font-family: var(--font-mono); | ||
| 2311 | font-size: 10px; | ||
| 2312 | text-transform: uppercase; | ||
| 2313 | letter-spacing: 0.06em; | ||
| 2314 | color: var(--color-ink); | ||
| 2315 | border: 1px solid var(--color-rule-strong); | ||
| 2316 | border-radius: 4px; | ||
| 2317 | padding: 0 4px; | ||
| 2318 | white-space: nowrap; | ||
| 2319 | } | ||
| 2320 | .bw-lane__tally { | ||
| 2321 | margin-top: 0.6rem; | ||
| 2322 | padding-top: 0.5rem; | ||
| 2323 | border-top: 1px solid var(--color-rule); | ||
| 2324 | font-family: var(--font-mono); | ||
| 2325 | font-size: 10.5px; | ||
| 2326 | color: var(--color-ink-3); | ||
| 2327 | min-height: 1.6em; | ||
| 2328 | } | ||
| 2329 | |||
| 2330 | @media (prefers-reduced-motion: reduce) { | ||
| 2331 | .bw-flood__fill, | ||
| 2332 | .bw-pane, | ||
| 2333 | .bw-step, | ||
| 2334 | .bw-seg__btn, | ||
| 2335 | .bw-btn, | ||
| 2336 | .bw-map__cell, | ||
| 2337 | .bw-lane { | ||
| 2338 | transition: none; | ||
| 2339 | } | ||
| 2340 | .bw-fade-in { | ||
| 2341 | animation: none; | ||
| 2342 | } | ||
| 2343 | } | ||
| @@ -330,10 +330,11 @@ · 329 unmodified lines | |||
| 330 | "version": "0.0.5", | 330 | "version": "0.0.5", |
| 331 | "dependencies": { | 331 | "dependencies": { |
| 332 | "@astrojs/cloudflare": "^13.0.0", | 332 | "@astrojs/cloudflare": "^13.0.0", |
| 333 | "@astrojs/mdx": "^6.0.3", | ||
| 333 | "@astrojs/react": "^5.0.4", | 334 | "@astrojs/react": "^5.0.4", |
| 334 | "@executor-js/react": "workspace:*", | 335 | "@executor-js/react": "workspace:*", |
| 335 | "@tailwindcss/vite": "^4.2.2", | 336 | "@tailwindcss/vite": "^4.2.2", |
| 336 | "astro": "^6.1.3", | 337 | "astro": "^6.4.8", |
| 337 | "clsx": "^2.1.1", | 338 | "clsx": "^2.1.1", |
| 338 | "motion": "^12.38.0", | 339 | "motion": "^12.38.0", |
| 339 | "posthog-js": "^1.372.5", | 340 | "posthog-js": "^1.372.5", |
| @@ -1312,17 +1313,19 @@ · 972 unmodified lines | |||
| 1312 | 1313 | ||
| 1313 | "@astrojs/cloudflare": ["@astrojs/cloudflare@13.1.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/underscore-redirects": "1.0.3", "@cloudflare/vite-plugin": "^1.25.6", "piccolore": "^0.1.3", "tinyglobby": "^0.2.15", "vite": "^7.3.1" }, "peerDependencies": { "astro": "^6.0.0", "wrangler": "^4.61.1" } }, "sha512-Ogl8p4MifPMHbpHKJL78eqEL+I3wbHrYmo83P/FkKZQ9VzzKi2A1RjnbaiiPAwrA8xQOqIUo4zlN7+Mse0aJAQ=="], | 1314 | "@astrojs/cloudflare": ["@astrojs/cloudflare@13.1.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/underscore-redirects": "1.0.3", "@cloudflare/vite-plugin": "^1.25.6", "piccolore": "^0.1.3", "tinyglobby": "^0.2.15", "vite": "^7.3.1" }, "peerDependencies": { "astro": "^6.0.0", "wrangler": "^4.61.1" } }, "sha512-Ogl8p4MifPMHbpHKJL78eqEL+I3wbHrYmo83P/FkKZQ9VzzKi2A1RjnbaiiPAwrA8xQOqIUo4zlN7+Mse0aJAQ=="], |
| 1314 | 1315 | ||
| 1315 | "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="], | 1316 | "@astrojs/compiler": ["@astrojs/compiler@4.0.0", "", {}, "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA=="], |
| 1316 | 1317 | ||
| 1317 | "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="], | 1318 | "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="], |
| 1318 | 1319 | ||
| 1319 | "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.1.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-P+HnCsu2js3BoTc8kFmu+E9gOcFeMdPris75g+Zl4sY8+bBRbSQV6xzcBDbZ27eE7yBGEGQoqjpChx+KJYIPYQ=="], | 1320 | "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.2.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.0", "@astrojs/prism": "4.0.2", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-+YxmVQu1Bd+MFfSzjq1rOJvD9+nIOJzz5YIIhdIH01RrxRkKbyKoEgyIqP3yv51MhzMDgd79QaPv+kCVPT8vHw=="], |
| 1320 | 1321 | ||
| 1321 | "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="], | 1322 | "@astrojs/mdx": ["@astrojs/mdx@6.0.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.0", "@astrojs/markdown-remark": "7.2.0", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "@astrojs/markdown-satteri": "0.3.0", "astro": "^6.4.0" }, "optionalPeers": ["@astrojs/markdown-satteri"] }, "sha512-+4P3ZvwsRAqAbBgY+uZMewFo3ficlIBPZfu/Luk+v4ia/ZOuFhpsw7r+7672uT2Fc1UPdp7yW0eU5egvSq0wbw=="], |
| 1323 | |||
| 1324 | "@astrojs/prism": ["@astrojs/prism@4.0.2", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA=="], | ||
| 1322 | 1325 | ||
| 1323 | "@astrojs/react": ["@astrojs/react@5.0.4", "", { "dependencies": { "@astrojs/internal-helpers": "0.9.0", "@vitejs/plugin-react": "^5.2.0", "devalue": "^5.6.4", "ultrahtml": "^1.6.0", "vite": "^7.3.2" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-yDNE4VnKOzCjH9dCBi7pT4F6kpI3M9TkS+uxnCB0sGIS6t5vKonOY+Hs/UUnSajJGT5jeBRfpI9IQp+r/n1fBA=="], | 1326 | "@astrojs/react": ["@astrojs/react@5.0.4", "", { "dependencies": { "@astrojs/internal-helpers": "0.9.0", "@vitejs/plugin-react": "^5.2.0", "devalue": "^5.6.4", "ultrahtml": "^1.6.0", "vite": "^7.3.2" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-yDNE4VnKOzCjH9dCBi7pT4F6kpI3M9TkS+uxnCB0sGIS6t5vKonOY+Hs/UUnSajJGT5jeBRfpI9IQp+r/n1fBA=="], |
| 1324 | 1327 | ||
| 1325 | "@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="], | 1328 | "@astrojs/telemetry": ["@astrojs/telemetry@3.3.2", "", { "dependencies": { "ci-info": "^4.4.0", "dset": "^3.1.4", "is-docker": "^4.0.0", "is-wsl": "^3.1.1", "which-pm-runs": "^1.1.0" } }, "sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ=="], |
| 1326 | 1329 | ||
| 1327 | "@astrojs/underscore-redirects": ["@astrojs/underscore-redirects@1.0.3", "", {}, "sha512-cxnGSw+sJigBLdX4TMSZKkzV6C3gMLJMucDk2W+n281Xhie68T2/9f1+1NMNDCZsc5i0FED7Qt5I10g2O9wtZg=="], | 1330 | "@astrojs/underscore-redirects": ["@astrojs/underscore-redirects@1.0.3", "", {}, "sha512-cxnGSw+sJigBLdX4TMSZKkzV6C3gMLJMucDk2W+n281Xhie68T2/9f1+1NMNDCZsc5i0FED7Qt5I10g2O9wtZg=="], |
| 1328 | 1331 | ||
| @@ -3366,7 +3369,7 @@ · 2037 unmodified lines | |||
| 3366 | 3369 | ||
| 3367 | "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], | 3370 | "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], |
| 3368 | 3371 | ||
| 3369 | "astro": ["astro@6.1.7", "", { "dependencies": { "@astrojs/compiler": "^3.0.1", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.1.0", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-pvZysIUV2C2nRv8N7cXAkCLcfDQz/axAxF09SqiTz1B+xnvbhy6KzL2I6J15ZBXk8k0TfMD75dJ151QyQmAqZA=="], | 3372 | "astro": ["astro@6.4.8", "", { "dependencies": { "@astrojs/compiler": "^4.0.0", "@astrojs/internal-helpers": "0.10.0", "@astrojs/markdown-remark": "7.2.0", "@astrojs/telemetry": "3.3.2", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.8.1", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.5", "vfile": "^6.0.3", "vite": "^7.3.2", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "./bin/astro.mjs" } }, "sha512-KK5lX90uU9EeVaTjINyj3sy9/NFXVa59aowaqbWBDDKLXZh4rr7GwIaCFYVetE22MJtsCNFerQXn0vlCLmpP/Q=="], |
| 3370 | 3373 | ||
| 3371 | "async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="], | 3374 | "async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="], |
| 3372 | 3375 | ||
| @@ -3808,8 +3811,6 @@ · 435 unmodified lines | |||
| 3808 | 3811 | ||
| 3809 | "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], | 3812 | "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], |
| 3810 | 3813 | ||
| 3811 | "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], | ||
| 3812 | |||
| 3813 | "dmg-builder": ["dmg-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg=="], | 3814 | "dmg-builder": ["dmg-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg=="], |
| 3814 | 3815 | ||
| 3815 | "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], | 3816 | "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], |
| @@ -4328,7 +4329,7 @@ · 512 unmodified lines | |||
| 4328 | 4329 | ||
| 4329 | "is-descriptor": ["is-descriptor@1.0.3", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw=="], | 4330 | "is-descriptor": ["is-descriptor@1.0.3", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw=="], |
| 4330 | 4331 | ||
| 4331 | "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], | 4332 | "is-docker": ["is-docker@4.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA=="], |
| 4332 | 4333 | ||
| 4333 | "is-electron": ["is-electron@2.2.2", "", {}, "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="], | 4334 | "is-electron": ["is-electron@2.2.2", "", {}, "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="], |
| 4334 | 4335 | ||
| @@ -5614,8 +5615,6 @@ · 1279 unmodified lines | |||
| 5614 | 5615 | ||
| 5615 | "ts-md5": ["ts-md5@2.0.1", "", {}, "sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w=="], | 5616 | "ts-md5": ["ts-md5@2.0.1", "", {}, "sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w=="], |
| 5616 | 5617 | ||
| 5617 | "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], | ||
| 5618 | |||
| 5619 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], | 5618 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], |
| 5620 | 5619 | ||
| 5621 | "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], | 5620 | "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], |
| @@ -5864,6 +5863,14 @@ · 242 unmodified lines | |||
| 5864 | 5863 | ||
| 5865 | "@astrojs/cloudflare/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], | 5864 | "@astrojs/cloudflare/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], |
| 5866 | 5865 | ||
| 5866 | "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.0", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.1.1", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw=="], | ||
| 5867 | |||
| 5868 | "@astrojs/mdx/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.0", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.1.1", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw=="], | ||
| 5869 | |||
| 5870 | "@astrojs/mdx/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], | ||
| 5871 | |||
| 5872 | "@astrojs/mdx/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="], | ||
| 5873 | |||
| 5867 | "@astrojs/react/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.9.0", "", { "dependencies": { "picomatch": "^4.0.4" } }, "sha512-GdYkzR26re8izmyYlBqf4z2s7zNngmWLFuxw0UKiPNqHraZGS6GKWIwSHgS22RDlu2ePFJ8bzmpBcUszut/SDg=="], | 5874 | "@astrojs/react/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.9.0", "", { "dependencies": { "picomatch": "^4.0.4" } }, "sha512-GdYkzR26re8izmyYlBqf4z2s7zNngmWLFuxw0UKiPNqHraZGS6GKWIwSHgS22RDlu2ePFJ8bzmpBcUszut/SDg=="], |
| 5868 | 5875 | ||
| 5869 | "@astrojs/react/@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], | 5876 | "@astrojs/react/@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], |
| @@ -6434,14 +6441,22 @@ · 564 unmodified lines | |||
| 6434 | 6441 | ||
| 6435 | "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], | 6442 | "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], |
| 6436 | 6443 | ||
| 6437 | "astro/@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="], | 6444 | "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.0", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.1.1", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw=="], |
| 6438 | 6445 | ||
| 6439 | "astro/package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], | 6446 | "astro/devalue": ["devalue@5.9.1", "", {}, "sha512-+17vil3EVQRzvtDJSFuTWEb8XJRvXqAiV3qZyQWD398QeXUa6CxsUyMdD1fxzEhUrd4FojitFz7lhIHBTlV4fw=="], |
| 6447 | |||
| 6448 | "astro/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="], | ||
| 6449 | |||
| 6450 | "astro/get-tsconfig": ["get-tsconfig@5.0.0-beta.4", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ=="], | ||
| 6440 | 6451 | ||
| 6441 | "astro/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], | 6452 | "astro/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], |
| 6453 | |||
| 6454 | "astro/package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], | ||
| 6442 | 6455 | ||
| 6443 | "astro/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], | 6456 | "astro/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], |
| 6444 | 6457 | ||
| 6458 | "astro/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], | ||
| 6459 | |||
| 6445 | "atlas-api-client/rxjs": ["rxjs@5.5.12", "", { "dependencies": { "symbol-observable": "1.0.1" } }, "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw=="], | 6460 | "atlas-api-client/rxjs": ["rxjs@5.5.12", "", { "dependencies": { "symbol-observable": "1.0.1" } }, "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw=="], |
| 6446 | 6461 | ||
| 6447 | "atmn/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], | 6462 | "atmn/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], |
| @@ -6594,6 +6609,8 @@ · 146 unmodified lines | |||
| 6594 | 6609 | ||
| 6595 | "ink-confirm-input/ink-text-input": ["ink-text-input@3.3.0", "", { "dependencies": { "chalk": "^3.0.0", "prop-types": "^15.5.10" }, "peerDependencies": { "ink": "^2.0.0", "react": "^16.5.2" } }, "sha512-gO4wrOf2ie3YuEARTIwGlw37lMjFn3Gk6CKIDrMlHb46WFMagZU7DplohjM24zynlqfnXA5UDEIfC2NBcvD8kg=="], | 6610 | "ink-confirm-input/ink-text-input": ["ink-text-input@3.3.0", "", { "dependencies": { "chalk": "^3.0.0", "prop-types": "^15.5.10" }, "peerDependencies": { "ink": "^2.0.0", "react": "^16.5.2" } }, "sha512-gO4wrOf2ie3YuEARTIwGlw37lMjFn3Gk6CKIDrMlHb46WFMagZU7DplohjM24zynlqfnXA5UDEIfC2NBcvD8kg=="], |
| 6596 | 6611 | ||
| 6612 | "is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], | ||
| 6613 | |||
| 6597 | "jake/async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], | 6614 | "jake/async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], |
| 6598 | 6615 | ||
| 6599 | "jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], | 6616 | "jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], |
| @@ -6798,6 +6815,10 @@ · 198 unmodified lines | |||
| 6798 | 6815 | ||
| 6799 | "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], | 6816 | "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], |
| 6800 | 6817 | ||
| 6818 | "@astrojs/markdown-remark/@astrojs/internal-helpers/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], | ||
| 6819 | |||
| 6820 | "@astrojs/mdx/@astrojs/internal-helpers/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], | ||
| 6821 | |||
| 6801 | "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], | 6822 | "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], |
| 6802 | 6823 | ||
| 6803 | "@azure/identity/@azure/msal-node/@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="], | 6824 | "@azure/identity/@azure/msal-node/@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="], |
| @@ -7266,12 +7287,6 @@ · 462 unmodified lines | |||
| 7266 | 7287 | ||
| 7267 | "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], | 7288 | "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], |
| 7268 | 7289 | ||
| 7269 | "astro/@clack/prompts/@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="], | ||
| 7270 | |||
| 7271 | "astro/@clack/prompts/fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="], | ||
| 7272 | |||
| 7273 | "astro/@clack/prompts/fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="], | ||
| 7274 | |||
| 7275 | "broadcast-channel/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], | 7290 | "broadcast-channel/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], |
| 7276 | 7291 | ||
| 7277 | "broadcast-channel/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="], | 7292 | "broadcast-channel/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="], |
| @@ -7766,8 +7781,6 @@ · 488 unmodified lines | |||
| 7766 | 7781 | ||
| 7767 | "agents/yargs/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], | 7782 | "agents/yargs/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], |
| 7768 | 7783 | ||
| 7769 | "astro/@clack/prompts/fast-string-width/fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="], | ||
| 7770 | |||
| 7771 | "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], | 7784 | "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], |
| 7772 | 7785 | ||
| 7773 | "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], | 7786 | "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], |