c118996746
- SwiftUI macOS app with C++17 code analysis engine (ObjC++ bridge) - Agentic AI loop: LLM plans → tool calls → execution → feedback loop - 15 agent tools: file ops, terminal, git, xcode build, code intel - 7 persistent terminal tools with background session management - Chat sidebar with agent step rendering and auto-apply - NVIDIA NIM API integration (Llama 3.3 70B default) - OpenAI tool_calls format with prompt-based fallback - Code editor with syntax highlighting and multi-tab support - File tree, console view, terminal view - Git integration and workspace management
288 lines
9.4 KiB
Bash
Executable File
288 lines
9.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# generate-icon.sh — Generate CxIDE app icon at all required sizes
|
|
# Uses macOS built-in sips for resizing
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
ICON_DIR="$PROJECT_DIR/Assets.xcassets/AppIcon.appiconset"
|
|
TMP_DIR=$(mktemp -d)
|
|
|
|
# Generate a 1024x1024 base icon using Python + CoreGraphics
|
|
python3 << 'PYEOF'
|
|
import subprocess, os, tempfile, json
|
|
|
|
# Create SVG-like icon using HTML canvas rendered via sips
|
|
# Instead, use the system Python to generate via CoreGraphics
|
|
svg = '''<?xml version="1.0" encoding="UTF-8"?>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
|
|
<defs>
|
|
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
|
<stop offset="100%" style="stop-color:#16213e"/>
|
|
</linearGradient>
|
|
<linearGradient id="accent" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" style="stop-color:#6666f2"/>
|
|
<stop offset="100%" style="stop-color:#4ecdc4"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<!-- Rounded rect background -->
|
|
<rect x="0" y="0" width="1024" height="1024" rx="220" ry="220" fill="url(#bg)"/>
|
|
<!-- Code bracket left -->
|
|
<text x="200" y="620" font-family="SF Mono, Menlo, monospace" font-size="420" font-weight="bold" fill="url(#accent)" opacity="0.9"><</text>
|
|
<!-- Cx text -->
|
|
<text x="340" y="610" font-family="SF Pro Display, Helvetica Neue, sans-serif" font-size="340" font-weight="800" fill="white" letter-spacing="-10">Cx</text>
|
|
<!-- Code bracket right -->
|
|
<text x="680" y="620" font-family="SF Mono, Menlo, monospace" font-size="420" font-weight="bold" fill="url(#accent)" opacity="0.9">/></text>
|
|
<!-- Subtle bottom glow -->
|
|
<ellipse cx="512" cy="820" rx="300" ry="40" fill="#4ecdc4" opacity="0.15"/>
|
|
</svg>'''
|
|
|
|
tmp = tempfile.mkdtemp()
|
|
svg_path = os.path.join(tmp, 'icon.svg')
|
|
with open(svg_path, 'w') as f:
|
|
f.write(svg)
|
|
print(svg_path)
|
|
PYEOF
|
|
|
|
echo "Generating CxIDE app icon..."
|
|
|
|
# Check for rsvg-convert or use qlmanage as fallback
|
|
SVG_PATH=$(python3 -c "
|
|
import tempfile, os
|
|
svg = '''<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\" width=\"1024\" height=\"1024\">
|
|
<defs>
|
|
<linearGradient id=\"bg\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">
|
|
<stop offset=\"0%\" style=\"stop-color:#1a1a2e\"/>
|
|
<stop offset=\"100%\" style=\"stop-color:#16213e\"/>
|
|
</linearGradient>
|
|
<linearGradient id=\"accent\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">
|
|
<stop offset=\"0%\" style=\"stop-color:#6666f2\"/>
|
|
<stop offset=\"100%\" style=\"stop-color:#4ecdc4\"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<rect x=\"0\" y=\"0\" width=\"1024\" height=\"1024\" rx=\"220\" ry=\"220\" fill=\"url(#bg)\"/>
|
|
<text x=\"200\" y=\"620\" font-family=\"SF Mono, Menlo, monospace\" font-size=\"420\" font-weight=\"bold\" fill=\"url(#accent)\" opacity=\"0.9\"><</text>
|
|
<text x=\"340\" y=\"610\" font-family=\"SF Pro Display, Helvetica Neue\" font-size=\"340\" font-weight=\"800\" fill=\"white\">>_</text>
|
|
<text x=\"680\" y=\"620\" font-family=\"SF Mono, Menlo, monospace\" font-size=\"420\" font-weight=\"bold\" fill=\"url(#accent)\" opacity=\"0.9\">/></text>
|
|
<ellipse cx=\"512\" cy=\"820\" rx=\"300\" ry=\"40\" fill=\"#4ecdc4\" opacity=\"0.15\"/>
|
|
</svg>'''
|
|
tmp = tempfile.mkdtemp()
|
|
p = os.path.join(tmp, 'icon.svg')
|
|
with open(p, 'w') as f: f.write(svg)
|
|
print(p)
|
|
")
|
|
|
|
BASE_PNG="$TMP_DIR/icon_1024.png"
|
|
|
|
# Try rsvg-convert first, fall back to qlmanage
|
|
if command -v rsvg-convert &>/dev/null; then
|
|
rsvg-convert -w 1024 -h 1024 "$SVG_PATH" -o "$BASE_PNG"
|
|
elif command -v qlmanage &>/dev/null; then
|
|
qlmanage -t -s 1024 -o "$TMP_DIR" "$SVG_PATH" 2>/dev/null
|
|
mv "$TMP_DIR/icon.svg.png" "$BASE_PNG" 2>/dev/null || true
|
|
fi
|
|
|
|
# If SVG rendering failed, generate a simple icon with Python
|
|
if [ ! -f "$BASE_PNG" ]; then
|
|
python3 << PYEOF2
|
|
import struct, zlib, os
|
|
|
|
def create_png(width, height, pixels):
|
|
"""Create a minimal PNG file from raw RGBA pixel data."""
|
|
def chunk(chunk_type, data):
|
|
c = chunk_type + data
|
|
return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
|
|
|
|
header = b'\x89PNG\r\n\x1a\n'
|
|
ihdr = chunk(b'IHDR', struct.pack('>IIBBBBB', width, height, 8, 6, 0, 0, 0))
|
|
|
|
raw = b''
|
|
for y in range(height):
|
|
raw += b'\x00' # filter byte
|
|
for x in range(width):
|
|
idx = (y * width + x) * 4
|
|
raw += bytes(pixels[idx:idx+4])
|
|
|
|
idat = chunk(b'IDAT', zlib.compress(raw, 9))
|
|
iend = chunk(b'IEND', b'')
|
|
return header + ihdr + idat + iend
|
|
|
|
W = H = 1024
|
|
pixels = [0] * (W * H * 4)
|
|
|
|
def set_pixel(x, y, r, g, b, a=255):
|
|
if 0 <= x < W and 0 <= y < H:
|
|
idx = (y * W + x) * 4
|
|
# Alpha blend
|
|
old_a = pixels[idx+3] / 255.0
|
|
new_a = a / 255.0
|
|
out_a = new_a + old_a * (1 - new_a)
|
|
if out_a > 0:
|
|
pixels[idx] = int((r * new_a + pixels[idx] * old_a * (1 - new_a)) / out_a)
|
|
pixels[idx+1] = int((g * new_a + pixels[idx+1] * old_a * (1 - new_a)) / out_a)
|
|
pixels[idx+2] = int((b * new_a + pixels[idx+2] * old_a * (1 - new_a)) / out_a)
|
|
pixels[idx+3] = int(out_a * 255)
|
|
|
|
def fill_rounded_rect(x0, y0, x1, y1, radius, r, g, b, a=255):
|
|
for y in range(y0, y1):
|
|
for x in range(x0, x1):
|
|
# Check corners
|
|
inside = True
|
|
for cx, cy in [(x0+radius, y0+radius), (x1-radius, y0+radius),
|
|
(x0+radius, y1-radius), (x1-radius, y1-radius)]:
|
|
dx = x - cx
|
|
dy = y - cy
|
|
if ((x < x0+radius or x >= x1-radius) and
|
|
(y < y0+radius or y >= y1-radius)):
|
|
if dx*dx + dy*dy > radius*radius:
|
|
inside = False
|
|
break
|
|
if inside:
|
|
# Gradient: dark blue
|
|
t = (x - x0) / (x1 - x0) * 0.3 + (y - y0) / (y1 - y0) * 0.7
|
|
rr = int(r * (1-t*0.2))
|
|
gg = int(g * (1-t*0.1))
|
|
bb = int(b * (1+t*0.1))
|
|
set_pixel(x, y, min(rr,255), min(gg,255), min(bb,255), a)
|
|
|
|
# Background - dark midnight blue with rounded corners
|
|
fill_rounded_rect(0, 0, 1024, 1024, 220, 26, 26, 46)
|
|
|
|
# Draw ">_" cursor symbol in center
|
|
def fill_rect(x0, y0, w, h, r, g, b, a=255):
|
|
for y in range(y0, y0+h):
|
|
for x in range(x0, x0+w):
|
|
set_pixel(x, y, r, g, b, a)
|
|
|
|
# Greater-than sign ">"
|
|
# Top bar of >
|
|
for i in range(180):
|
|
x = 280 + i
|
|
y = 340 + i // 2
|
|
for t in range(28):
|
|
set_pixel(x, y+t, 102, 102, 242)
|
|
|
|
# Bottom bar of >
|
|
for i in range(180):
|
|
x = 280 + i
|
|
y = 620 - i // 2
|
|
for t in range(28):
|
|
set_pixel(x, y+t, 78, 205, 196)
|
|
|
|
# Underscore cursor
|
|
fill_rect(490, 620, 240, 30, 255, 255, 255)
|
|
|
|
# Small dot decoration
|
|
for dy in range(-8, 9):
|
|
for dx in range(-8, 9):
|
|
if dx*dx + dy*dy <= 64:
|
|
set_pixel(512 + dx, 780 + dy, 78, 205, 196, 100)
|
|
|
|
png_data = create_png(W, H, pixels)
|
|
with open("$TMP_DIR/icon_1024.png", 'wb') as f:
|
|
f.write(png_data)
|
|
print("Generated 1024x1024 icon")
|
|
PYEOF2
|
|
fi
|
|
|
|
if [ ! -f "$BASE_PNG" ]; then
|
|
echo "Error: Could not generate base icon"
|
|
exit 1
|
|
fi
|
|
|
|
# Generate all required sizes
|
|
SIZES=("16" "32" "64" "128" "256" "512" "1024")
|
|
|
|
for SIZE in "${SIZES[@]}"; do
|
|
cp "$BASE_PNG" "$TMP_DIR/icon_${SIZE}.png"
|
|
sips -z "$SIZE" "$SIZE" "$TMP_DIR/icon_${SIZE}.png" --out "$ICON_DIR/icon_${SIZE}x${SIZE}.png" >/dev/null 2>&1
|
|
done
|
|
|
|
# Also create @2x versions
|
|
sips -z 32 32 "$BASE_PNG" --out "$ICON_DIR/icon_16x16@2x.png" >/dev/null 2>&1
|
|
sips -z 64 64 "$BASE_PNG" --out "$ICON_DIR/icon_32x32@2x.png" >/dev/null 2>&1
|
|
sips -z 256 256 "$BASE_PNG" --out "$ICON_DIR/icon_128x128@2x.png" >/dev/null 2>&1
|
|
sips -z 512 512 "$BASE_PNG" --out "$ICON_DIR/icon_256x256@2x.png" >/dev/null 2>&1
|
|
sips -z 1024 1024 "$BASE_PNG" --out "$ICON_DIR/icon_512x512@2x.png" >/dev/null 2>&1
|
|
|
|
# Update Contents.json
|
|
cat > "$ICON_DIR/Contents.json" << 'ICONJSON'
|
|
{
|
|
"images" : [
|
|
{
|
|
"filename" : "icon_16x16.png",
|
|
"idiom" : "mac",
|
|
"scale" : "1x",
|
|
"size" : "16x16"
|
|
},
|
|
{
|
|
"filename" : "icon_16x16@2x.png",
|
|
"idiom" : "mac",
|
|
"scale" : "2x",
|
|
"size" : "16x16"
|
|
},
|
|
{
|
|
"filename" : "icon_32x32.png",
|
|
"idiom" : "mac",
|
|
"scale" : "1x",
|
|
"size" : "32x32"
|
|
},
|
|
{
|
|
"filename" : "icon_32x32@2x.png",
|
|
"idiom" : "mac",
|
|
"scale" : "2x",
|
|
"size" : "32x32"
|
|
},
|
|
{
|
|
"filename" : "icon_128x128.png",
|
|
"idiom" : "mac",
|
|
"scale" : "1x",
|
|
"size" : "128x128"
|
|
},
|
|
{
|
|
"filename" : "icon_128x128@2x.png",
|
|
"idiom" : "mac",
|
|
"scale" : "2x",
|
|
"size" : "128x128"
|
|
},
|
|
{
|
|
"filename" : "icon_256x256.png",
|
|
"idiom" : "mac",
|
|
"scale" : "1x",
|
|
"size" : "256x256"
|
|
},
|
|
{
|
|
"filename" : "icon_256x256@2x.png",
|
|
"idiom" : "mac",
|
|
"scale" : "2x",
|
|
"size" : "256x256"
|
|
},
|
|
{
|
|
"filename" : "icon_512x512.png",
|
|
"idiom" : "mac",
|
|
"scale" : "1x",
|
|
"size" : "512x512"
|
|
},
|
|
{
|
|
"filename" : "icon_512x512@2x.png",
|
|
"idiom" : "mac",
|
|
"scale" : "2x",
|
|
"size" : "512x512"
|
|
}
|
|
],
|
|
"info" : {
|
|
"author" : "xcode",
|
|
"version" : 1
|
|
}
|
|
}
|
|
ICONJSON
|
|
|
|
# Cleanup
|
|
rm -rf "$TMP_DIR"
|
|
|
|
echo "✓ App icon generated at $ICON_DIR"
|
|
ls -la "$ICON_DIR"/*.png 2>/dev/null | wc -l | xargs -I{} echo " {} PNG files created"
|