Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
273244817a | ||
|
|
02386999f7 | ||
|
|
69b25bbda5 | ||
|
|
a4fd98d934 |
No files matched your search
@@ -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*
|
||||||
|
|||||||
@@ -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> | <a href="https://agentcompany.pages.dev">Website</a> | <a href="#features">Features</a> | <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>
|
||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 13 KiB |
@@ -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 |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 559 B |
|
After Width: | Height: | Size: 164 B |
|
After Width: | Height: | Size: 243 B |
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,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.",
|
||||||
|
|||||||
|
After Width: | Height: | Size: 752 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 465 KiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 606 KiB |
@@ -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__":
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||