Add cmux SSH blog post (#2377)
* Add cmux SSH blog post * Shorten SSH blog post, remove headings * Trim SSH blog post to 2 paragraphs * Remove first person, add feature bullet list * Clarify no port forwarding needed * Add cmux claude-teams and cmux omo blog posts * Wrap command names in code tags * Add code tags to SSH post, shorten claude-teams and omo paragraphs * Reorder blog posts: SSH first, then claude-teams and omo * Update omo post with actual oh-my-openagent details, improve titles * Add GPL blog post, fix omo title to specialist agents * Explain what the relay daemon is for in SSH post * Move drag-image to second bullet, use routes through * Rewrite SSH intro, add image upload video * Add SSH docs page, link blog posts to docs * Add omo demo video to blog post * Trim omo agent details * Lead omo paragraph with cmux omo command * Rename omo title to oh-my-openagent subagents * Add claude-teams demo video to blog post * Fix omo naming, link to docs instead of blog posts * Add demo videos to SSH blog, SSH docs, claude-teams docs, omo docs --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
This commit is contained in:
parent
e2e5f87b74
commit
51fe28e45d
15 changed files with 557 additions and 0 deletions
86
web/app/[locale]/blog/cmux-claude-teams/page.tsx
Normal file
86
web/app/[locale]/blog/cmux-claude-teams/page.tsx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { useTranslations } from "next-intl";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { buildAlternates } from "../../../../i18n/seo";
|
||||
import { Link } from "../../../../i18n/navigation";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "blog.cmuxClaudeTeams" });
|
||||
return {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
keywords: [
|
||||
"cmux", "Claude Code", "agent teams", "teammate mode", "tmux",
|
||||
"terminal", "macOS", "AI coding agents", "split panes",
|
||||
],
|
||||
openGraph: {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
type: "article",
|
||||
publishedTime: "2026-03-30T00:00:00Z",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
},
|
||||
alternates: buildAlternates(locale, "/blog/cmux-claude-teams"),
|
||||
};
|
||||
}
|
||||
|
||||
export default function CmuxClaudeTeamsPage() {
|
||||
const t = useTranslations("blog.posts.cmuxClaudeTeams");
|
||||
const tc = useTranslations("common");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="text-sm text-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
← {tc("backToBlog")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1>{t("title")}</h1>
|
||||
<time dateTime="2026-03-30" className="text-sm text-muted">
|
||||
{t("date")}
|
||||
</time>
|
||||
|
||||
<video
|
||||
src="/blog/cmux-claude-teams-demo.mp4"
|
||||
width={1824}
|
||||
height={1080}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="mt-6 rounded-lg w-full h-auto"
|
||||
/>
|
||||
|
||||
<p className="mt-6">
|
||||
{t.rich("p1", {
|
||||
code: (chunks) => <code>{chunks}</code>,
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{t.rich("p2", {
|
||||
code: (chunks) => <code>{chunks}</code>,
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{t.rich("p3", {
|
||||
code: (chunks) => <code>{chunks}</code>,
|
||||
omoLink: (chunks) => (
|
||||
<Link href="/docs/agent-integrations/oh-my-opencode">{chunks}</Link>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
|
||||
<p className="mt-4">
|
||||
<Link href="/docs/agent-integrations/claude-code-teams">Read the docs →</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
81
web/app/[locale]/blog/cmux-omo/page.tsx
Normal file
81
web/app/[locale]/blog/cmux-omo/page.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { useTranslations } from "next-intl";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { buildAlternates } from "../../../../i18n/seo";
|
||||
import { Link } from "../../../../i18n/navigation";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "blog.cmuxOmo" });
|
||||
return {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
keywords: [
|
||||
"cmux", "OpenCode", "oh-my-opencode", "oh-my-openagent", "tmux",
|
||||
"terminal", "macOS", "AI coding agents", "multi-model",
|
||||
],
|
||||
openGraph: {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
type: "article",
|
||||
publishedTime: "2026-03-30T00:00:00Z",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
},
|
||||
alternates: buildAlternates(locale, "/blog/cmux-omo"),
|
||||
};
|
||||
}
|
||||
|
||||
export default function CmuxOmoPage() {
|
||||
const t = useTranslations("blog.posts.cmuxOmo");
|
||||
const tc = useTranslations("common");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="text-sm text-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
← {tc("backToBlog")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1>{t("title")}</h1>
|
||||
<time dateTime="2026-03-30" className="text-sm text-muted">
|
||||
{t("date")}
|
||||
</time>
|
||||
|
||||
<video
|
||||
src="/blog/cmux-omo-demo.mp4"
|
||||
width={1824}
|
||||
height={1080}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="mt-6 rounded-lg w-full h-auto"
|
||||
/>
|
||||
|
||||
<p className="mt-6">
|
||||
{t.rich("p1", {
|
||||
code: (chunks) => <code>{chunks}</code>,
|
||||
claudeTeamsLink: (chunks) => (
|
||||
<Link href="/docs/agent-integrations/claude-code-teams">{chunks}</Link>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{t.rich("p2", {
|
||||
code: (chunks) => <code>{chunks}</code>,
|
||||
})}
|
||||
</p>
|
||||
|
||||
<p className="mt-4">
|
||||
<Link href="/docs/agent-integrations/oh-my-opencode">Read the docs →</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
90
web/app/[locale]/blog/cmux-ssh/page.tsx
Normal file
90
web/app/[locale]/blog/cmux-ssh/page.tsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { useTranslations } from "next-intl";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { buildAlternates } from "../../../../i18n/seo";
|
||||
import { Link } from "../../../../i18n/navigation";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "blog.cmuxSsh" });
|
||||
return {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
keywords: [
|
||||
"cmux", "SSH", "remote development", "terminal", "macOS",
|
||||
"port forwarding", "notifications", "AI coding agents",
|
||||
"Claude Code", "remote workspace", "developer tools",
|
||||
],
|
||||
openGraph: {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
type: "article",
|
||||
publishedTime: "2026-03-30T00:00:00Z",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
},
|
||||
alternates: buildAlternates(locale, "/blog/cmux-ssh"),
|
||||
};
|
||||
}
|
||||
|
||||
export default function CmuxSshPage() {
|
||||
const t = useTranslations("blog.posts.cmuxSsh");
|
||||
const tc = useTranslations("common");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="text-sm text-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
← {tc("backToBlog")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1>{t("title")}</h1>
|
||||
<time dateTime="2026-03-30" className="text-sm text-muted">
|
||||
{t("date")}
|
||||
</time>
|
||||
|
||||
<p className="mt-6">
|
||||
{t.rich("p1", {
|
||||
code: (chunks) => <code>{chunks}</code>,
|
||||
})}
|
||||
</p>
|
||||
|
||||
<video
|
||||
src="/blog/cmux-ssh-image-upload.mp4"
|
||||
width={1824}
|
||||
height={1080}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="my-6 rounded-lg w-full h-auto"
|
||||
/>
|
||||
|
||||
<ul className="mt-4 space-y-1">
|
||||
<li>Browser panes route through the remote machine, so <code>localhost:3000</code> reaches the remote dev server without port forwarding</li>
|
||||
<li>Drag an image into a remote terminal to upload via scp</li>
|
||||
<li>Coding agents on the remote box send notifications to your local sidebar</li>
|
||||
<li><code>cmux claude-teams</code> and <code>cmux omo</code> work over SSH, spawning teammate panes locally while computation runs remote</li>
|
||||
<li>The sidebar shows connection state and detected listening ports</li>
|
||||
</ul>
|
||||
|
||||
<iframe
|
||||
className="my-6 rounded-lg w-full aspect-video"
|
||||
src="https://www.youtube.com/embed/RoR9pMOZWkk"
|
||||
title="cmux SSH demo"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
|
||||
<p className="mt-4">
|
||||
<Link href="/docs/ssh">Read the SSH docs →</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
56
web/app/[locale]/blog/gpl/page.tsx
Normal file
56
web/app/[locale]/blog/gpl/page.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useTranslations } from "next-intl";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { buildAlternates } from "../../../../i18n/seo";
|
||||
import { Link } from "../../../../i18n/navigation";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "blog.gpl" });
|
||||
return {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
keywords: [
|
||||
"cmux", "GPL", "AGPL", "open source", "license",
|
||||
"terminal", "macOS", "copyleft",
|
||||
],
|
||||
openGraph: {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
type: "article",
|
||||
publishedTime: "2026-03-30T00:00:00Z",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
},
|
||||
alternates: buildAlternates(locale, "/blog/gpl"),
|
||||
};
|
||||
}
|
||||
|
||||
export default function GplPage() {
|
||||
const t = useTranslations("blog.posts.gpl");
|
||||
const tc = useTranslations("common");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="text-sm text-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
← {tc("backToBlog")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1>{t("title")}</h1>
|
||||
<time dateTime="2026-03-30" className="text-sm text-muted">
|
||||
{t("date")}
|
||||
</time>
|
||||
|
||||
<p className="mt-6">{t("p1")}</p>
|
||||
<p>{t("p2")}</p>
|
||||
<p>{t("p3")}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -18,6 +18,10 @@ export async function generateMetadata({
|
|||
}
|
||||
|
||||
const blogSlugs = [
|
||||
"cmuxSsh",
|
||||
"cmuxClaudeTeams",
|
||||
"cmuxOmo",
|
||||
"gpl",
|
||||
"cmdShiftU",
|
||||
"zenOfCmux",
|
||||
"showHnLaunch",
|
||||
|
|
@ -25,6 +29,10 @@ const blogSlugs = [
|
|||
] as const;
|
||||
|
||||
const slugToPath: Record<string, string> = {
|
||||
cmuxOmo: "cmux-omo",
|
||||
cmuxClaudeTeams: "cmux-claude-teams",
|
||||
cmuxSsh: "cmux-ssh",
|
||||
gpl: "gpl",
|
||||
cmdShiftU: "cmd-shift-u",
|
||||
zenOfCmux: "zen-of-cmux",
|
||||
showHnLaunch: "show-hn-launch",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,36 @@
|
|||
export const blogPosts = [
|
||||
{
|
||||
slug: "cmux-ssh",
|
||||
key: "cmuxSsh",
|
||||
title: "cmux SSH",
|
||||
date: "2026-03-30",
|
||||
summary:
|
||||
"One command gives you persistent remote sessions, browser panes that reach remote ports, and agent notifications that come home.",
|
||||
},
|
||||
{
|
||||
slug: "cmux-claude-teams",
|
||||
key: "cmuxClaudeTeams",
|
||||
title: "Claude Code teammate agents as native cmux panes",
|
||||
date: "2026-03-30",
|
||||
summary:
|
||||
"Claude Code's teammate mode requires tmux. cmux fakes it so teammates become native splits with sidebar metadata and notifications.",
|
||||
},
|
||||
{
|
||||
slug: "cmux-omo",
|
||||
key: "cmuxOmo",
|
||||
title: "oh-my-openagent subagents as native cmux panes",
|
||||
date: "2026-03-30",
|
||||
summary:
|
||||
"oh-my-openagent (formerly oh-my-opencode) orchestrates parallel specialist agents across Claude, GPT, and Gemini. cmux omo turns their tmux panes into native splits.",
|
||||
},
|
||||
{
|
||||
slug: "gpl",
|
||||
key: "gpl",
|
||||
title: "cmux is now GPL",
|
||||
date: "2026-03-30",
|
||||
summary:
|
||||
"cmux relicensed from AGPL to GPL. Copyleft stays, corporate adoption gets easier.",
|
||||
},
|
||||
{
|
||||
slug: "cmd-shift-u",
|
||||
key: "cmdShiftU",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const navItems: NavEntry[] = [
|
|||
{ titleKey: "apiReference", href: "/docs/api" },
|
||||
{ titleKey: "browserAutomation", href: "/docs/browser-automation" },
|
||||
{ titleKey: "notifications", href: "/docs/notifications" },
|
||||
{ titleKey: "ssh", href: "/docs/ssh" },
|
||||
{
|
||||
sectionKey: "agentIntegrations",
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@ export default function ClaudeCodeTeamsPage() {
|
|||
|
||||
<p>{t("intro")}</p>
|
||||
|
||||
<video
|
||||
src="/blog/cmux-claude-teams-demo.mp4"
|
||||
width={1824}
|
||||
height={1080}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="my-6 rounded-lg w-full h-auto"
|
||||
/>
|
||||
|
||||
<h2>{t("usage")}</h2>
|
||||
<CodeBlock lang="bash">{`cmux claude-teams
|
||||
cmux claude-teams --continue
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@ export default function OhMyOpenCodePage() {
|
|||
|
||||
<p>{t("intro")}</p>
|
||||
|
||||
<video
|
||||
src="/blog/cmux-omo-demo.mp4"
|
||||
width={1824}
|
||||
height={1080}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="my-6 rounded-lg w-full h-auto"
|
||||
/>
|
||||
|
||||
<h2>{t("usage")}</h2>
|
||||
<CodeBlock lang="bash">{`cmux omo
|
||||
cmux omo --continue
|
||||
|
|
|
|||
92
web/app/[locale]/docs/ssh/page.tsx
Normal file
92
web/app/[locale]/docs/ssh/page.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { useTranslations } from "next-intl";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { buildAlternates } from "../../../../i18n/seo";
|
||||
import { CodeBlock } from "../../components/code-block";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "docs.ssh" });
|
||||
return {
|
||||
title: t("metaTitle"),
|
||||
description: t("metaDescription"),
|
||||
alternates: buildAlternates(locale, "/docs/ssh"),
|
||||
};
|
||||
}
|
||||
|
||||
export default function SshPage() {
|
||||
const t = useTranslations("docs.ssh");
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{t("title")}</h1>
|
||||
<p>{t("intro")}</p>
|
||||
|
||||
<iframe
|
||||
className="my-6 rounded-lg w-full aspect-video"
|
||||
src="https://www.youtube.com/embed/RoR9pMOZWkk"
|
||||
title="cmux SSH demo"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
|
||||
<h2>{t("usage")}</h2>
|
||||
<CodeBlock lang="bash">{`cmux ssh user@remote
|
||||
cmux ssh user@remote --name "dev server"
|
||||
cmux ssh user@remote -p 2222
|
||||
cmux ssh user@remote -i ~/.ssh/id_ed25519`}</CodeBlock>
|
||||
<p>{t("usageDesc")}</p>
|
||||
|
||||
<h2>{t("flagsTitle")}</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("flagName")}</th>
|
||||
<th>{t("flagDesc")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>--name</code></td><td>{t("flagNameVal")}</td></tr>
|
||||
<tr><td><code>-p, --port</code></td><td>{t("flagPort")}</td></tr>
|
||||
<tr><td><code>-i, --identity</code></td><td>{t("flagIdentity")}</td></tr>
|
||||
<tr><td><code>-o, --ssh-option</code></td><td>{t("flagSshOption")}</td></tr>
|
||||
<tr><td><code>--no-focus</code></td><td>{t("flagNoFocus")}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>{t("browserTitle")}</h2>
|
||||
<p>{t("browserDesc")}</p>
|
||||
|
||||
<h2>{t("dragDropTitle")}</h2>
|
||||
<p>{t("dragDropDesc")}</p>
|
||||
|
||||
<h2>{t("notificationsTitle")}</h2>
|
||||
<p>{t("notificationsDesc")}</p>
|
||||
|
||||
<h2>{t("agentsTitle")}</h2>
|
||||
<p>{t("agentsDesc")}</p>
|
||||
<CodeBlock lang="bash">{`# Inside an SSH session:
|
||||
cmux claude-teams
|
||||
cmux omo`}</CodeBlock>
|
||||
|
||||
<h2>{t("reconnectTitle")}</h2>
|
||||
<p>{t("reconnectDesc")}</p>
|
||||
|
||||
<h2>{t("daemonTitle")}</h2>
|
||||
<p>{t("daemonDesc")}</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("daemonFeature")}</th>
|
||||
<th>{t("daemonHow")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>{t("daemonProxy")}</td><td>{t("daemonProxyHow")}</td></tr>
|
||||
<tr><td>{t("daemonRelay")}</td><td>{t("daemonRelayHow")}</td></tr>
|
||||
<tr><td>{t("daemonSession")}</td><td>{t("daemonSessionHow")}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>{t("daemonPath")}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -10,6 +10,10 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
|||
{ path: "/blog/show-hn-launch", lastModified: "2026-02-21", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/blog/introducing-cmux", lastModified: "2026-02-12", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/blog/zen-of-cmux", lastModified: "2026-02-27", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/blog/cmux-claude-teams", lastModified: "2026-03-30", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/blog/cmux-omo", lastModified: "2026-03-30", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/blog/cmux-ssh", lastModified: "2026-03-30", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/blog/gpl", lastModified: "2026-03-30", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/blog/cmd-shift-u", lastModified: "2026-03-04", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/docs/getting-started", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.9 },
|
||||
{ path: "/docs/concepts", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.8 },
|
||||
|
|
@ -18,6 +22,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
|||
{ path: "/docs/keyboard-shortcuts", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.7 },
|
||||
{ path: "/docs/api", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.8 },
|
||||
{ path: "/docs/notifications", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.8 },
|
||||
{ path: "/docs/ssh", lastModified: "2026-03-31", changeFrequency: "monthly" as const, priority: 0.8 },
|
||||
{ path: "/docs/changelog", lastModified: "2026-03-18", changeFrequency: "weekly" as const, priority: 0.5 },
|
||||
{ path: "/docs/browser-automation", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.8 },
|
||||
{ path: "/community", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.5 },
|
||||
|
|
|
|||
|
|
@ -112,6 +112,22 @@
|
|||
"metaTitle": "The Zen of cmux",
|
||||
"metaDescription": "cmux is a primitive, not a solution. It gives you composable pieces and your workflow is up to you."
|
||||
},
|
||||
"cmuxClaudeTeams": {
|
||||
"metaTitle": "Claude Code teammate agents as native cmux panes",
|
||||
"metaDescription": "Claude Code's teammate mode requires tmux. cmux fakes it so teammates become native splits with sidebar metadata and notifications."
|
||||
},
|
||||
"cmuxOmo": {
|
||||
"metaTitle": "oh-my-openagent subagents as native cmux panes",
|
||||
"metaDescription": "oh-my-openagent (formerly oh-my-opencode) orchestrates parallel specialist agents across Claude, GPT, and Gemini. cmux omo turns their tmux panes into native splits."
|
||||
},
|
||||
"gpl": {
|
||||
"metaTitle": "cmux is now GPL",
|
||||
"metaDescription": "cmux relicensed from AGPL to GPL. Copyleft stays, corporate adoption gets easier."
|
||||
},
|
||||
"cmuxSsh": {
|
||||
"metaTitle": "cmux SSH",
|
||||
"metaDescription": "One command gives you persistent remote sessions, browser panes that reach remote ports, and agent notifications that come home."
|
||||
},
|
||||
"cmdShiftU": {
|
||||
"metaTitle": "Cmd+Shift+U",
|
||||
"metaDescription": "How Cmd+Shift+U navigates between finished agents across workspaces in cmux."
|
||||
|
|
@ -125,6 +141,36 @@
|
|||
"metaDescription": "A native macOS terminal built on Ghostty, designed for running multiple AI coding agents side by side."
|
||||
},
|
||||
"posts": {
|
||||
"cmuxClaudeTeams": {
|
||||
"title": "Claude Code teammate agents as native cmux panes",
|
||||
"summary": "Claude Code's teammate mode requires tmux. cmux fakes it so teammates become native splits with sidebar metadata and notifications.",
|
||||
"date": "March 30, 2026",
|
||||
"p1": "Claude Code has an experimental teammate mode that spawns sub-agents in parallel. It manages them by issuing tmux commands (<code>split-window</code>, <code>send-keys</code>, <code>capture-pane</code>, <code>select-layout</code>), so if you're not in tmux, it doesn't work. <code>cmux claude-teams</code> places a shim on PATH that intercepts every tmux call and translates it into cmux's split/workspace API. It sets a fake <code>TMUX</code> env var so Claude thinks it's in tmux, then execs into <code>claude --teammate-mode auto</code>.",
|
||||
"p2": "The shim translates <code>split-window</code> into <code>surface.split</code>, <code>send-keys</code> into <code>surface.send_text</code>, <code>capture-pane</code> into <code>surface.read_text</code>. Teammates stack vertically in a right column, auto-equalizing as agents spawn and exit. Every pane shows up in the sidebar with notifications. Works over SSH via the Go relay daemon.",
|
||||
"p3": "The same shim powers <omoLink><code>cmux omo</code></omoLink> for oh-my-openagent (formerly oh-my-opencode)."
|
||||
},
|
||||
"cmuxOmo": {
|
||||
"title": "oh-my-openagent subagents as native cmux panes",
|
||||
"summary": "oh-my-openagent (formerly oh-my-opencode) orchestrates parallel specialist agents across Claude, GPT, and Gemini. cmux omo turns their tmux panes into native splits.",
|
||||
"date": "March 30, 2026",
|
||||
"p1": "<code>cmux omo</code> integrates oh-my-openagent (formerly oh-my-opencode), a plugin for OpenCode that orchestrates specialist agents across Claude, GPT, and Gemini in parallel. Each agent gets its own tmux pane. <code>cmux omo</code> uses the same tmux shim as <claudeTeamsLink><code>cmux claude-teams</code></claudeTeamsLink>: fake <code>TMUX</code> env var, every tmux command translated to cmux splits. It auto-installs the plugin into a shadow config so <code>~/.config/opencode</code> stays untouched.",
|
||||
"p2": "The shim also intercepts <code>terminal-notifier</code> calls and routes them through <code>cmux notify</code>. Works over SSH via the Go relay daemon."
|
||||
},
|
||||
"gpl": {
|
||||
"title": "cmux is now GPL",
|
||||
"summary": "cmux relicensed from AGPL to GPL. Copyleft stays, corporate adoption gets easier.",
|
||||
"date": "March 30, 2026",
|
||||
"p1": "cmux was AGPL-3.0. AGPL's network-use clause (Section 13) is designed for server software, requiring anyone who runs a modified version over a network to publish their source. cmux is a desktop app. Nobody runs cmux over a network. But several companies emailed us saying their org has a blanket AGPL ban, blocking adoption entirely.",
|
||||
"p2": "We relicensed to GPL-3.0. GPL still requires any distributed fork to publish its full source under GPL, which prevents proprietary commercial forks. It drops only the network-use clause, which cmux never needed. This matches Zed's approach: GPL for the editor, AGPL only for server components.",
|
||||
"p3": "The dual-license structure stays the same: GPL for open source, commercial license available for organizations that can't do GPL. Copyright stays with Manaflow. Personal and experimental forks remain fully allowed."
|
||||
},
|
||||
"cmuxSsh": {
|
||||
"title": "cmux SSH",
|
||||
"summary": "One command gives you persistent remote sessions, browser panes that reach remote ports, and agent notifications that come home.",
|
||||
"date": "March 30, 2026",
|
||||
"p1": "<code>cmux ssh user@remote</code> creates a workspace for the remote machine. Drag an image into a remote Claude Code session and it gets uploaded automatically. Browser panes route through the remote network, so localhost just works. Uses your <code>~/.ssh/config</code>, reconnects on drops.",
|
||||
"features": "Browser panes route traffic through the remote network, so localhost:3000 reaches the remote dev server without -L flags. Coding agents on the remote box send notifications to your local sidebar. cmux claude-teams and cmux omo work over SSH, spawning teammate panes locally while computation runs remote. Drag a file into a remote terminal to upload via scp. The sidebar shows connection state and detected listening ports."
|
||||
},
|
||||
"cmdShiftU": {
|
||||
"title": "Cmd+Shift+U",
|
||||
"summary": "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.",
|
||||
|
|
@ -607,6 +653,43 @@
|
|||
"patternDebug": "Capture debug artifacts on failure",
|
||||
"patternSession": "Persist and restore browser session"
|
||||
},
|
||||
"ssh": {
|
||||
"title": "SSH",
|
||||
"metaTitle": "SSH",
|
||||
"metaDescription": "Connect to remote machines with cmux ssh. Browser panes route through the remote network, images drag-and-drop via scp, and agent notifications come home.",
|
||||
"intro": "cmux ssh creates a workspace for a remote machine. Browser panes route through the remote network, files drag-and-drop via scp, coding agents send notifications to your local sidebar, and sessions reconnect on drops.",
|
||||
"usage": "Usage",
|
||||
"usageDesc": "cmux ssh reads your ~/.ssh/config for host aliases, identity files, and proxy settings. All flags mirror their ssh equivalents.",
|
||||
"flagsTitle": "Flags",
|
||||
"flagName": "Flag",
|
||||
"flagDesc": "Description",
|
||||
"flagNameVal": "Set the workspace title",
|
||||
"flagPort": "SSH port (default 22)",
|
||||
"flagIdentity": "Path to identity file",
|
||||
"flagSshOption": "Pass arbitrary SSH options (e.g. -o StrictHostKeyChecking=no)",
|
||||
"flagNoFocus": "Create the workspace without switching to it",
|
||||
"browserTitle": "Browser panes",
|
||||
"browserDesc": "Browser panes in a remote workspace route all HTTP and WebSocket traffic through the remote machine's network. Type localhost:3000 and you're looking at the dev server running on the remote box. No -L flags, no manual port forwarding. Each remote workspace gets an isolated cookie store so sessions are scoped per-connection.",
|
||||
"dragDropTitle": "Drag and drop",
|
||||
"dragDropDesc": "Drag an image or file into a remote terminal and cmux uploads it via scp through the existing SSH connection. cmux detects the foreground SSH process by TTY and routes the upload through ControlMaster multiplexing.",
|
||||
"notificationsTitle": "Notifications",
|
||||
"notificationsDesc": "Processes on the remote machine can run cmux commands that execute on your local instance. When a coding agent calls cmux notify on the remote box, the notification appears in your local sidebar. The blue ring lights up on the workspace tab. Cmd+Shift+U jumps to it. Notification spam from flaky connections is suppressed with a per-host cooldown.",
|
||||
"agentsTitle": "Coding agents over SSH",
|
||||
"agentsDesc": "cmux claude-teams and cmux omo both work inside SSH sessions. The Go relay daemon on the remote host handles the same tmux-compat translation that the local Swift CLI does. Teammate agents spawn as native cmux splits on your local machine while computation runs on the remote box.",
|
||||
"reconnectTitle": "Reconnect",
|
||||
"reconnectDesc": "When the connection drops, cmux reconnects with exponential backoff (3s, 6s, 12s, up to 60s). The remote session persists and cmux reattaches on reconnect, resizing with smallest-screen-wins semantics. Default keepalive options (ServerAliveInterval=20, ServerAliveCountMax=2) are injected unless your config already sets them.",
|
||||
"daemonTitle": "Relay daemon",
|
||||
"daemonDesc": "On first connect, cmux probes the remote host (uname -s, uname -m) and uploads a versioned cmuxd-remote binary. The binary speaks JSON-RPC over stdio and handles three things:",
|
||||
"daemonFeature": "Feature",
|
||||
"daemonHow": "How",
|
||||
"daemonProxy": "Browser traffic proxying",
|
||||
"daemonProxyHow": "SOCKS5 and HTTP CONNECT over the daemon's stdio channel",
|
||||
"daemonRelay": "CLI relay",
|
||||
"daemonRelayHow": "Reverse TCP tunnel with HMAC-SHA256 auth so remote processes can call cmux commands locally",
|
||||
"daemonSession": "Session management",
|
||||
"daemonSessionHow": "Persists sessions across reconnects, coordinates PTY resize across multiple attachments",
|
||||
"daemonPath": "The daemon binary is stored at ~/.cmux/bin/cmuxd-remote/<version>/<os>-<arch>/cmuxd-remote on the remote host and verified against a SHA-256 manifest embedded in the app."
|
||||
},
|
||||
"changelog": {
|
||||
"title": "Changelog",
|
||||
"metaTitle": "Changelog",
|
||||
|
|
@ -706,6 +789,7 @@
|
|||
"apiReference": "API Reference",
|
||||
"browserAutomation": "Browser Automation",
|
||||
"notifications": "Notifications",
|
||||
"ssh": "SSH",
|
||||
"agentIntegrations": "Agent Integrations",
|
||||
"claudeCodeTeams": "Claude Code Teams",
|
||||
"ohMyOpenCode": "oh-my-opencode",
|
||||
|
|
|
|||
BIN
web/public/blog/cmux-claude-teams-demo.mp4
Normal file
BIN
web/public/blog/cmux-claude-teams-demo.mp4
Normal file
Binary file not shown.
BIN
web/public/blog/cmux-omo-demo.mp4
Normal file
BIN
web/public/blog/cmux-omo-demo.mp4
Normal file
Binary file not shown.
BIN
web/public/blog/cmux-ssh-image-upload.mp4
Normal file
BIN
web/public/blog/cmux-ssh-image-upload.mp4
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue