Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions website/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export default defineConfig({
// Cloudflare adapter to a workerd/wrangler runtime that doesn't bind :4321 here,
// so we serve dist/ with a plain static server (the site is static; the Worker
// just serves these same assets in production).
webServer: { command: "npm run build && python3 -m http.server 4321 --directory dist", url: "http://localhost:4321", reuseExistingServer: !process.env.CI },
use: { baseURL: "http://localhost:4321" },
// Keep tests on their own port so a developer's Astro server (and its dev
// toolbar) can never be mistaken for the production-static test target.
webServer: { command: "npm run build && python3 -m http.server 4173 --directory dist", url: "http://localhost:4173", reuseExistingServer: false },
use: { baseURL: "http://localhost:4173" },
});
Binary file modified website/public/og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 20 additions & 19 deletions website/scripts/gen-og.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ const svg = String.raw`<svg xmlns="http://www.w3.org/2000/svg" width="1200" heig
<path d="M19 8c2-5 8-7 14-5 5 1 7 5 4 9-3 4-16 4-18 0-1-1-1-3 0-4Z" fill="#8B8B85"/>
</g>
<text x="118" y="80" fill="#191919" font-family="Arial, sans-serif" font-size="28" font-weight="700" letter-spacing="-0.7">agentcairn</text>
<text x="58" y="137" fill="#5F5F5A" font-family="monospace" font-size="17" letter-spacing="1.4">ONE VAULT · EVERY SUPPORTED AGENT · LOCAL-FIRST</text>
<text x="58" y="137" fill="#5F5F5A" font-family="monospace" font-size="17" letter-spacing="1.15">LOCAL-FIRST · OPEN SOURCE · PLAIN MARKDOWN</text>

<text x="58" y="229" fill="#191919" font-family="Georgia, serif" font-size="64" letter-spacing="-1.8">One memory across</text>
<text x="58" y="299" fill="#191919" font-family="Georgia, serif" font-size="64" letter-spacing="-1.8">your coding agents.</text>
<text x="58" y="354" fill="#191919" font-family="Arial, sans-serif" font-size="29" font-weight="700" letter-spacing="-0.5">Plain Markdown under your control.</text>
<text x="58" y="402" fill="#5F5F5A" font-family="Arial, sans-serif" font-size="20">Capture once. Inspect the files. Recall anywhere.</text>
<text x="58" y="229" fill="#191919" font-family="Georgia, serif" font-size="55" letter-spacing="-1.8">One memory trail across</text>
<text x="58" y="299" fill="#191919" font-family="Georgia, serif" font-size="55" letter-spacing="-1.8">connected coding agents.</text>
<text x="58" y="354" fill="#191919" font-family="Arial, sans-serif" font-size="29" font-weight="700" letter-spacing="-0.5">Files survive. Indexes rebuild.</text>
<text x="58" y="402" fill="#5F5F5A" font-family="Arial, sans-serif" font-size="20">Leave durable context for whichever agent comes next.</text>

<g transform="translate(58 470)">
<rect width="310" height="52" rx="13" fill="#FFFFFF" stroke="#D6D6D0" stroke-width="2"/>
<text x="20" y="33" fill="#2563EB" font-family="monospace" font-size="17">MARKDOWN · DUCKDB · MCP</text>
<rect width="468" height="52" rx="13" fill="#FFFFFF" stroke="#D6D6D0" stroke-width="2"/>
<text x="20" y="33" fill="#2563EB" font-family="monospace" font-size="16">NO HOSTED ACCOUNT · NO EXTERNAL DATABASE</text>
</g>
<text x="58" y="572" fill="#70706A" font-family="monospace" font-size="16">agentcairn.dev</text>

Expand All @@ -36,25 +36,26 @@ const svg = String.raw`<svg xmlns="http://www.w3.org/2000/svg" width="1200" heig
<circle cx="961" cy="394" r="5" fill="#317CFF"/>

<g transform="rotate(-1 969 136)">
<rect x="796" y="74" width="346" height="126" rx="22" fill="#191919"/>
<text x="824" y="108" fill="#9ABEFF" font-family="monospace" font-size="16" letter-spacing="1">MCP · CITED RECALL</text>
<text x="824" y="148" fill="#FFFFFF" font-family="Arial, sans-serif" font-size="21" font-weight="700">Current context</text>
<text x="824" y="177" fill="#C8C8C3" font-family="monospace" font-size="15">↳ auth-fix.md · api</text>
<rect x="796" y="74" width="346" height="126" rx="15" fill="#2563EB"/>
<text x="824" y="108" fill="#FFFFFF" font-family="monospace" font-size="16" letter-spacing="1">CLAUDE CODE · REMEMBER</text>
<text x="824" y="148" fill="#FFFFFF" font-family="Arial, sans-serif" font-size="20" font-weight="700">Staging deploys use blue-green.</text>
<text x="824" y="177" fill="#FFFFFF" font-family="monospace" font-size="15">written and indexed</text>
</g>

<g transform="rotate(0.8 942 305)">
<rect x="748" y="230" width="394" height="146" rx="23" fill="#FFFFFF" stroke="#D6D6D0" stroke-width="2"/>
<text x="777" y="267" fill="#5F5F5A" font-family="monospace" font-size="16" letter-spacing="1">INDEX · REBUILDABLE CACHE</text>
<text x="777" y="314" fill="#191919" font-family="Arial, sans-serif" font-size="30" font-weight="700">DuckDB</text>
<text x="777" y="346" fill="#5F5F5A" font-family="monospace" font-size="15">BM25 + vectors + rerank</text>
<rect x="690" y="230" width="452" height="146" rx="15" fill="#FFFFFF" stroke="#D6D6D0" stroke-width="2"/>
<text x="719" y="263" fill="#2563EB" font-family="monospace" font-size="15" letter-spacing="1">MARKDOWN · CANONICAL</text>
<text x="719" y="298" fill="#191919" font-family="monospace" font-size="12">memories/staging-deploys-use-blue-green-5b9d7c60.md</text>
<text x="719" y="328" fill="#5F5F5A" font-family="monospace" font-size="14">project: checkout-service · harness: mcp</text>
<text x="719" y="354" fill="#5F5F5A" font-family="monospace" font-size="11">- [context] Staging deploys use blue-green. #remembered</text>
<circle cx="1098" cy="340" r="8" fill="#E89B3C"/>
</g>

<g transform="rotate(-0.6 916 494)">
<rect x="690" y="414" width="452" height="158" rx="24" fill="#EAF1FF" stroke="#317CFF" stroke-width="2.5"/>
<text x="720" y="452" fill="#2563EB" font-family="monospace" font-size="16" letter-spacing="1">VAULT · SOURCE OF TRUTH</text>
<text x="720" y="498" fill="#191919" font-family="Arial, sans-serif" font-size="27" font-weight="700">Markdown you control</text>
<text x="720" y="534" fill="#4F4F4A" font-family="monospace" font-size="15">inspect · edit · sync</text>
<rect x="690" y="414" width="452" height="158" rx="15" fill="#191919"/>
<text x="720" y="452" fill="#9ABEFF" font-family="monospace" font-size="16" letter-spacing="1">CODEX · RECALL</text>
<text x="720" y="498" fill="#FFFFFF" font-family="Arial, sans-serif" font-size="25" font-weight="700">Use the blue-green strategy.</text>
<text x="720" y="534" fill="#C8C8C3" font-family="monospace" font-size="13">↳ staging-deploys-use-blue-green-5b9d7c60</text>
</g>
</svg>`;

Expand Down
264 changes: 197 additions & 67 deletions website/src/components/AgentsSupported.astro
Original file line number Diff line number Diff line change
@@ -1,71 +1,201 @@
---
import Section from "./Section.astro";
import { agents } from "../lib/content";
const obsidianWebp = agents.obsidian.image.replace(/\.png$/i, ".webp");

// Render inline `code` spans and [label](href) markdown links into safe HTML.
// Tokenize on code spans first, then apply the link regex ONLY to the
// non-code segments — so markdown-link syntax inside a code span is left intact.
function inline(s: string) {
return s
.split(/(`[^`]+`)/)
.map((seg) =>
seg.startsWith("`") && seg.endsWith("`")
? `<code class="font-mono text-[0.85em]">${seg
.slice(1, -1)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")}</code>`
: seg.replace(
/\[(.+?)\]\((.+?)\)/g,
(_m, label, href) => `<a href="${href}" class="underline">${label}</a>`,
),
)
.join("");
}

const full = agents.rows.filter((row) => row.ambient === "full");
const partial = agents.rows.filter((row) => row.ambient === "partial");
const explicit = agents.rows.filter((row) => row.ambient === "none");
---
<Section id="hosts" eyebrow={agents.eyebrow} eyebrowHeading={false}>
<h2 class="section-title mb-5">{agents.h2}</h2>
<p
class="prose-measure font-serif text-[16px] leading-[1.55] text-[var(--color-ink-muted)]"
set:html={inline(agents.body)}
/>
<p id="hosts-scroll-hint" class="table-scroll-hint table-scroll-hint--hosts mt-6">Swipe to compare every host →</p>
<div class="table-scroll hosts-table mt-2" tabindex="0" role="region" aria-label="Supported agents" aria-describedby="hosts-scroll-hint">
<table class="min-w-[760px] w-full border-collapse font-mono text-[13px]">
<caption class="sr-only">Supported coding-agent hosts, integration level, setup command, and ambient memory support</caption>
<thead><tr class="text-[var(--color-ink-muted)] text-left border-b border-[var(--color-border)]">
<th class="px-3 py-3 font-normal">host</th>
<th class="px-3 py-3 font-normal">support</th>
<th class="px-3 py-3 font-normal">set up with</th>
<th class="px-3 py-3 font-normal">ambient</th>
</tr></thead>
<tbody>
{agents.rows.map((r) => (
<tr class={"border-b border-[var(--color-border-faint)] " + (r.ambient !== "none" ? "text-[var(--color-ink)] font-medium" : "text-[var(--color-ink-muted)]")}>
<td class="px-3 py-3 whitespace-nowrap">{r.host}</td>
<td class="px-3 whitespace-nowrap">{r.support}</td>
<td class="px-3 whitespace-nowrap">{r.setup}</td>
<td class="px-3">{r.ambient === "full" ? "✓" : r.ambient === "partial" ? "◐" : "—"}</td>
</tr>
))}
</tbody>
</table>
<section id="hosts" class="agents-section mx-auto max-w-[var(--container-frame)] px-[var(--page-pad)]">
<div class="agents-section__header">
<h2>{agents.h2}</h2>
<p>{agents.body}</p>
</div>
<pre class="prose-measure font-mono text-[13px] leading-[1.7] bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl p-5 overflow-x-auto mt-6" tabindex="0">{agents.install.join("\n")}</pre>
<p class="prose-measure font-serif text-[14px] text-[var(--color-ink-faint)] mt-3">{agents.note}</p>

<p
class="prose-measure font-serif text-[16px] leading-[1.55] text-[var(--color-ink-muted)] mt-10"
set:html={inline(agents.obsidian.body)}
/>
<figure class="obsidian-figure m-0 mt-6">
<picture>
<source srcset={obsidianWebp} type="image/webp" />
<img src={agents.obsidian.image} width="1280" height="752" loading="lazy" decoding="async"
class="w-full h-auto rounded-xl border border-[var(--color-border-faint)]"
alt={agents.obsidian.imageAlt} />
</picture>
<figcaption class="mt-3 font-serif text-[14px] text-[var(--color-ink-faint)]">{agents.obsidian.caption}</figcaption>
</figure>
</Section>

<section class="ambient-map" aria-labelledby="ambient-map-title">
<h3 id="ambient-map-title" class="sr-only">AgentCairn automation levels by host</h3>
<div class="ambient-row ambient-row--full">
<div>
<p>Ambient</p>
<span>Recall and capture follow the host lifecycle.</span>
</div>
<ul>{full.map((row) => <li>{row.host}</li>)}</ul>
</div>
<div class="ambient-row ambient-row--partial">
<div>
<p>Sweep-backed</p>
<span>Tools are available; transcript capture runs out-of-band.</span>
</div>
<ul>{partial.map((row) => <li>{row.host}</li>)}</ul>
</div>
<div class="ambient-row ambient-row--explicit">
<div>
<p>Explicit MCP</p>
<span>The same memory tools, invoked when you choose.</span>
</div>
<ul>{explicit.map((row) => <li>{row.host}</li>)}</ul>
</div>
</section>

<details class="host-details">
<summary>Compare every host, integration, and setup command</summary>
<div class="host-details__body">
<p id="hosts-scroll-hint" class="table-scroll-hint table-scroll-hint--hosts">Swipe to compare every host →</p>
<div class="table-scroll hosts-table" tabindex="0" role="region" aria-label="Supported agents" aria-describedby="hosts-scroll-hint">
<table class="min-w-[760px] w-full border-collapse font-mono text-[13px]">
<caption class="sr-only">Supported coding-agent hosts, integration level, setup command, and ambient memory support</caption>
<thead><tr>
<th>host</th>
<th>support</th>
<th>set up with</th>
<th>ambient</th>
</tr></thead>
<tbody>
{agents.rows.map((row) => (
<tr class:list={[row.ambient !== "none" && "is-strong"]}>
<td>{row.host}</td>
<td>{row.support}</td>
<td>{row.setup}</td>
<td>{row.ambient === "full" ? "full" : row.ambient === "partial" ? "sweep" : "explicit"}</td>
</tr>
))}
</tbody>
</table>
</div>
<p class="host-details__note">{agents.note}</p>
</div>
</details>
</section>

<style>
.agents-section { padding-top: var(--spacing-section); }

.agents-section__header {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(18rem, 0.68fr);
gap: clamp(2rem, 7vw, 7rem);
align-items: end;
}

.agents-section h2 {
max-width: 15ch;
margin: 0;
font-family: var(--font-serif);
font-size: clamp(2.2rem, 4.8vw, 3.6rem);
font-weight: 430;
letter-spacing: -0.035em;
line-height: 1.02;
text-wrap: balance;
}

.agents-section__header > p {
max-width: 38rem;
margin: 0;
color: var(--color-ink-muted);
font-family: var(--font-sans);
font-size: 15px;
line-height: 1.62;
text-wrap: pretty;
}

.ambient-map {
margin-top: clamp(2rem, 5vw, 3.5rem);
border-top: 1px solid var(--color-ink);
}

.ambient-row {
display: grid;
grid-template-columns: minmax(12rem, 0.5fr) minmax(0, 1fr);
gap: 2rem;
align-items: center;
padding: 1.1rem 0;
border-bottom: 1px solid var(--color-border);
}

.ambient-row p,
.ambient-row span { margin: 0; }

.ambient-row p {
font-family: var(--font-sans);
font-size: 14px;
font-weight: 680;
}

.ambient-row--full p { color: var(--color-accent-text); }
.ambient-row--partial p { color: #98600d; }
.ambient-row--explicit p { color: var(--color-ink); }

.ambient-row span {
display: block;
margin-top: 0.2rem;
color: var(--color-ink-faint);
font-family: var(--font-sans);
font-size: 12px;
line-height: 1.45;
}

.ambient-row ul {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0;
padding: 0;
list-style: none;
}

.ambient-row li {
padding: 0.35rem 0.6rem;
border-radius: 999px;
background: var(--color-surface);
color: var(--color-ink);
font-family: var(--font-sans);
font-size: 12px;
font-weight: 560;
}

.ambient-row--full li { background: var(--color-surface-blue); color: #174ea6; }

.host-details {
margin-top: 1rem;
border-bottom: 1px solid var(--color-border);
}

.host-details summary {
display: flex;
min-height: 48px;
align-items: center;
justify-content: space-between;
gap: 1rem;
color: var(--color-ink);
font-family: var(--font-sans);
font-size: 13px;
font-weight: 650;
cursor: pointer;
list-style: none;
}

.host-details summary::-webkit-details-marker { display: none; }
.host-details summary::after { color: var(--color-accent-text); content: "+"; font-size: 20px; font-weight: 400; }
.host-details[open] summary::after { content: "−"; }

.host-details__body { padding: 0.75rem 0 2rem; }
.host-details th,
.host-details td { padding: 0.65rem 0.55rem; border-bottom: 1px solid var(--color-border-faint); text-align: left; white-space: nowrap; }
.host-details th { color: var(--color-ink-muted); font-weight: 450; }
.host-details tbody { color: var(--color-ink-muted); }
.host-details tr.is-strong { color: var(--color-ink); font-weight: 560; }

.host-details__note {
max-width: 72ch;
margin: 1rem 0 0;
color: var(--color-ink-faint);
font-family: var(--font-sans);
font-size: 12px;
line-height: 1.55;
}

@media (max-width: 760px) {
.agents-section__header,
.ambient-row { grid-template-columns: minmax(0, 1fr); }
.ambient-row { gap: 0.75rem; align-items: start; }
}
</style>
28 changes: 0 additions & 28 deletions website/src/components/ClaudeMemoryCallout.astro

This file was deleted.

4 changes: 2 additions & 2 deletions website/src/components/CommandBox.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
// A copy/paste command box matching the home hero's style.
// `command` may contain multiple lines (joined with "\n").
import CopyButton from "./CopyButton.astro";
const { command } = Astro.props;
const { command, label } = Astro.props;
---
<div class="command-shell font-mono text-[14px]" role="group" aria-label="Copyable command">
<div class="command-scroll" tabindex="0">
<code class="whitespace-pre leading-[1.7]">{command}</code>
</div>
<CopyButton text={command} />
<CopyButton text={command} label={label} />
</div>
<p class="command-scroll-hint">Swipe to view the full command →</p>
Loading