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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
[![Discord](https://img.shields.io/discord/1446553342699507907?logo=discord&label=Discord)](https://cdr.co/mux-discord)
[![X (formerly Twitter)](https://img.shields.io/badge/Follow-%40codermux-black?logo=x)](https://x.com/codermux)


</div>

> [!IMPORTANT]
> This project was renamed from Mux to Shux after Mux.com raised a trademark concern. β€œMux” is a common technical abbreviation of β€œmultiplexer” and we do not expect confusion between the projects, but chose to rename ours rather than spend more time on the dispute. β€œShux” captures our reaction to the process and bears no other significance.
>
Shux is a desktop & browser application for parallel agentic development. It enables developers to plan and execute tasks with multiple AI agents on local or remote compute.
>
> Shux is a desktop & browser application for parallel agentic development. It enables developers to plan and execute tasks with multiple AI agents on local or remote compute.

<p><img src="./docs/img/mux-demo.gif" alt="Shux product demo" width="100%" /></p>

Expand Down
7 changes: 5 additions & 2 deletions docs/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions docs/img/black-shux.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions docs/img/logo-black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions docs/img/logo-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/logo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions docs/img/white-shux.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,11 @@
<div id="root">
<div class="boot-loader" role="status" aria-live="polite" aria-busy="true">
<div class="boot-loader__inner">
<svg class="boot-loader__logo" aria-hidden="true" viewBox="0 0 150 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<text x="2" y="47" fill="currentColor" font-family="Arial, Helvetica, sans-serif" font-size="50" font-weight="700" letter-spacing="-2">shux</text>
<rect x="133" y="14" width="15" height="34" fill="currentColor" />
</svg>
<svg class="boot-loader__logo" aria-hidden="true" viewBox="0 0 146.5354 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Outlined glyphs keep every wordmark on the same Geist Sans Bold geometry. -->
<path d="M14.2080 48.5760Q10.3680 48.5760 7.6320 47.4720Q4.8960 46.3680 3.4800 44.4240Q2.0640 42.4800 1.8720 39.8880L9.1680 39.6480Q9.5040 41.5200 10.7280 42.5040Q11.9520 43.4880 14.2560 43.4880Q16.2240 43.4880 17.2800 42.8400Q18.3360 42.1920 18.3360 40.9440Q18.3360 40.0800 17.9280 39.5520Q17.5200 39.0240 16.3920 38.6160Q15.2640 38.2080 13.1040 37.8720Q9.0240 37.2000 6.7200 36.2160Q4.4160 35.2320 3.4560 33.7200Q2.4960 32.2080 2.4960 29.9520Q2.4960 26.1600 5.4240 23.9280Q8.3520 21.6960 13.9200 21.6960Q19.1520 21.6960 22.1040 24.0960Q25.0560 26.4960 25.5360 30.5280L18.3360 30.8160Q18.0960 28.9440 16.9200 27.8640Q15.7440 26.7840 13.8240 26.7840Q11.9520 26.7840 10.9200 27.5760Q9.8880 28.3680 9.8880 29.6640Q9.8880 30.6240 10.3440 31.2240Q10.8000 31.8240 11.8320 32.2080Q12.8640 32.5920 14.6880 32.8320Q18.8160 33.4560 21.2160 34.4400Q23.6160 35.4240 24.6720 36.9600Q25.7280 38.4960 25.7280 40.8000Q25.7280 44.4960 22.6320 46.5360Q19.5360 48.5760 14.2080 48.5760M28.8480 48L28.8480 13.9200L36.0480 13.9200L36.0480 28.2720L35.0880 28.2720Q35.5680 25.0560 37.7760 23.3760Q39.9840 21.6960 43.1520 21.6960Q47.3280 21.6960 49.5840 24.3600Q51.8400 27.0240 51.8400 31.4400L51.8400 48L44.6400 48L44.6400 33.1680Q44.6400 30.2400 43.7280 28.8240Q42.8160 27.4080 40.7520 27.4080Q38.4960 27.4080 37.2720 28.9440Q36.0480 30.4800 36.0480 33.3600L36.0480 48M65.6640 48.5760Q61.6800 48.5760 59.4480 46.0320Q57.2160 43.4880 57.2160 38.8320L57.2160 22.2720L64.4160 22.2720L64.4160 37.1520Q64.4160 40.1280 65.3280 41.4960Q66.2400 42.8640 68.2080 42.8640Q70.4160 42.8640 71.6400 41.3280Q72.8640 39.7920 72.8640 36.8160L72.8640 22.2720L80.0640 22.2720L80.0640 48L73.4880 48L73.2960 40.6080L74.2080 40.8000Q73.6320 44.6400 71.4720 46.6080Q69.3120 48.5760 65.6640 48.5760M84.3360 48L93.5040 34.9440L84.6240 22.2720L92.2560 22.2720L97.7280 30.5280L103.1040 22.2720L110.8800 22.2720L102 34.9920L111.1680 48L103.4880 48L97.8720 39.2640L92.1600 48" fill="currentColor" />
<rect x="120.5354" y="13" width="26" height="35" fill="currentColor" />
</svg>
<p class="boot-loader__text">Loading Shux<span class="boot-loader__dots"></span></p>
</div>
</div>
Expand Down
Binary file modified public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/favicon-dark.ico
Binary file not shown.
Binary file modified public/favicon.ico
Binary file not shown.
Binary file modified public/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/tray-icon-black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/tray-icon-black@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/tray-icon-black@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/tray-icon-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/tray-icon-white@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/tray-icon-white@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 64 additions & 9 deletions scripts/generate-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* bun scripts/generate-icons.ts [commands...]
*
* Commands:
* update - Regenerate all derived icons from docs/img/logo-*.svg
* update - Regenerate all logo sources and derived assets
* png - Generate build/icon.png (512x512)
* icns - Generate build/icon.icns (macOS app icon)
* linux-icons - Generate build/icons/{16x16..512x512}.png (Linux icon set)
Expand All @@ -17,6 +17,12 @@ import { mkdir, rm, copyFile, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import sharp from "sharp";
import {
renderAppWordmark,
renderDocsWordmark,
renderInlineAppWordmark,
renderSquareLogo,
} from "./logoAssets";

const ICONSET_SIZES = [16, 32, 64, 128, 256, 512];
const FAVICON_SIZES = [16, 32, 48, 64, 128, 256];
Expand All @@ -29,6 +35,18 @@ const ROOT = path.resolve(__dirname, "..");
const SOURCE_BLACK = path.join(ROOT, "docs", "img", "logo-black.svg");
const SOURCE_WHITE = path.join(ROOT, "docs", "img", "logo-white.svg");

const WORDMARK_TARGETS = {
"docs/img/black-shux.svg": renderDocsWordmark("black"),
"docs/img/white-shux.svg": renderDocsWordmark("white"),
"src/browser/assets/logos/shux-logo-dark.svg": renderAppWordmark("white"),
"src/browser/assets/logos/shux-logo-light.svg": renderAppWordmark("black"),
} as const;

const INLINE_WORDMARK_TARGETS = {
"index.html": "boot-loader__logo",
"static/splash.html": "logo",
} as const;

// Build outputs
const BUILD_DIR = path.join(ROOT, "build");
const ICONSET_DIR = path.join(BUILD_DIR, "icon.iconset");
Expand Down Expand Up @@ -72,12 +90,10 @@ type LogoTargetConfig = RasterTargetConfig | SvgTargetConfig;
const MONO_ICON = { source: SOURCE_BLACK, bg: false } as const;
const APP_ICON = { source: SOURCE_WHITE, bg: true } as const;

// The source SVGs use viewBox="0 0 72 72" with a translate transform, leaving
// ~68% internal padding around the actual "m" + cursor mark. This cropped
// viewBox eliminates that padding so the mark fills the rendered image.
// Content bounds (after transform): x 8.85…63.15, y 24.5…47.5 β†’ 54.3Γ—23 units.
// Tight crop with ~0.5u breathing room: "8 24 56 24" β†’ aspect ratio β‰ˆ 2.33:1.
const TRAY_MARK_CROP = "8 24 56 24";
// Crop the centered square source to the outlined "s" + cursor bounds so the
// mark fills small tray images without clipping the glyph's natural overshoot.
// Content bounds: x 14.37…57.63, y 24.5…47.99 β†’ 43.26Γ—23.49 units.
const TRAY_MARK_CROP = "13.5 24 45 24.5";

// Targets to update (path -> config)
const LOGO_TARGETS = {
Expand All @@ -98,13 +114,13 @@ const LOGO_TARGETS = {
// iOS Safari uses apple-touch-icon for home screen installs.
"public/apple-touch-icon.png": { size: 180, ...APP_ICON },

// Electron Tray Icons – Wide Canvas with "m" Mark (Monochrome on Transparent)
// Electron Tray Icons – Wide Shux Mark (Monochrome on Transparent)
//
// The source SVGs have heavy internal padding (mark uses ~32% of canvas).
// We crop to TRAY_MARK_CROP before rendering so the mark fills the output.
//
// Pixel dimensions: 24Γ—24 @1x β†’ 48Γ—48 @2x β†’ 72Γ—72 @3x.
// Square canvas; the mark (aspect β‰ˆ 2.33:1) is height-constrained and
// Square canvas; the mark (aspect β‰ˆ 1.84:1) is height-constrained and
// centered horizontally with transparent side padding.
//
// macOS treats the black variant as a template image (adapts to light/dark
Expand Down Expand Up @@ -225,9 +241,48 @@ async function generateThemeFaviconSvg(output: string) {
await writeFile(output, themedSvg);
}

async function replaceInlineWordmark(relativePath: string, className: string) {
const outputPath = path.join(ROOT, relativePath);
const html = await readFile(outputPath, "utf8");
const pattern = new RegExp(
String.raw`^([ \t]*)<svg class="${className}"[\s\S]*?^[ \t]*</svg>`,
"m"
);
const match = html.match(pattern);
if (!match) {
throw new Error(`Could not find ${className} logo in ${relativePath}`);
}

const indent = match[1];
const svg = renderInlineAppWordmark(className)
.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
await writeFile(outputPath, html.replace(pattern, svg));
}

async function generateSourceLogos() {
await writeFile(SOURCE_BLACK, renderSquareLogo("black"));
console.log("βœ“ docs/img/logo-black.svg");
await writeFile(SOURCE_WHITE, renderSquareLogo("white"));
console.log("βœ“ docs/img/logo-white.svg");

for (const [relativePath, svg] of Object.entries(WORDMARK_TARGETS)) {
await writeFile(path.join(ROOT, relativePath), svg);
console.log(`βœ“ ${relativePath}`);
}

for (const [relativePath, className] of Object.entries(INLINE_WORDMARK_TARGETS)) {
await replaceInlineWordmark(relativePath, className);
console.log(`βœ“ ${relativePath}`);
}
}

async function updateAllLogos() {
console.log(`Updating all logos...\n`);

await generateSourceLogos();

for (const [relativePath, config] of Object.entries(LOGO_TARGETS)) {
const outputPath = path.join(ROOT, relativePath);

Expand Down
Loading
Loading