Add cross-platform packaging, icons, and Claude Code config discovery
- Auto-discovers Claude Code (~/.claude/settings.json) alongside Claude Desktop - Generated icons/app.icns (macOS) and icons/app.ico (Windows) from rounded PNGs - bcc.spec: PyInstaller spec for all platforms (.app on macOS, onefile on Windows/Linux) - .github/workflows/release.yml: builds and publishes GitHub Release on version tags - scripts/build_icons.py: regenerates icon files from source PNGs - requirements-dev.txt: adds pyinstaller and pillow for building - CLAUDE.md: initial repo guidance for Claude Code Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@@ -0,0 +1,129 @@
|
||||
name: Build & Release
|
||||
|
||||
# Trigger on version tags (e.g. git tag v1.0.0 && git push --tags)
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
# Manual run from Actions tab (useful for testing the workflow itself)
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
platform: macOS
|
||||
artifact: BetterClaudeConfig-macOS.zip
|
||||
- os: windows-latest
|
||||
platform: Windows
|
||||
artifact: BetterClaudeConfig-Windows.zip
|
||||
- os: ubuntu-latest
|
||||
platform: Linux
|
||||
artifact: BetterClaudeConfig-Linux.tar.gz
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Build (${{ matrix.platform }})
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements-dev.txt
|
||||
|
||||
# macOS: build app.icns from source PNGs (iconutil is built into macOS)
|
||||
- name: Generate app.icns (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: python scripts/build_icons.py
|
||||
|
||||
# Windows: generate app.ico using Pillow (already installed via requirements-dev.txt)
|
||||
- name: Generate app.ico (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: python scripts/build_icons.py
|
||||
|
||||
- name: Build with PyInstaller
|
||||
run: pyinstaller bcc.spec
|
||||
|
||||
# ── Package ──────────────────────────────────────────────────────────
|
||||
|
||||
- name: Package (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cd dist
|
||||
zip -r --symlinks "../${{ matrix.artifact }}" BetterClaudeConfig.app
|
||||
|
||||
- name: Package (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path dist\BetterClaudeConfig.exe `
|
||||
-DestinationPath "${{ matrix.artifact }}"
|
||||
|
||||
- name: Package (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
tar -czf "${{ matrix.artifact }}" -C dist BetterClaudeConfig
|
||||
|
||||
# ── Upload artifact for the release job ──────────────────────────────
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: ${{ matrix.artifact }}
|
||||
|
||||
# ── Create GitHub Release with all three artifacts ──────────────────────
|
||||
|
||||
release:
|
||||
name: Publish Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
# Only publish when a tag was pushed (not on workflow_dispatch without a tag)
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: Better Claude Config ${{ github.ref_name }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
generate_release_notes: true
|
||||
files: artifacts/*
|
||||
body: |
|
||||
## Better Claude Config ${{ github.ref_name }}
|
||||
|
||||
A GUI for managing MCP server configurations for Claude Desktop and Claude Code — no hand-editing JSON.
|
||||
|
||||
### Download
|
||||
|
||||
| Platform | File |
|
||||
|----------|------|
|
||||
| macOS | `BetterClaudeConfig-macOS.zip` — unzip and drag **BetterClaudeConfig.app** to Applications |
|
||||
| Windows | `BetterClaudeConfig-Windows.zip` — unzip and run **BetterClaudeConfig.exe** |
|
||||
| Linux | `BetterClaudeConfig-Linux.tar.gz` — extract and run **BetterClaudeConfig** |
|
||||
|
||||
### macOS note
|
||||
The app is not code-signed. On first launch, right-click → **Open** to bypass Gatekeeper, or run:
|
||||
```
|
||||
xattr -cr /Applications/BetterClaudeConfig.app
|
||||
```
|
||||
|
||||
### Requirements
|
||||
No Python installation needed — the app is self-contained.
|
||||
@@ -8,7 +8,8 @@ venv/
|
||||
# PyInstaller / build
|
||||
build/
|
||||
dist/
|
||||
*.spec
|
||||
# Note: bcc.spec is tracked; *.spec here would exclude it — keep this explicit:
|
||||
*.spec.bak
|
||||
|
||||
# App runtime
|
||||
.bcc_backups/
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Install runtime dependency
|
||||
pip install -r requirements.txt
|
||||
python bcc.py # run the GUI
|
||||
python test_core.py # run unit tests (23 tests, no GUI needed)
|
||||
|
||||
# Build a self-contained binary
|
||||
pip install -r requirements-dev.txt
|
||||
python scripts/build_icons.py # regenerate icons/app.icns + icons/app.ico if needed
|
||||
pyinstaller bcc.spec
|
||||
# macOS → dist/BetterClaudeConfig.app
|
||||
# Windows → dist/BetterClaudeConfig.exe
|
||||
# Linux → dist/BetterClaudeConfig
|
||||
```
|
||||
|
||||
Requires Python 3.10+. Runtime dependency: `PySide6>=6.6`. Build-time: `pyinstaller>=6.0`, `pillow>=10.0`.
|
||||
|
||||
## Architecture
|
||||
|
||||
The codebase is split into two layers:
|
||||
|
||||
**`bcc_core.py`** — All logic with no GUI imports. Contains:
|
||||
- `Profile` / `ServerEntry` dataclasses (the data model)
|
||||
- `discover_profiles()` — scans the platform's app-support directory for `Claude*` folders (Claude Desktop) **and** always adds `~/.claude/settings.json` (Claude Code, cross-platform)
|
||||
- `load_config` / `extract_servers` / `apply_servers` / `write_config` — the read/write pipeline; writes are atomic with rotating timestamped backups in `.bcc_backups/`
|
||||
- `parse_pasted_json()` — accepts three JSON shapes (full config, inner map, or bare server object)
|
||||
- `check_dependency()` / `diagnostics_text()` / `pin_command_path()` — PATH resolution logic; distinguishes "found on normal PATH" (ok) vs "found only on augmented PATH" (warn) vs "not found" (missing). Uses an `lru_cache`-memoized `augmented_path()` that extends the inherited PATH with common runtime locations (nvm, homebrew, cargo, volta, etc.)
|
||||
- `test_remote()` — synchronous HTTP reachability check, intended to run off the UI thread
|
||||
|
||||
**`bcc.py`** — PySide6 GUI that is a thin shell over `bcc_core`. Key classes:
|
||||
- `MainWindow` — manages the profile combo, two server tables (active/disabled), action bar, and dirty state
|
||||
- `ServerEditor` — right-panel form with a `QStackedWidget` for stdio vs remote pages; calls `core.check_dependency()` on every field change
|
||||
- `KeyValueTable` — reusable widget for env vars and headers
|
||||
- `ConnTester(QThread)` — background thread for remote reachability tests
|
||||
|
||||
**The cardinal rule**: `apply_servers()` only ever writes to `mcpServers` and `_disabledMcpServers`. All other keys in the user's config are preserved verbatim and in their original order.
|
||||
|
||||
Disabled servers are parked under `_disabledMcpServers` (which Claude Desktop ignores) so they can be re-enabled without losing their definition.
|
||||
|
||||
## Packaging
|
||||
|
||||
- **`bcc.spec`** — PyInstaller spec; handles macOS (onedir → `.app` bundle) and Windows/Linux (onefile). Platform-aware icon selection.
|
||||
- **`icons/app.icns`** (macOS) and **`icons/app.ico`** (Windows) are pre-generated and committed; source PNGs are in `icons/twin-gears/rounded/`.
|
||||
- **`scripts/build_icons.py`** — regenerates both icon files from source PNGs. Uses `iconutil` (macOS-only) for `.icns`, Pillow for `.ico`.
|
||||
- **`.github/workflows/release.yml`** — builds on all three platforms on tag push, publishes as GitHub Release assets.
|
||||
|
||||
To release: `git tag vX.Y.Z && git push --tags`.
|
||||
|
||||
## Key constants
|
||||
|
||||
- `ACCENT` at the top of `bcc.py` — the one constant to change for a rebrand
|
||||
- `KNOWN_FIELDS` in `bcc_core.py` — fields the editor renders; anything else on a server object is preserved as `_extra` in `ServerEditor`
|
||||
- `DISABLED_KEY = "_disabledMcpServers"` — the parking key for disabled servers
|
||||
- `MAX_BACKUPS = 15` — rolling backup limit per config file
|
||||
@@ -1,26 +1,39 @@
|
||||
# Better Claude Config (BCC)
|
||||
|
||||
A small, cross-platform GUI for editing the `mcpServers` block of one or more
|
||||
Claude Desktop installs — without ever hand-writing JSON.
|
||||
A small, cross-platform GUI for editing the `mcpServers` block of Claude Desktop
|
||||
and Claude Code installs — without ever hand-writing JSON.
|
||||
|
||||
BCC only ever touches `mcpServers` (and its own `_disabledMcpServers` parking
|
||||
key). Every other key in your config is preserved verbatim, in its original
|
||||
order. Each write is atomic and makes a timestamped backup first.
|
||||
|
||||
## Install & run
|
||||
## Download (no Python required)
|
||||
|
||||
Pre-built self-contained binaries are attached to every [GitHub Release](../../releases):
|
||||
|
||||
| Platform | Download | Run |
|
||||
|----------|----------|-----|
|
||||
| macOS | `BetterClaudeConfig-macOS.zip` | Unzip → drag **BetterClaudeConfig.app** to Applications |
|
||||
| Windows | `BetterClaudeConfig-Windows.zip` | Unzip → double-click **BetterClaudeConfig.exe** |
|
||||
| Linux | `BetterClaudeConfig-Linux.tar.gz` | Extract → run **BetterClaudeConfig** |
|
||||
|
||||
> **macOS Gatekeeper note:** the app is not notarized. On first launch, right-click → **Open**, or run `xattr -cr /Applications/BetterClaudeConfig.app` in a terminal.
|
||||
|
||||
## Run from source
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python bcc.py
|
||||
```
|
||||
|
||||
(Python 3.10+.)
|
||||
(Python 3.10+, PySide6 6.6+.)
|
||||
|
||||
## What it does
|
||||
|
||||
- **Auto-discovers installs** — scans the platform's app-support folder for any
|
||||
`Claude*` directory (so `Claude` and `Claude-Work` both show up), and lets you
|
||||
**Add config…** to point at any file manually.
|
||||
`Claude*` directory (so `Claude` and `Claude-Work` both show up) **and** finds
|
||||
Claude Code at `~/.claude/settings.json`. Use **Add config…** to point at any
|
||||
other file manually.
|
||||
- **Form-based editing** — name, command, args (one per line), env vars, or for
|
||||
remote servers: URL, transport, and headers. No raw JSON.
|
||||
- **Paste JSON** — drop in any snippet from an MCP doc (full `mcpServers` block,
|
||||
@@ -55,17 +68,40 @@ After saving, **restart that Claude install** for changes to take effect.
|
||||
|
||||
## Config locations it scans
|
||||
|
||||
| OS | Base it scans for `Claude*` |
|
||||
|---------|---------------------------------|
|
||||
**Claude Desktop**
|
||||
|
||||
| OS | Base it scans for `Claude*` |
|
||||
|---------|----------------------------------|
|
||||
| macOS | `~/Library/Application Support` |
|
||||
| Windows | `%APPDATA%` |
|
||||
| Linux | `~/.config` |
|
||||
|
||||
**Claude Code** (all platforms): `~/.claude/settings.json`
|
||||
|
||||
## Files
|
||||
|
||||
- `bcc.py` — the GUI.
|
||||
- `bcc_core.py` — all the file/JSON/validation/dependency logic (no GUI deps).
|
||||
- `test_core.py` — unit suite for the core (`python test_core.py`).
|
||||
- `bcc.spec` — PyInstaller build spec (cross-platform).
|
||||
- `scripts/build_icons.py` — regenerates `icons/app.icns` and `icons/app.ico` from source PNGs.
|
||||
|
||||
## Building from source
|
||||
|
||||
```bash
|
||||
pip install -r requirements-dev.txt
|
||||
python scripts/build_icons.py # regenerate icons if needed
|
||||
pyinstaller bcc.spec
|
||||
# macOS → dist/BetterClaudeConfig.app
|
||||
# Windows → dist/BetterClaudeConfig.exe
|
||||
# Linux → dist/BetterClaudeConfig
|
||||
```
|
||||
|
||||
Releases are built automatically by GitHub Actions (`.github/workflows/release.yml`) when a version tag is pushed:
|
||||
|
||||
```bash
|
||||
git tag v1.0.0 && git push --tags
|
||||
```
|
||||
|
||||
## Rebrand
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
#
|
||||
# PyInstaller spec for Better Claude Config.
|
||||
#
|
||||
# macOS → onedir build wrapped in a .app bundle (double-click native)
|
||||
# Windows → onefile .exe (no console window)
|
||||
# Linux → onefile binary (no console window)
|
||||
#
|
||||
# Build:
|
||||
# pip install pyinstaller
|
||||
# pyinstaller bcc.spec
|
||||
#
|
||||
# Output:
|
||||
# macOS → dist/BetterClaudeConfig.app
|
||||
# Windows → dist/BetterClaudeConfig.exe
|
||||
# Linux → dist/BetterClaudeConfig
|
||||
|
||||
import sys
|
||||
|
||||
APP_NAME = "BetterClaudeConfig"
|
||||
BUNDLE_ID = "com.betterclaude.config"
|
||||
|
||||
if sys.platform == "darwin":
|
||||
icon = "icons/app.icns"
|
||||
elif sys.platform == "win32":
|
||||
icon = "icons/app.ico"
|
||||
else:
|
||||
icon = None # Linux: icon embedded via .desktop file, not the binary
|
||||
|
||||
a = Analysis(
|
||||
["bcc.py"],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=["_tkinter", "tkinter"],
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
if sys.platform == "darwin":
|
||||
# ------------------------------------------------------------------ macOS
|
||||
# onedir so PyInstaller produces a proper .app bundle structure.
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name=APP_NAME,
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
console=False,
|
||||
argv_emulation=True, # lets macOS open files by passing argv
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=icon,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
name=APP_NAME,
|
||||
)
|
||||
app = BUNDLE(
|
||||
coll,
|
||||
name=f"{APP_NAME}.app",
|
||||
icon=icon,
|
||||
bundle_identifier=BUNDLE_ID,
|
||||
info_plist={
|
||||
"CFBundleName": "Better Claude Config",
|
||||
"CFBundleDisplayName": "Better Claude Config",
|
||||
"CFBundleShortVersionString": "1.0.0",
|
||||
"CFBundleVersion": "1.0.0",
|
||||
"NSHighResolutionCapable": True,
|
||||
"NSRequiresAquaSystemAppearance": False, # supports dark mode
|
||||
"LSMinimumSystemVersion": "11.0",
|
||||
},
|
||||
)
|
||||
|
||||
else:
|
||||
# ----------------------------------------------- Windows / Linux (onefile)
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name=APP_NAME,
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=icon,
|
||||
)
|
||||
@@ -78,11 +78,12 @@ def app_support_base() -> Path:
|
||||
|
||||
def discover_profiles() -> list[Profile]:
|
||||
"""
|
||||
Find every `Claude*` data directory in the platform's app-support base.
|
||||
Find every `Claude*` data directory in the platform's app-support base
|
||||
(Claude Desktop installs), then also check for a Claude Code global config.
|
||||
|
||||
This catches the standard `Claude` folder plus any sibling like
|
||||
`Claude-Work`, `Claude-Personal`, etc. A directory is listed even if the
|
||||
config file doesn't exist yet (it will be created on first save).
|
||||
Claude Desktop: scans the platform app-support folder for any `Claude*`
|
||||
directory (catches `Claude`, `Claude-Work`, etc.).
|
||||
Claude Code: always at ~/.claude/settings.json on every platform.
|
||||
"""
|
||||
base = app_support_base()
|
||||
out: list[Profile] = []
|
||||
@@ -93,6 +94,12 @@ def discover_profiles() -> list[Profile]:
|
||||
seen.add(d.name)
|
||||
cfg = d / CONFIG_FILENAME
|
||||
out.append(Profile(label=d.name, path=cfg, config_exists=cfg.is_file()))
|
||||
|
||||
# Claude Code global settings (~/.claude/settings.json) — same mcpServers format,
|
||||
# cross-platform (the ~/.claude/ directory is always in the same place).
|
||||
cc_cfg = Path.home() / ".claude" / "settings.json"
|
||||
out.append(Profile(label="Claude Code", path=cc_cfg, config_exists=cc_cfg.is_file()))
|
||||
|
||||
return out
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 750 B |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,6 @@
|
||||
# Runtime (also in requirements.txt)
|
||||
PySide6>=6.6
|
||||
|
||||
# Build / packaging
|
||||
pyinstaller>=6.0
|
||||
pillow>=10.0 # generates icons/app.ico during CI (Windows build)
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate icons/app.ico (Windows) and icons/app.icns (macOS) from the
|
||||
rounded PNG source files in icons/twin-gears/rounded/.
|
||||
|
||||
Run from the repo root:
|
||||
python scripts/build_icons.py
|
||||
|
||||
Requirements:
|
||||
pip install pillow # for .ico
|
||||
iconutil # built into macOS; for .icns
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).parent.parent
|
||||
SRC = ROOT / "icons" / "twin-gears" / "rounded"
|
||||
OUT = ROOT / "icons"
|
||||
|
||||
|
||||
def build_ico():
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
print("Pillow not installed — skipping .ico generation (pip install pillow)")
|
||||
return
|
||||
|
||||
sizes = [16, 32, 48, 64, 128, 256]
|
||||
imgs = []
|
||||
for s in sizes:
|
||||
p = SRC / f"icon-{s}.png"
|
||||
if not p.exists():
|
||||
print(f" Missing {p.name}, skipping")
|
||||
continue
|
||||
imgs.append(Image.open(p).convert("RGBA"))
|
||||
|
||||
if not imgs:
|
||||
print(" No source PNGs found — cannot build .ico")
|
||||
return
|
||||
|
||||
dest = OUT / "app.ico"
|
||||
imgs[-1].save(dest, format="ICO", append_images=imgs[:-1])
|
||||
print(f" Generated {dest.relative_to(ROOT)} ({dest.stat().st_size // 1024 + 1} KB)")
|
||||
|
||||
|
||||
def build_icns():
|
||||
if shutil.which("iconutil") is None:
|
||||
print(" iconutil not found (macOS-only) — skipping .icns generation")
|
||||
return
|
||||
|
||||
iconset = Path(tempfile.mkdtemp()) / "app.iconset"
|
||||
iconset.mkdir()
|
||||
mapping = {
|
||||
"icon_16x16.png": "icon-16.png",
|
||||
"icon_16x16@2x.png": "icon-32.png",
|
||||
"icon_32x32.png": "icon-32.png",
|
||||
"icon_32x32@2x.png": "icon-64.png",
|
||||
"icon_128x128.png": "icon-128.png",
|
||||
"icon_128x128@2x.png": "icon-256.png",
|
||||
"icon_256x256.png": "icon-256.png",
|
||||
"icon_256x256@2x.png": "icon-512.png",
|
||||
"icon_512x512.png": "icon-512.png",
|
||||
"icon_512x512@2x.png": "icon-1024.png",
|
||||
}
|
||||
for dst_name, src_name in mapping.items():
|
||||
src = SRC / src_name
|
||||
if src.exists():
|
||||
shutil.copy2(src, iconset / dst_name)
|
||||
else:
|
||||
print(f" Missing {src_name}, skipping {dst_name}")
|
||||
|
||||
dest = OUT / "app.icns"
|
||||
subprocess.run(
|
||||
["iconutil", "-c", "icns", str(iconset), "-o", str(dest)],
|
||||
check=True,
|
||||
)
|
||||
shutil.rmtree(iconset.parent)
|
||||
print(f" Generated {dest.relative_to(ROOT)} ({dest.stat().st_size // 1024} KB)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Building icons/app.ico ...")
|
||||
build_ico()
|
||||
print("Building icons/app.icns ...")
|
||||
build_icns()
|
||||
print("Done.")
|
||||
@@ -18,7 +18,8 @@ for d in ("Claude", "Claude-Work", "NotClaude"):
|
||||
c.app_support_base = lambda: base
|
||||
profs = c.discover_profiles()
|
||||
labels = sorted(p.label for p in profs)
|
||||
check("discovers Claude + Claude-Work, not NotClaude", labels == ["Claude", "Claude-Work"])
|
||||
check("discovers Claude + Claude-Work, not NotClaude",
|
||||
"Claude" in labels and "Claude-Work" in labels and "NotClaude" not in labels)
|
||||
check("flags missing config file", any(p.label=="Claude-Work" and not p.config_exists for p in profs))
|
||||
|
||||
# ---- 2. write preserves OTHER keys + order, only touches mcpServers ----
|
||||
|
||||