diff --git a/web/app/[locale]/blog/cmux-claude-teams/page.tsx b/web/app/[locale]/blog/cmux-claude-teams/page.tsx new file mode 100644 index 00000000..25abae72 --- /dev/null +++ b/web/app/[locale]/blog/cmux-claude-teams/page.tsx @@ -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 ( + <> +
+ {t.rich("p1", {
+ code: (chunks) => {chunks},
+ })}
+
+ {t.rich("p2", {
+ code: (chunks) => {chunks},
+ })}
+
+ {t.rich("p3", {
+ code: (chunks) => {chunks},
+ omoLink: (chunks) => (
+ {chunks}
+ ),
+ })}
+
+ Read the docs → +
+ > + ); +} diff --git a/web/app/[locale]/blog/cmux-omo/page.tsx b/web/app/[locale]/blog/cmux-omo/page.tsx new file mode 100644 index 00000000..5105fd49 --- /dev/null +++ b/web/app/[locale]/blog/cmux-omo/page.tsx @@ -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 ( + <> +
+ {t.rich("p1", {
+ code: (chunks) => {chunks},
+ claudeTeamsLink: (chunks) => (
+ {chunks}
+ ),
+ })}
+
+ {t.rich("p2", {
+ code: (chunks) => {chunks},
+ })}
+
+ Read the docs → +
+ > + ); +} diff --git a/web/app/[locale]/blog/cmux-ssh/page.tsx b/web/app/[locale]/blog/cmux-ssh/page.tsx new file mode 100644 index 00000000..be679a3e --- /dev/null +++ b/web/app/[locale]/blog/cmux-ssh/page.tsx @@ -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 ( + <> +
+ {t.rich("p1", {
+ code: (chunks) => {chunks},
+ })}
+
localhost:3000 reaches the remote dev server without port forwardingcmux claude-teams and cmux omo work over SSH, spawning teammate panes locally while computation runs remote+ Read the SSH docs → +
+ > + ); +} diff --git a/web/app/[locale]/blog/gpl/page.tsx b/web/app/[locale]/blog/gpl/page.tsx new file mode 100644 index 00000000..102a88d0 --- /dev/null +++ b/web/app/[locale]/blog/gpl/page.tsx @@ -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 ( + <> +{t("p1")}
+{t("p2")}
+{t("p3")}
+ > + ); +} diff --git a/web/app/[locale]/blog/page.tsx b/web/app/[locale]/blog/page.tsx index f9cf1ac3..c995f508 100644 --- a/web/app/[locale]/blog/page.tsx +++ b/web/app/[locale]/blog/page.tsx @@ -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{t("intro")}
+ +{t("intro")}
+ +{t("intro")}
+ + + +{t("usageDesc")}
+ +| {t("flagName")} | +{t("flagDesc")} | +
|---|---|
--name | {t("flagNameVal")} |
-p, --port | {t("flagPort")} |
-i, --identity | {t("flagIdentity")} |
-o, --ssh-option | {t("flagSshOption")} |
--no-focus | {t("flagNoFocus")} |
{t("browserDesc")}
+ +{t("dragDropDesc")}
+ +{t("notificationsDesc")}
+ +{t("agentsDesc")}
+{t("reconnectDesc")}
+ +{t("daemonDesc")}
+| {t("daemonFeature")} | +{t("daemonHow")} | +
|---|---|
| {t("daemonProxy")} | {t("daemonProxyHow")} |
| {t("daemonRelay")} | {t("daemonRelayHow")} |
| {t("daemonSession")} | {t("daemonSessionHow")} |
{t("daemonPath")}
+ > + ); +} diff --git a/web/app/sitemap.ts b/web/app/sitemap.ts index 4749347e..a4ce63ac 100644 --- a/web/app/sitemap.ts +++ b/web/app/sitemap.ts @@ -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 }, diff --git a/web/messages/en.json b/web/messages/en.json index 692e0d2b..06dda52a 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -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 (split-window, send-keys, capture-pane, select-layout), so if you're not in tmux, it doesn't work. cmux claude-teams places a shim on PATH that intercepts every tmux call and translates it into cmux's split/workspace API. It sets a fake TMUX env var so Claude thinks it's in tmux, then execs into claude --teammate-mode auto.",
+ "p2": "The shim translates split-window into surface.split, send-keys into surface.send_text, capture-pane into surface.read_text. 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 cmux omocmux omo 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. cmux omo uses the same tmux shim as cmux claude-teamsTMUX env var, every tmux command translated to cmux splits. It auto-installs the plugin into a shadow config so ~/.config/opencode stays untouched.",
+ "p2": "The shim also intercepts terminal-notifier calls and routes them through cmux notify. 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": "cmux ssh user@remote 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 ~/.ssh/config, 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/