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:
Lawrence Chen 2026-03-31 05:11:57 -07:00 committed by GitHub
parent e2e5f87b74
commit 51fe28e45d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 557 additions and 0 deletions

View 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"
>
&larr; {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 &rarr;</Link>
</p>
</>
);
}

View 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"
>
&larr; {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 &rarr;</Link>
</p>
</>
);
}

View 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"
>
&larr; {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 &rarr;</Link>
</p>
</>
);
}

View 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"
>
&larr; {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>
</>
);
}

View file

@ -18,6 +18,10 @@ export async function generateMetadata({
} }
const blogSlugs = [ const blogSlugs = [
"cmuxSsh",
"cmuxClaudeTeams",
"cmuxOmo",
"gpl",
"cmdShiftU", "cmdShiftU",
"zenOfCmux", "zenOfCmux",
"showHnLaunch", "showHnLaunch",
@ -25,6 +29,10 @@ const blogSlugs = [
] as const; ] as const;
const slugToPath: Record<string, string> = { const slugToPath: Record<string, string> = {
cmuxOmo: "cmux-omo",
cmuxClaudeTeams: "cmux-claude-teams",
cmuxSsh: "cmux-ssh",
gpl: "gpl",
cmdShiftU: "cmd-shift-u", cmdShiftU: "cmd-shift-u",
zenOfCmux: "zen-of-cmux", zenOfCmux: "zen-of-cmux",
showHnLaunch: "show-hn-launch", showHnLaunch: "show-hn-launch",

View file

@ -1,4 +1,36 @@
export const blogPosts = [ 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", slug: "cmd-shift-u",
key: "cmdShiftU", key: "cmdShiftU",

View file

@ -20,6 +20,7 @@ export const navItems: NavEntry[] = [
{ titleKey: "apiReference", href: "/docs/api" }, { titleKey: "apiReference", href: "/docs/api" },
{ titleKey: "browserAutomation", href: "/docs/browser-automation" }, { titleKey: "browserAutomation", href: "/docs/browser-automation" },
{ titleKey: "notifications", href: "/docs/notifications" }, { titleKey: "notifications", href: "/docs/notifications" },
{ titleKey: "ssh", href: "/docs/ssh" },
{ {
sectionKey: "agentIntegrations", sectionKey: "agentIntegrations",
children: [ children: [

View file

@ -28,6 +28,17 @@ export default function ClaudeCodeTeamsPage() {
<p>{t("intro")}</p> <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> <h2>{t("usage")}</h2>
<CodeBlock lang="bash">{`cmux claude-teams <CodeBlock lang="bash">{`cmux claude-teams
cmux claude-teams --continue cmux claude-teams --continue

View file

@ -28,6 +28,17 @@ export default function OhMyOpenCodePage() {
<p>{t("intro")}</p> <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> <h2>{t("usage")}</h2>
<CodeBlock lang="bash">{`cmux omo <CodeBlock lang="bash">{`cmux omo
cmux omo --continue cmux omo --continue

View 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>
</>
);
}

View file

@ -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/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/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/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: "/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/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 }, { 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/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/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/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/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: "/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 }, { path: "/community", lastModified: "2026-03-18", changeFrequency: "monthly" as const, priority: 0.5 },

View file

@ -112,6 +112,22 @@
"metaTitle": "The Zen of cmux", "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." "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": { "cmdShiftU": {
"metaTitle": "Cmd+Shift+U", "metaTitle": "Cmd+Shift+U",
"metaDescription": "How Cmd+Shift+U navigates between finished agents across workspaces in cmux." "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." "metaDescription": "A native macOS terminal built on Ghostty, designed for running multiple AI coding agents side by side."
}, },
"posts": { "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": { "cmdShiftU": {
"title": "Cmd+Shift+U", "title": "Cmd+Shift+U",
"summary": "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.", "summary": "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.",
@ -607,6 +653,43 @@
"patternDebug": "Capture debug artifacts on failure", "patternDebug": "Capture debug artifacts on failure",
"patternSession": "Persist and restore browser session" "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": { "changelog": {
"title": "Changelog", "title": "Changelog",
"metaTitle": "Changelog", "metaTitle": "Changelog",
@ -706,6 +789,7 @@
"apiReference": "API Reference", "apiReference": "API Reference",
"browserAutomation": "Browser Automation", "browserAutomation": "Browser Automation",
"notifications": "Notifications", "notifications": "Notifications",
"ssh": "SSH",
"agentIntegrations": "Agent Integrations", "agentIntegrations": "Agent Integrations",
"claudeCodeTeams": "Claude Code Teams", "claudeCodeTeams": "Claude Code Teams",
"ohMyOpenCode": "oh-my-opencode", "ohMyOpenCode": "oh-my-opencode",

Binary file not shown.

Binary file not shown.

Binary file not shown.