Compare commits

...
4 Commits
Author SHA1 Message Date
ZacharyZhang-NYandClaude Opus 4.6 273244817a feat: rebrand icons — #191919 circle bg, #FBFAF5 logo
Build & Release / Lint & Typecheck (push) Successful in 36s
Build & Release / Unit Tests (push) Successful in 34s
Build & Release / Windows (x64) (push) Failing after 34s
Build & Release / Windows (arm64) (push) Failing after 34s
Build & Release / macOS (x64) (push) Canceled after 0s
Build & Release / macOS (arm64) (push) Canceled after 0s
Build & Release / Linux (arm64) (push) Canceled after 50s
Build & Release / Linux (x64) (push) Canceled after 0s
Build & Release / Publish Release (push) Canceled after 0s
Replace all orange/red icon assets with new brand colors:
- Circle background #191919, logo mark #FBFAF5
- Regenerated: icon.png, icon.ico, icon.icns, all tray icons
- Updated generate-icons.py with new palette and circle shape
- Added source logo.svg
- Bump version to 0.4.92

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:24:20 -04:00
ZacharyZhang-NYandClaude Opus 4.6 02386999f7 docs: remove local-only tagline, drop Kimi CLI and OpenCode from README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 11:58:43 -04:00
ZacharyZhang-NYandClaude Opus 4.6 69b25bbda5 docs: add screenshots and redesign README with feature showcase
- Add 7 product screenshots (Dashboard, OrgChart, Messages, Tasks, Approvals, Documents, Onboarding)
- Redesign README with centered logo, badge row, nav links, and feature sections with screenshots
- Add supported AI runtimes table, tech stack table, and architecture overview
- Add landing/ to .gitignore (deployed separately to Cloudflare Pages)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 11:57:30 -04:00
ZacharyZhang-NYandClaude Opus 4.6 a4fd98d934 fix: add system tray icon for all platforms (macOS, Windows, Linux)
Build & Release / Lint & Typecheck (push) Successful in 1m29s
Build & Release / Unit Tests (push) Successful in 31s
Build & Release / Windows (arm64) (push) Failing after 4m48s
Build & Release / Windows (x64) (push) Failing after 4m42s
Build & Release / Linux (x64) (push) Failing after 19s
Build & Release / macOS (arm64) (push) Canceled after 0s
Build & Release / macOS (x64) (push) Canceled after 0s
Build & Release / Publish Release (push) Canceled after 0s
Build & Release / Linux (arm64) (push) Failing after 1m9s
Tray was invisible on Windows/Linux (icon not bundled in asar) and
completely absent on macOS (early return guard). Generate dedicated
tray-sized icons, bundle them in the packaged app, and create tray
on all platforms with macOS Template image support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 01:21:17 -04:00
22 changed files with 290 additions and 127 deletions

No files matched your search

+3
View File
@@ -48,6 +48,9 @@ oldreference/
# Local design scratch files # Local design scratch files
homepagedesign.html homepagedesign.html
# Landing page (deployed separately to Cloudflare Pages)
landing/
# Package manager logs # Package manager logs
npm-debug.log* npm-debug.log*
pnpm-debug.log* pnpm-debug.log*
+144 -12
View File
@@ -1,16 +1,111 @@
# AgentCompany <p align="center">
<img src="assets/icon.svg" width="80" height="80" alt="AgentCompany logo" />
</p>
Cross-platform Electron desktop control plane for local-first AI agent companies. <h1 align="center">AgentCompany</h1>
AgentCompany is a standalone desktop application for managing multiple AI coding agents (Codex, Claude Code, Gemini CLI, OpenCode, Kimi Code) as an organization with goals, tasks, approvals, budgets, and audit trails. <p align="center">
<strong>Launch autonomous AI companies from your desktop.</strong><br/>
Set a goal. Watch AI agents self-organize, delegate, and deliver.
</p>
<p align="center">
<a href="https://github.com/ZacharyZhang-NY/AgentCompany/releases"><img alt="Latest Release" src="https://img.shields.io/github/v/release/ZacharyZhang-NY/AgentCompany?style=flat-square&color=4a5d7a" /></a>
<a href="https://github.com/ZacharyZhang-NY/AgentCompany/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/ZacharyZhang-NY/AgentCompany?style=flat-square&color=4a5d7a" /></a>
<img alt="Platform" src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-4a5d7a?style=flat-square" />
<img alt="Electron" src="https://img.shields.io/badge/electron-39.8.0-4a5d7a?style=flat-square" />
</p>
<p align="center">
<a href="https://github.com/ZacharyZhang-NY/AgentCompany/releases">Download</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="https://agentcompany.pages.dev">Website</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#features">Features</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="#quick-start">Quick Start</a>
</p>
---
<p align="center">
<img src="screenshots/Dashboard.jpeg" width="800" alt="AgentCompany Dashboard — active agents, tasks, and company metrics" />
</p>
## What is AgentCompany?
AgentCompany is a cross-platform desktop app that turns a single goal into a fully autonomous AI company. You provide a company name, description, and mission. A CEO agent self-organizes departments, hires specialists, delegates tasks, and delivers real results — code, content, research, business plans, and more — while you observe through a Slack-style command hub.
### Supported AI Runtimes
| Runtime | Status |
|---------|--------|
| Claude Code | Ready |
| Codex CLI | Ready |
| Gemini CLI | Ready |
---
## Features
### Self-Organizing Teams
Your AI CEO builds the org chart — recruiting specialists, creating departments, and establishing reporting lines from a single goal.
<p align="center">
<img src="screenshots/Auto-OrgChart-Team.jpeg" width="700" alt="Auto-generated org chart with CEO and department heads" />
</p>
### Real-Time Agent Communication
Agents coordinate through Slack-style channels and direct messages. Watch strategic decisions unfold in real time.
<p align="center">
<img src="screenshots/AI-Internal-Realtime-Message.jpeg" width="700" alt="Real-time messaging between AI agents" />
</p>
### Autonomous Task Management
Goals break down into tasks automatically. Agents pick up work, report progress, and escalate blockers without intervention.
<p align="center">
<img src="screenshots/Auto-Task.jpeg" width="700" alt="Autonomous task queue with goals and status tracking" />
</p>
### Human-in-the-Loop Approvals
Critical decisions surface for your review. Approve, reject, or redirect — your company respects the chain of command.
<p align="center">
<img src="screenshots/Approval-by-Human.jpeg" width="700" alt="Human approval workflow for agent decisions" />
</p>
### AI Document Generation
Agents draft business plans, marketing strategies, and technical specs — stored in a shared document center.
<p align="center">
<img src="screenshots/Document-Center.jpeg" width="700" alt="Document center with AI-generated deliverables" />
</p>
### One-Click Company Creation
Name it, describe the mission, connect a runtime, and launch. Agents start working in under a minute.
<p align="center">
<img src="screenshots/Onboarding.jpeg" width="700" alt="Company creation onboarding flow" />
</p>
---
## Quick Start ## Quick Start
### Download
Grab the latest release for your platform:
**[Download AgentCompany](https://github.com/ZacharyZhang-NY/AgentCompany/releases)**
> Available for macOS (Apple Silicon & Intel), Windows, and Linux.
### Build from Source
```sh ```sh
# Prerequisites: Node.js 22+, pnpm 10+
pnpm install pnpm install
pnpm dev pnpm dev
``` ```
---
## Verification ## Verification
```sh ```sh
@@ -26,25 +121,62 @@ pnpm build:verify
## Packaging ## Packaging
```sh ```sh
pnpm build # Compile + package the current host platform into /build/release pnpm build # Compile + package for current platform → /build/release
pnpm build:dir # Compile + unpackaged directory pnpm build:dir # Compile + unpackaged directory
pnpm build:all # Build for all platforms
``` ```
- Supported targets: macOS, Windows, Linux - **Targets:** macOS (`.dmg`), Windows (`.exe` / NSIS), Linux (`.AppImage`)
- Packaging uses `electron-builder` - Packaging uses `electron-builder`
- Local packaging cleans `/build` and injects a non-mutating build version
- Local builds use ad-hoc signing (`CSC_IDENTITY_AUTO_DISCOVERY=false`) - Local builds use ad-hoc signing (`CSC_IDENTITY_AUTO_DISCOVERY=false`)
- Defaults to `--publish never`; override with `AGENTCOMPANY_ELECTRON_PUBLISH` env var - Defaults to `--publish never`; override with `AGENTCOMPANY_ELECTRON_PUBLISH`
---
## Architecture ## Architecture
``` ```
src/ src/
main/ # Electron main process, IPC, orchestration, database main/ # Electron main process — database, services, IPC, orchestration
preload/ # Typed bridge (contextIsolation) preload/ # Context-isolated bridge — DesktopApi via contextBridge
renderer/ # React UI renderer/ # React 19 UI — components, hooks, i18n
shared/ # Contracts, types shared across processes shared/ # Contracts, types, Zod schemas shared across processes
packages/ packages/
adapter-utils/ # Shared adapter utilities adapter-utils/ # Shared adapter utilities
adapter-*/ # Per-connector adapter packages adapter-claude-local/
adapter-codex-local/
adapter-gemini-local/
``` ```
### Key Design Decisions
- **Local-first:** SQLite database, secrets in OS keychain, no external services required
- **Security:** `contextIsolation: true`, `nodeIntegration: false`, Zod-validated IPC, JWT agent tokens
- **Real autonomy:** Agents communicate, delegate, escalate, and produce deliverables without manual prompting
---
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Framework | Electron 39.8.0 |
| Runtime | Node.js 22+ |
| Language | TypeScript 5.9 (strict) |
| UI | React 19, Tailwind CSS 4, Framer Motion |
| Build | electron-vite, Vite 7, electron-builder |
| Database | SQLite (WAL mode, 25+ tables) |
| Validation | Zod 4 |
| Testing | Vitest, Playwright |
---
## License
[MIT](LICENSE)
---
<p align="center">
<sub>Built with quiet authority. Ship your AI company.</sub>
</p>
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 36 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 13 KiB

+5 -19
View File
@@ -1,21 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none">
<defs> <circle cx="512" cy="512" r="480" fill="#191919"/>
<linearGradient id="bg" x1="200" x2="824" y1="96" y2="928" gradientUnits="userSpaceOnUse"> <g transform="translate(268 255) scale(4)">
<stop stop-color="#111315"/> <path fill="#FBFAF5" d="M 76.604813 111.661926 C 65.070183 111.662621 53.987053 111.662621 42.757744 111.662621 C 45.2239 101.998177 45.297546 101.949265 53.830105 102.036659 C 57.279823 102.071999 60.730202 102.04248 64.259201 102.04248 C 62.159458 93.316338 61.799103 93.009819 51.340881 91.341682 C 59.312901 73.847687 67.193756 56.553749 75.587578 38.134155 C 86.933441 62.965954 97.804642 86.758881 108.711754 110.630394 C 100.707413 111.871689 92.355965 107.246559 88.107536 98.852119 C 84.207748 91.146538 80.800591 83.191696 77.172813 75.348343 C 76.340195 73.54818 75.492958 71.754791 74.614624 69.877441 C 69.107384 75.691788 68.008286 81.475128 71.005882 88.088753 C 74.413452 95.606903 77.813919 103.128281 81.452705 111.167984 C 79.55114 111.381325 78.303734 111.521271 76.604813 111.661926 Z"/>
<stop offset="1" stop-color="#1b1f23"/> <path fill="#FBFAF5" d="M 26.805992 87.743256 C 38.616821 61.830872 50.261688 36.260269 62.470131 9.452141 C 65.732315 17.818909 71.145126 23.524567 66.258224 33.086182 C 55.518253 54.099815 46.339973 75.911873 36.533691 97.402336 C 32.699284 105.805443 26.495394 110.823158 16.012421 111.410957 C 19.72117 103.270836 23.180601 95.67794 26.805992 87.743256 Z"/>
</linearGradient> </g>
<radialGradient id="glow" cx="0" cy="0" r="1"
gradientTransform="translate(450 348) rotate(36) scale(800)" gradientUnits="userSpaceOnUse">
<stop stop-color="#d97745" stop-opacity=".13"/>
<stop offset="1" stop-color="#d97745" stop-opacity="0"/>
</radialGradient>
</defs>
<rect width="896" height="896" x="64" y="64" rx="224" fill="url(#bg)"/>
<rect width="896" height="896" x="64" y="64" rx="224" fill="url(#glow)"/>
<polygon points="490,200 534,200 578,800 298,800" fill="#d97745" fill-opacity=".94"/>
<polygon points="490,200 534,200 726,800 446,800" fill="#d97745" fill-opacity=".94"/>
<rect x="400" y="419" width="224" height="42" rx="12" fill="#ffebd7"/>
<rect x="374" y="524" width="276" height="42" rx="12" fill="#ebb48c"/>
<rect x="348" y="629" width="328" height="42" rx="12" fill="#be8c69"/>
<circle cx="512" cy="198" r="20" fill="#ffebd7"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

+2
View File
@@ -6,6 +6,8 @@ directories:
files: files:
- out/** - out/**
- package.json - package.json
- assets/icon.png
- assets/tray-icon*.png
asar: true asar: true
icon: assets/icon.png icon: assets/icon.png
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="122" height="126" viewBox="0 0 122 126" xmlns="http://www.w3.org/2000/svg">
<path id="Path" fill="#FBFAF5" stroke="none" d="M 76.604813 111.661926 C 65.070183 111.662621 53.987053 111.662621 42.757744 111.662621 C 45.2239 101.998177 45.297546 101.949265 53.830105 102.036659 C 57.279823 102.071999 60.730202 102.04248 64.259201 102.04248 C 62.159458 93.316338 61.799103 93.009819 51.340881 91.341682 C 59.312901 73.847687 67.193756 56.553749 75.587578 38.134155 C 86.933441 62.965954 97.804642 86.758881 108.711754 110.630394 C 100.707413 111.871689 92.355965 107.246559 88.107536 98.852119 C 84.207748 91.146538 80.800591 83.191696 77.172813 75.348343 C 76.340195 73.54818 75.492958 71.754791 74.614624 69.877441 C 69.107384 75.691788 68.008286 81.475128 71.005882 88.088753 C 74.413452 95.606903 77.813919 103.128281 81.452705 111.167984 C 79.55114 111.381325 78.303734 111.521271 76.604813 111.661926 Z"/>
<path id="path1" fill="#FBFAF5" stroke="none" d="M 26.805992 87.743256 C 38.616821 61.830872 50.261688 36.260269 62.470131 9.452141 C 65.732315 17.818909 71.145126 23.524567 66.258224 33.086182 C 55.518253 54.099815 46.339973 75.911873 36.533691 97.402336 C 32.699284 105.805443 26.495394 110.823158 16.012421 111.410957 C 19.72117 103.270836 23.180601 95.67794 26.805992 87.743256 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "agent-company", "name": "agent-company",
"version": "0.4.90", "version": "0.4.92",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Cross-platform Electron desktop control plane for local-first AI agent companies.", "description": "Cross-platform Electron desktop control plane for local-first AI agent companies.",
Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

+100 -92
View File
@@ -1,19 +1,17 @@
#!/usr/bin/env python3 #!/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. Design v4: Circle background #191919 with #FBFAF5 "A" logo.
Two terracotta legs meet at an apex, with three warm crossbars of Uses the actual logo paths from logo.svg rendered via cairosvg/Pillow.
decreasing brightness (CEO → Managers → ICs). All tones stay warm.
""" """
from __future__ import annotations from __future__ import annotations
import math
import shutil import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from PIL import Image, ImageDraw, ImageFilter from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
ASSETS = ROOT / "assets" ASSETS = ROOT / "assets"
@@ -25,66 +23,40 @@ SVG_PATH = ASSETS / "icon.svg"
SIZE = 1024 SIZE = 1024
# ── Brand palette ────────────────────────────────────────────────────── # ── Brand palette ──────────────────────────────────────────────────────
TERRACOTTA = (217, 119, 69) # #d97745 BG_COLOR = (25, 25, 25) # #191919
SLATE_TOP = (17, 19, 21) # #111315 LOGO_COLOR = (251, 250, 245) # #FBFAF5
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)))
def build_icon() -> Image.Image: def build_icon() -> Image.Image:
# ── 1. Gradient background with warm glow ────────────────────────── """Build the app icon by rendering icon.svg via cairosvg, or fallback to Pillow."""
bg = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) # Try cairosvg first (best quality)
px = bg.load() try:
top_c = rgba(*SLATE_TOP) import cairosvg
bot_c = rgba(*SLATE_BOT) svg_data = SVG_PATH.read_bytes()
glow_cx, glow_cy = SIZE * 0.44, SIZE * 0.34 png_data = cairosvg.svg2png(bytestring=svg_data, output_width=SIZE, output_height=SIZE)
glow_r = SIZE * 0.78 import io
return Image.open(io.BytesIO(png_data)).convert("RGBA")
except Exception:
pass
for y in range(SIZE): # Fallback: draw programmatically with Pillow
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 ──────────────────────────────────
icon = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) icon = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
mask = Image.new("L", (SIZE, SIZE), 0) draw = ImageDraw.Draw(icon)
inset = 64
ImageDraw.Draw(mask).rounded_rectangle( # Circle background
(inset, inset, SIZE - inset, SIZE - inset), radius=224, fill=255, 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 cx = SIZE // 2
apex_y = 200 apex_y = 200
base_y = 800 base_y = 800
leg_w = 64 leg_w = 64
base_half = 250 base_half = 250
fill = (*LOGO_COLOR, 245)
def leg_x(y: int) -> tuple[float, float]: def leg_x(y: int) -> tuple[float, float]:
t = (y - apex_y) / (base_y - apex_y) t = (y - apex_y) / (base_y - apex_y)
@@ -109,67 +81,102 @@ def build_icon() -> Image.Image:
(lr_bot - leg_w, base_y), (lr_bot - leg_w, base_y),
] ]
draw.polygon(left_leg, fill=rgba(*TERRACOTTA, 240)) draw.polygon(left_leg, fill=fill)
draw.polygon(right_leg, fill=rgba(*TERRACOTTA, 240)) 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_h = 42
bar_r = 12 bar_r = 12
bar_pad = 14 bar_pad = 14
tiers = [ tiers = [
(440, rgba(*BAR_CEO)), (440, (*LOGO_COLOR, 255)),
(545, rgba(*BAR_MGR)), (545, (*LOGO_COLOR, 210)),
(650, rgba(*BAR_IC)), (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) left_cx, right_cx = leg_x(bar_cy)
bx0 = int(left_cx + bar_pad) bx0 = int(left_cx + bar_pad)
bx1 = int(right_cx - bar_pad) bx1 = int(right_cx - bar_pad)
draw.rounded_rectangle( draw.rounded_rectangle(
(bx0, bar_cy - bar_h // 2, bx1, bar_cy + bar_h // 2), (bx0, bar_cy - bar_h // 2, bx1, bar_cy + bar_h // 2),
radius=bar_r, radius=bar_r,
fill=fill, fill=bar_fill,
) )
# ── 5. Apex beacon ───────────────────────────────────────────────── # Apex beacon
dot_r = 20 dot_r = 20
dot_cy = apex_y - 2 dot_cy = apex_y - 2
draw.ellipse( draw.ellipse(
(cx - dot_r, dot_cy - dot_r, cx + dot_r, dot_cy + dot_r), (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 return icon
def save_svg() -> None: def build_tray_icons() -> None:
svg = """\ """Generate tray/notification area icons for all platforms."""
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none"> # Color tray icon (Windows/Linux) — 32x32 and 64x64
<defs> for name, size in [("tray-icon.png", 32), ("tray-icon@2x.png", 64)]:
<linearGradient id="bg" x1="200" x2="824" y1="96" y2="928" gradientUnits="userSpaceOnUse"> img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
<stop stop-color="#111315"/> draw = ImageDraw.Draw(img)
<stop offset="1" stop-color="#1b1f23"/> margin = 1
</linearGradient> draw.ellipse((margin, margin, size - margin, size - margin), fill=(*BG_COLOR, 255))
<radialGradient id="glow" cx="0" cy="0" r="1"
gradientTransform="translate(450 348) rotate(36) scale(800)" gradientUnits="userSpaceOnUse"> # Simple A shape scaled to tray size
<stop stop-color="#d97745" stop-opacity=".13"/> cx = size // 2
<stop offset="1" stop-color="#d97745" stop-opacity="0"/> top_y = int(size * 0.18)
</radialGradient> bot_y = int(size * 0.82)
</defs> spread = int(size * 0.28)
<rect width="896" height="896" x="64" y="64" rx="224" fill="url(#bg)"/> w = max(2, int(size * 0.06))
<rect width="896" height="896" x="64" y="64" rx="224" fill="url(#glow)"/>
<polygon points="490,200 534,200 578,800 298,800" fill="#d97745" fill-opacity=".94"/> draw.polygon([
<polygon points="490,200 534,200 726,800 446,800" fill="#d97745" fill-opacity=".94"/> (cx - w, top_y), (cx + w, top_y),
<rect x="400" y="419" width="224" height="42" rx="12" fill="#ffebd7"/> (cx + spread + w, bot_y), (cx + spread - w, bot_y),
<rect x="374" y="524" width="276" height="42" rx="12" fill="#ebb48c"/> ], fill=(*LOGO_COLOR, 240))
<rect x="348" y="629" width="328" height="42" rx="12" fill="#be8c69"/> draw.polygon([
<circle cx="512" cy="198" r="20" fill="#ffebd7"/> (cx - w, top_y), (cx + w, top_y),
</svg> (cx - spread + w, bot_y), (cx - spread - w, bot_y),
""" ], fill=(*LOGO_COLOR, 240))
SVG_PATH.write_text(svg, encoding="utf-8")
# 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: def save_png_and_ico(image: Image.Image) -> None:
@@ -204,11 +211,12 @@ def save_icns(image: Image.Image) -> None:
def main() -> None: def main() -> None:
ASSETS.mkdir(parents=True, exist_ok=True) ASSETS.mkdir(parents=True, exist_ok=True)
save_svg()
image = build_icon() image = build_icon()
save_png_and_ico(image) save_png_and_ico(image)
save_icns(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__": if __name__ == "__main__":
+30 -3
View File
@@ -1,5 +1,5 @@
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import { extname } from "node:path"; import { extname, dirname, join } from "node:path";
import { pathToFileURL } from "node:url"; import { pathToFileURL } from "node:url";
import { resolve, sep } from "node:path"; import { resolve, sep } from "node:path";
import { import {
@@ -275,11 +275,38 @@ export function createDesktopShellService({
} }
function createTray() { function createTray() {
if (process.platform === "darwin" || appTray) { if (appTray) {
return; return;
} }
const trayIcon = nativeImage.createFromPath(iconPath); const assetsDir = dirname(iconPath);
let trayIcon: Electron.NativeImage;
if (process.platform === "darwin") {
// macOS menu bar: use Template images (monochrome black + alpha).
// Electron auto-detects the @2x variant when the base name includes "Template".
const templatePath = join(assetsDir, "tray-iconTemplate.png");
trayIcon = nativeImage.createFromPath(templatePath);
if (!trayIcon.isEmpty()) {
trayIcon.setTemplateImage(true);
}
} else {
// Windows / Linux: use the color tray icon
const trayPath = join(assetsDir, "tray-icon.png");
trayIcon = nativeImage.createFromPath(trayPath);
}
// Fallback: resize the full app icon
if (trayIcon.isEmpty()) {
const fallback = nativeImage.createFromPath(iconPath);
if (!fallback.isEmpty()) {
trayIcon = fallback.resize({ width: 32, height: 32 });
if (process.platform === "darwin") {
trayIcon.setTemplateImage(true);
}
}
}
appTray = new Tray(trayIcon.isEmpty() ? nativeImage.createEmpty() : trayIcon); appTray = new Tray(trayIcon.isEmpty() ? nativeImage.createEmpty() : trayIcon);
appTray.setToolTip("AgentCompany"); appTray.setToolTip("AgentCompany");
appTray.setContextMenu( appTray.setContextMenu(