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
5 5
6import tailwindcss from "@tailwindcss/vite";6import tailwindcss from "@tailwindcss/vite";
7import react from "@astrojs/react";7import react from "@astrojs/react";
8import mdx from "@astrojs/mdx";
8 9
9import cloudflare from "@astrojs/cloudflare";10import cloudflare from "@astrojs/cloudflare";
10 11
@@ -29,7 +30,7 @@ const wranglerPublicDefine = () => { · 18 unmodified lines
29export default defineConfig({30export 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(),
apps/marketing/package.json+2−1
@@ -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",
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
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## Source31## 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.
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
6import { useState } from "react";
7import { 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
21type Interface_ = "cli" | "mcp";
22type Loading = "lazy" | "eager";
23
24const SYSTEM_TOK = 1200;
25
26type Segment = { readonly label: string; readonly tok: number; readonly flood?: boolean };
27
28const 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
42const cellTotal = (i: Interface_, l: Loading) =>
43 SYSTEM_TOK + CELLS[i][l].reduce((s, seg) => s + seg.tok, 0);
44
45const MAX_TOTAL = Math.max(
46 cellTotal("cli", "eager"),
47 cellTotal("mcp", "eager"),
48 cellTotal("cli", "lazy"),
49 cellTotal("mcp", "lazy"),
50);
51
52function 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
84export 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
6import { useState } from "react";
7import { 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
20type Row = { readonly name: string; readonly tokens: number };
21
22const 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];
31const MAX2 = 410797;
32
33type Paired = { readonly name: string; readonly ratio: number; readonly note?: string };
34
35// Table 3: cost with MCP attached ÷ cost without, per harness.
36const 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.
46const 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
52type Stage = "guess" | "revealed" | "paired";
53
54export 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
6import { useEffect, useRef, useState } from "react";
7import { 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
17type Step = { readonly text: string; readonly bad?: boolean; readonly done?: boolean };
18
19type Lane = {
20 readonly id: string;
21 readonly title: string;
22 readonly sub: string;
23 readonly steps: ReadonlyArray<Step>;
24};
25
26const 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
57const STEP_MS = 800;
58const MAX_STEPS = Math.max(...LANES.map((l) => l.steps.length));
59
60export 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
6import { useEffect, useRef, useState } from "react";
7import { 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
17type 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.
25const 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];
33const REST = 38; // the rest of the 44-tool catalog
34
35export 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
7import { useEffect, useRef, useState } from "react";
8import { 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
18type Surface = "mcp" | "cli" | "http";
19
20const 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.
27function Hl({ children }: { readonly children: React.ReactNode }) {
28 return <mark className="bw-hl">{children}</mark>;
29}
30
31function 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
55function 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
74function 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
95const BODIES: Record<Surface, () => React.ReactNode> = {
96 mcp: McpBody,
97 cli: CliBody,
98 http: HttpBody,
99};
100
101export 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 @@
1import { useEffect, useRef, useState } from "react";
2
3/** Formats 12345 as "12,345". */
4export const fmt = (n: number) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
5
6export 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. */
19export 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. */
57export 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 @@
1import { defineCollection, z } from "astro:content";1import { defineCollection, z } from "astro:content";
2import { glob } from "astro/loaders";2import { glob } from "astro/loaders";
3 3
4// Blog posts authored as Markdown under src/content/blog. The glob loader4// 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 the5// 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.
7const blog = defineCollection({8const 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(),
apps/marketing/src/content/blog/mcp-cli-same-thing.mdx+94−0
@@ -0,0 +1,94 @@
1---
2title: "MCPs, CLIs, and APIs are the same thing"
3description: "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."
4date: 2026-08-23
5author: "Rhys Sullivan"
6---
7
8import { SameCallDemo } from "../../components/blog/same-call-demo";
9import { McpToCliDemo } from "../../components/blog/mcp-to-cli-demo";
10import { ContextFloodDemo } from "../../components/blog/context-flood-demo";
11import { HarnessSpreadDemo } from "../../components/blog/harness-spread-demo";
12import { InstallRaceDemo } from "../../components/blog/install-race-demo";
13
14Last 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
16The 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
18Here 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
22Same 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
24And 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
28The 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
32Nothing 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
34Note 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
36If 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
40When 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
42There 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
46The 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
48This 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
52So 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
56The 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
58When 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
60To 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
64The strongest real advantage CLIs have today has nothing to do with tokens. It is this:
65
66<InstallRaceDemo client:visible />
67
68Tell 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
70But 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
74If 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
82I 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
86You 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
88That 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
90Stop 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
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}
bun.lock+35−22
@@ -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=="],