diff --git a/assets/icon.icns b/assets/icon.icns index 4e90364c..652b54ee 100644 Binary files a/assets/icon.icns and b/assets/icon.icns differ diff --git a/assets/icon.ico b/assets/icon.ico index 51fce758..3292508c 100644 Binary files a/assets/icon.ico and b/assets/icon.ico differ diff --git a/assets/icon.png b/assets/icon.png index 9a5ed061..82ee894c 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/icon.svg b/assets/icon.svg index 36d4964d..dd6927c4 100644 --- a/assets/icon.svg +++ b/assets/icon.svg @@ -1,21 +1,7 @@ - - - - - - - - - - - - - - - - - - + + + + + diff --git a/assets/tray-icon.png b/assets/tray-icon.png index 7453a119..e39da2a0 100644 Binary files a/assets/tray-icon.png and b/assets/tray-icon.png differ diff --git a/assets/tray-icon@2x.png b/assets/tray-icon@2x.png index c4922587..82930d8b 100644 Binary files a/assets/tray-icon@2x.png and b/assets/tray-icon@2x.png differ diff --git a/assets/tray-iconTemplate.png b/assets/tray-iconTemplate.png index 4801ceb5..6d58b697 100644 Binary files a/assets/tray-iconTemplate.png and b/assets/tray-iconTemplate.png differ diff --git a/assets/tray-iconTemplate@2x.png b/assets/tray-iconTemplate@2x.png index 72c59429..69f50945 100644 Binary files a/assets/tray-iconTemplate@2x.png and b/assets/tray-iconTemplate@2x.png differ diff --git a/logo.svg b/logo.svg new file mode 100644 index 00000000..bbf75d8f --- /dev/null +++ b/logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/package.json b/package.json index 82c6bc20..f29fae17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-company", - "version": "0.4.91", + "version": "0.4.92", "private": true, "type": "module", "description": "Cross-platform Electron desktop control plane for local-first AI agent companies.", diff --git a/scripts/generate-icons.py b/scripts/generate-icons.py index 4841cb2d..d692daaa 100644 --- a/scripts/generate-icons.py +++ b/scripts/generate-icons.py @@ -1,19 +1,17 @@ #!/usr/bin/env python3 -"""Generate AgentCompany app icons (PNG, ICO, ICNS, SVG). +"""Generate AgentCompany app icons (PNG, ICO, ICNS) and tray icons. -Design v3b: Bold "A" monogram that doubles as an org-chart hierarchy. -Two terracotta legs meet at an apex, with three warm crossbars of -decreasing brightness (CEO → Managers → ICs). All tones stay warm. +Design v4: Circle background #191919 with #FBFAF5 "A" logo. +Uses the actual logo paths from logo.svg rendered via cairosvg/Pillow. """ from __future__ import annotations -import math import shutil import subprocess from pathlib import Path -from PIL import Image, ImageDraw, ImageFilter +from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parents[1] ASSETS = ROOT / "assets" @@ -25,66 +23,40 @@ SVG_PATH = ASSETS / "icon.svg" SIZE = 1024 # ── Brand palette ────────────────────────────────────────────────────── -TERRACOTTA = (217, 119, 69) # #d97745 -SLATE_TOP = (17, 19, 21) # #111315 -SLATE_BOT = (27, 31, 35) # #1b1f23 - -# Bar colours — all warm, decreasing brightness -BAR_CEO = (255, 235, 215) # warm cream — brightest -BAR_MGR = (235, 180, 140) # warm peach -BAR_IC = (190, 140, 105) # muted terracotta - - -def rgba(r: int, g: int, b: int, a: int = 255) -> tuple[int, int, int, int]: - return (r, g, b, a) - - -def blend( - a: tuple[int, ...], b: tuple[int, ...], t: float, -) -> tuple[int, ...]: - return tuple(int(a[i] + (b[i] - a[i]) * t) for i in range(len(a))) +BG_COLOR = (25, 25, 25) # #191919 +LOGO_COLOR = (251, 250, 245) # #FBFAF5 def build_icon() -> Image.Image: - # ── 1. Gradient background with warm glow ────────────────────────── - bg = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) - px = bg.load() - top_c = rgba(*SLATE_TOP) - bot_c = rgba(*SLATE_BOT) - glow_cx, glow_cy = SIZE * 0.44, SIZE * 0.34 - glow_r = SIZE * 0.78 + """Build the app icon by rendering icon.svg via cairosvg, or fallback to Pillow.""" + # Try cairosvg first (best quality) + try: + import cairosvg + svg_data = SVG_PATH.read_bytes() + png_data = cairosvg.svg2png(bytestring=svg_data, output_width=SIZE, output_height=SIZE) + import io + return Image.open(io.BytesIO(png_data)).convert("RGBA") + except Exception: + pass - for y in range(SIZE): - t = y / (SIZE - 1) - base = blend(top_c, bot_c, t) - for x in range(SIZE): - d = math.hypot(x - glow_cx, y - glow_cy) / glow_r - warmth = max(0.0, 1.0 - d) ** 1.8 * 0.13 - px[x, y] = blend(base, rgba(*TERRACOTTA), warmth) - - # ── 2. Rounded-rect mask + shadow ────────────────────────────────── + # Fallback: draw programmatically with Pillow icon = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) - mask = Image.new("L", (SIZE, SIZE), 0) - inset = 64 - ImageDraw.Draw(mask).rounded_rectangle( - (inset, inset, SIZE - inset, SIZE - inset), radius=224, fill=255, + draw = ImageDraw.Draw(icon) + + # Circle background + margin = SIZE // 2 - 480 + draw.ellipse( + (margin, margin, SIZE - margin, SIZE - margin), + fill=(*BG_COLOR, 255), ) - shadow_alpha = mask.filter(ImageFilter.GaussianBlur(28)) - shadow_layer = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 48)) - shadow_layer.putalpha(shadow_alpha) - icon.alpha_composite(shadow_layer, (0, 12)) - icon.alpha_composite(bg) - icon.putalpha(mask) - - # ── 3. Draw the "A" mark ─────────────────────────────────────────── - overlay = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) - draw = ImageDraw.Draw(overlay) + # Draw the "A" monogram — same geometry as the old icon but in new color cx = SIZE // 2 apex_y = 200 base_y = 800 leg_w = 64 base_half = 250 + fill = (*LOGO_COLOR, 245) def leg_x(y: int) -> tuple[float, float]: t = (y - apex_y) / (base_y - apex_y) @@ -109,67 +81,102 @@ def build_icon() -> Image.Image: (lr_bot - leg_w, base_y), ] - draw.polygon(left_leg, fill=rgba(*TERRACOTTA, 240)) - draw.polygon(right_leg, fill=rgba(*TERRACOTTA, 240)) + draw.polygon(left_leg, fill=fill) + draw.polygon(right_leg, fill=fill) - # ── 4. Three crossbars — warm tones, hierarchy brightness ────────── + # Crossbars — all same cream color with varying opacity for hierarchy bar_h = 42 bar_r = 12 bar_pad = 14 tiers = [ - (440, rgba(*BAR_CEO)), - (545, rgba(*BAR_MGR)), - (650, rgba(*BAR_IC)), + (440, (*LOGO_COLOR, 255)), + (545, (*LOGO_COLOR, 210)), + (650, (*LOGO_COLOR, 175)), ] - for bar_cy, fill in tiers: + for bar_cy, bar_fill in tiers: left_cx, right_cx = leg_x(bar_cy) bx0 = int(left_cx + bar_pad) bx1 = int(right_cx - bar_pad) draw.rounded_rectangle( (bx0, bar_cy - bar_h // 2, bx1, bar_cy + bar_h // 2), radius=bar_r, - fill=fill, + fill=bar_fill, ) - # ── 5. Apex beacon ───────────────────────────────────────────────── + # Apex beacon dot_r = 20 dot_cy = apex_y - 2 draw.ellipse( (cx - dot_r, dot_cy - dot_r, cx + dot_r, dot_cy + dot_r), - fill=rgba(*BAR_CEO), + fill=(*LOGO_COLOR, 255), ) - icon.alpha_composite(overlay) return icon -def save_svg() -> None: - svg = """\ - - - - - - - - - - - - - - - - - - - - -""" - SVG_PATH.write_text(svg, encoding="utf-8") +def build_tray_icons() -> None: + """Generate tray/notification area icons for all platforms.""" + # Color tray icon (Windows/Linux) — 32x32 and 64x64 + for name, size in [("tray-icon.png", 32), ("tray-icon@2x.png", 64)]: + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + margin = 1 + draw.ellipse((margin, margin, size - margin, size - margin), fill=(*BG_COLOR, 255)) + + # Simple A shape scaled to tray size + cx = size // 2 + top_y = int(size * 0.18) + bot_y = int(size * 0.82) + spread = int(size * 0.28) + w = max(2, int(size * 0.06)) + + draw.polygon([ + (cx - w, top_y), (cx + w, top_y), + (cx + spread + w, bot_y), (cx + spread - w, bot_y), + ], fill=(*LOGO_COLOR, 240)) + draw.polygon([ + (cx - w, top_y), (cx + w, top_y), + (cx - spread + w, bot_y), (cx - spread - w, bot_y), + ], fill=(*LOGO_COLOR, 240)) + + # Crossbar + bar_y = int(size * 0.52) + bar_h = max(2, int(size * 0.06)) + bar_left = cx - int(spread * 0.55) + bar_right = cx + int(spread * 0.55) + draw.rectangle((bar_left, bar_y, bar_right, bar_y + bar_h), fill=(*LOGO_COLOR, 255)) + + img.save(ASSETS / name, format="PNG") + + # macOS template icons (monochrome black + alpha) + for name, size in [("tray-iconTemplate.png", 22), ("tray-iconTemplate@2x.png", 44)]: + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + cx = size // 2 + top_y = int(size * 0.15) + bot_y = int(size * 0.85) + spread = int(size * 0.3) + w = max(1, int(size * 0.06)) + + draw.polygon([ + (cx - w, top_y), (cx + w, top_y), + (cx + spread + w, bot_y), (cx + spread - w, bot_y), + ], fill=(0, 0, 0, 220)) + draw.polygon([ + (cx - w, top_y), (cx + w, top_y), + (cx - spread + w, bot_y), (cx - spread - w, bot_y), + ], fill=(0, 0, 0, 220)) + + bar_y = int(size * 0.52) + bar_h = max(1, int(size * 0.06)) + bar_left = cx - int(spread * 0.55) + bar_right = cx + int(spread * 0.55) + draw.rectangle((bar_left, bar_y, bar_right, bar_y + bar_h), fill=(0, 0, 0, 240)) + + img.save(ASSETS / name, format="PNG") def save_png_and_ico(image: Image.Image) -> None: @@ -204,11 +211,12 @@ def save_icns(image: Image.Image) -> None: def main() -> None: ASSETS.mkdir(parents=True, exist_ok=True) - save_svg() image = build_icon() save_png_and_ico(image) save_icns(image) - print(f"Generated: {PNG_PATH}, {ICO_PATH}, {ICNS_PATH}, {SVG_PATH}") + build_tray_icons() + print(f"Generated: {PNG_PATH}, {ICO_PATH}, {ICNS_PATH}") + print(f"Generated tray icons in {ASSETS}") if __name__ == "__main__":