History

marketing: interactive blog post on MCP/CLI/API tool calling

Rhys Sullivan committed 2 months ago

Commit 90f79651 parent c11bef213 files · +1683 −28Browse files

Unified · Split

13 files
  1. apps/marketing/astro.config.mjs +2 −1
  2. apps/marketing/package.json +2 −1
  3. apps/marketing/public/llms.txt +5 −0
  4. apps/marketing/src/components/blog/context-flood-demo.tsx +180 −0
  5. apps/marketing/src/components/blog/harness-spread-demo.tsx +158 −0
  6. apps/marketing/src/components/blog/install-race-demo.tsx +134 −0
  7. apps/marketing/src/components/blog/mcp-to-cli-demo.tsx +155 −0
  8. apps/marketing/src/components/blog/same-call-demo.tsx +173 −0
  9. apps/marketing/src/components/blog/shared.ts +72 −0
  10. apps/marketing/src/content.config.ts +5 −4
  11. apps/marketing/src/content/blog/mcp-cli-same-thing.mdx +94 −0
  12. apps/marketing/src/styles/global.css +668 −0
  13. bun.lock +35 −22
apps/marketing/astro.config.mjs+2−1
@@ -5,6 +5,7 @@ import { unstable_readConfig } from "wrangler"; · 4 unmodified lines
55
66 import tailwindcss from "@tailwindcss/vite";
77 import react from "@astrojs/react";
8+import mdx from "@astrojs/mdx";
89
910 import cloudflare from "@astrojs/cloudflare";
1011
@@ -29,7 +30,7 @@ const wranglerPublicDefine = () => { · 18 unmodified lines
2930 export default defineConfig({
3031 site: "https://executor.sh",
3132 output: "server",
32− integrations: [react()],
33+ integrations: [react(), mdx()],
3334 vite: {
3435 plugins: [tailwindcss()],
3536 define: wranglerPublicDefine(),
apps/marketing/package.json+2−1
@@ -15,10 +15,11 @@ · 14 unmodified lines
1515 },
1616 "dependencies": {
1717 "@astrojs/cloudflare": "^13.0.0",
18+ "@astrojs/mdx": "^6.0.3",
1819 "@astrojs/react": "^5.0.4",
1920 "@executor-js/react": "workspace:*",
2021 "@tailwindcss/vite": "^4.2.2",
21− "astro": "^6.1.3",
22+ "astro": "^6.4.8",
2223 "clsx": "^2.1.1",
2324 "motion": "^12.38.0",
2425 "posthog-js": "^1.372.5",
apps/marketing/public/llms.txt+5−0
@@ -23,6 +23,11 @@ Run it your way: local CLI, a desktop app, hosted Executor Cloud, or self-hosted · 22 unmodified lines
2323 - [Pricing](https://executor.sh/#pricing): plans for Executor Cloud.
2424 - [Install](https://executor.sh/#install): install the CLI and connect your first agent.
2525
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+
2631 ## Source
2732
2833 - [GitHub](https://github.com/UsefulSoftwareCo/executor): source for the integration layer, plugins, and hosts.
apps/marketing/src/components/blog/context-flood-demo.tsx+180−0
@@ -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+}
apps/marketing/src/components/blog/harness-spread-demo.tsx+158−0
@@ -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+}
apps/marketing/src/components/blog/install-race-demo.tsx+134−0
@@ -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+}
apps/marketing/src/components/blog/mcp-to-cli-demo.tsx+155−0
@@ -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+}
apps/marketing/src/components/blog/same-call-demo.tsx+173−0
@@ -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+}
apps/marketing/src/components/blog/shared.ts+72−0
@@ -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+}
apps/marketing/src/content.config.ts+5−4
@@ -1,11 +1,12 @@
11 import { defineCollection, z } from "astro:content";
22 import { glob } from "astro/loaders";
33
4−// Blog posts authored as Markdown under src/content/blog. The glob loader
5−// turns each file into a collection entry whose `id` is the slug used in the
6−// /blog/[...slug] route (filename without extension).
4+// Blog posts authored as Markdown or MDX under src/content/blog. The glob
5+// loader turns each file into a collection entry whose `id` is the slug used
6+// in the /blog/[...slug] route (filename without extension). MDX is for posts
7+// that embed interactive island components.
78 const blog = defineCollection({
8− loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
9+ loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/blog" }),
910 schema: z.object({
1011 title: z.string(),
1112 description: z.string(),
apps/marketing/src/content/blog/mcp-cli-same-thing.mdx+94−0
@@ -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)._
apps/marketing/src/styles/global.css+668−0
@@ -1673,3 +1673,671 @@ body { · 1672 unmodified lines
16731673 .blog-prose h3 + p {
16741674 margin-top: 0;
16751675 }
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+}
bun.lock+35−22
@@ -330,10 +330,11 @@ · 329 unmodified lines
330330 "version": "0.0.5",
331331 "dependencies": {
332332 "@astrojs/cloudflare": "^13.0.0",
333+ "@astrojs/mdx": "^6.0.3",
333334 "@astrojs/react": "^5.0.4",
334335 "@executor-js/react": "workspace:*",
335336 "@tailwindcss/vite": "^4.2.2",
336− "astro": "^6.1.3",
337+ "astro": "^6.4.8",
337338 "clsx": "^2.1.1",
338339 "motion": "^12.38.0",
339340 "posthog-js": "^1.372.5",
@@ -1312,17 +1313,19 @@ · 972 unmodified lines
13121313
13131314 "@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=="],
13141315
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=="],
13161317
13171318 "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="],
13181319
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=="],
13201321
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=="],
13221325
13231326 "@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=="],
13241327
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=="],
13261329
13271330 "@astrojs/underscore-redirects": ["@astrojs/underscore-redirects@1.0.3", "", {}, "sha512-cxnGSw+sJigBLdX4TMSZKkzV6C3gMLJMucDk2W+n281Xhie68T2/9f1+1NMNDCZsc5i0FED7Qt5I10g2O9wtZg=="],
13281331
@@ -3366,7 +3369,7 @@ · 2037 unmodified lines
33663369
33673370 "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
33683371
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=="],
33703373
33713374 "async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="],
33723375
@@ -3808,8 +3811,6 @@ · 435 unmodified lines
38083811
38093812 "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
38103813
3811− "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
3812−
38133814 "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=="],
38143815
38153816 "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
43284329
43294330 "is-descriptor": ["is-descriptor@1.0.3", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw=="],
43304331
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=="],
43324333
43334334 "is-electron": ["is-electron@2.2.2", "", {}, "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="],
43344335
@@ -5614,8 +5615,6 @@ · 1279 unmodified lines
56145615
56155616 "ts-md5": ["ts-md5@2.0.1", "", {}, "sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w=="],
56165617
5617− "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
5618−
56195618 "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
56205619
56215620 "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
58645863
58655864 "@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=="],
58665865
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+
58675874 "@astrojs/react/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.9.0", "", { "dependencies": { "picomatch": "^4.0.4" } }, "sha512-GdYkzR26re8izmyYlBqf4z2s7zNngmWLFuxw0UKiPNqHraZGS6GKWIwSHgS22RDlu2ePFJ8bzmpBcUszut/SDg=="],
58685875
58695876 "@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
64346441
64356442 "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
64366443
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=="],
64386445
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=="],
64406451
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=="],
64426455
64436456 "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=="],
64446457
6458+ "astro/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
6459+
64456460 "atlas-api-client/rxjs": ["rxjs@5.5.12", "", { "dependencies": { "symbol-observable": "1.0.1" } }, "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw=="],
64466461
64476462 "atmn/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
@@ -6594,6 +6609,8 @@ · 146 unmodified lines
65946609
65956610 "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=="],
65966611
6612+ "is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
6613+
65976614 "jake/async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
65986615
65996616 "jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -6798,6 +6815,10 @@ · 198 unmodified lines
67986815
67996816 "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
68006817
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+
68016822 "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
68026823
68036824 "@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
72667287
72677288 "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
72687289
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−
72757290 "broadcast-channel/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
72767291
72777292 "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
77667781
77677782 "agents/yargs/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
77687783
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−
77717784 "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
77727785
77737786 "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],