mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
ProleStatus: app-window default, light splash restored, status bar controls; endpoint config via properties; build + docs
- Default to Application Window mode with full app menu; status bar overlay remains available - Restore clickable startup Splash Tip (5s) with animated GIF and live counter; use light theme (Aqua) - Status bar item: monospaced "P" icon; left click shows overlay (status mode) or main window (app mode) - Overlay: add Maximize button (□) to toggle back to main window; keep click‑through elsewhere - Global hotkey Cmd+Opt+Shift+P toggles modes; Prole menu item mirrors the same toggle and updates title dynamically - Application menu (Prole): Show Status Bar / Show Main Window (contextual), Refresh Now, Quit - Application Window layout: non‑scrolling, vertical 1‑line rows (svc, k3s aggregate, local) with top‑right timestamp; bottom‑left controls (⟳ Refresh, _ Minimize) - Parameterize endpoints via `prole.properties` (bundled + user override). Replace legacy raspberry with retropie defaults - ServiceChecker + UI read endpoints from Config loader; tooltips/labels reflect configured hosts/ports - Build script: generate `Info.plist` with `LSUIElement=false`; bundle resources (`prole-type.gif`, `prole.properties`); ad‑hoc codesign. Universal build supported - Documentation: rewrite README with technical build/run/config details and operational posture Files: - proleStatus/Sources/: AppDelegate.swift, OverlayWindow.swift, StatusView.swift, StatusItemController.swift, SplashTipWindowController.swift, MainWindowController.swift, AppStatusView.swift, ServiceChecker.swift, Config.swift - proleStatus/build.sh - proleStatus/prole.properties - proleStatus/README.md Notes: - Build verified via `./build.sh build` (arm64). App starts in App Window mode with light theme; menu + hotkey + overlay toggle operate as intended
This commit is contained in:
parent
053d907666
commit
8a6e8738db
BIN
img/proleIcon.png
Normal file
BIN
img/proleIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
87
install.py
87
install.py
@ -27,6 +27,14 @@ from installer.workstation import (
|
||||
)
|
||||
from installer import deploy as inst_deploy
|
||||
|
||||
# On macOS, set process name as early as possible so the menu bar shows 'Prole Installer'
|
||||
if platform.system() == 'Darwin':
|
||||
try:
|
||||
from Foundation import NSProcessInfo
|
||||
NSProcessInfo.processInfo().setProcessName_("Prole Installer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Get the project root directory (kept local for clarity in this legacy entry)
|
||||
PROJECT_ROOT = Path(__file__).parent.absolute()
|
||||
|
||||
@ -90,8 +98,11 @@ class ProleInstaller:
|
||||
self._bg_item = None
|
||||
try:
|
||||
from PIL import Image, ImageTk # optional
|
||||
# Use the full-frame background image per latest spec
|
||||
bg_path = PROJECT_ROOT / 'img' / 'proleLogoSepia.png'
|
||||
# Use the full-frame background image from config
|
||||
try:
|
||||
bg_path = inst_config.get_ui_background_image_path()
|
||||
except Exception:
|
||||
bg_path = PROJECT_ROOT / 'img' / 'proleLogoSepia.png'
|
||||
if bg_path.exists():
|
||||
self._bg_pil = Image.open(str(bg_path)).convert('RGBA')
|
||||
|
||||
@ -190,16 +201,13 @@ class ProleInstaller:
|
||||
|
||||
# ---------------- App identity (title, Dock icon) ----------------
|
||||
def _set_app_identity(self):
|
||||
"""Set the app name shown by Tk and attempt to set the macOS Dock icon.
|
||||
"""Set the installer identity: process/menu name and Dock icon on macOS.
|
||||
|
||||
Notes:
|
||||
- tk appname affects Tk's internal application name and may improve
|
||||
display in some OS integrations. On macOS, fully changing the menu bar
|
||||
app name from "Python" typically requires running as a bundled app
|
||||
with CFBundleName set, but we still set the Tk appname here.
|
||||
- For the Dock icon on macOS, we try to use the ProleStatus.app icns
|
||||
(capital P icon). If PyObjC (AppKit) isn't available, we fall back to
|
||||
setting a Tk window icon from a PNG/GIF in img/.
|
||||
- We set Tk's appname for consistency.
|
||||
- On macOS, attempt to set the process/menu name to 'Prole Installer'
|
||||
via NSProcessInfo if PyObjC is available.
|
||||
- Prefer the Prole.app .icns from the built app; fallback to local PNG/GIF.
|
||||
"""
|
||||
# Set Tk application name
|
||||
try:
|
||||
@ -207,11 +215,30 @@ class ProleInstaller:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# macOS Dock icon via AppKit (preferred)
|
||||
# macOS: set process/menu name and Dock icon via AppKit/Foundation
|
||||
if platform.system() == 'Darwin':
|
||||
# Try to set the visible process name for the menu bar
|
||||
try:
|
||||
from Foundation import NSProcessInfo
|
||||
NSProcessInfo.processInfo().setProcessName_("Prole Installer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Also try to retitle the first main menu item so the menu next to the Apple logo reads 'Prole Installer'
|
||||
try:
|
||||
from AppKit import NSApplication
|
||||
app = NSApplication.sharedApplication()
|
||||
main_menu = app.mainMenu()
|
||||
if main_menu is not None and main_menu.numberOfItems() > 0:
|
||||
first_item = main_menu.itemAtIndex_(0)
|
||||
if first_item is not None:
|
||||
first_item.setTitle_("Prole Installer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
icns_candidates = [
|
||||
PROJECT_ROOT / 'proleStatus' / 'dist' / 'ProleStatus.app' / 'Contents' / 'Resources' / 'AppIcon.icns',
|
||||
PROJECT_ROOT / 'proleStatus' / 'dist' / 'ProleStatus.app' / 'Contents' / 'Resources' / 'ProleStatus.icns',
|
||||
PROJECT_ROOT / 'prole-app' / 'dist' / 'Prole.app' / 'Contents' / 'Resources' / 'AppIcon.icns',
|
||||
PROJECT_ROOT / 'prole-app' / 'dist' / 'Prole.app' / 'Contents' / 'Resources' / 'Prole.icns',
|
||||
]
|
||||
icns_path = next((p for p in icns_candidates if p.exists()), None)
|
||||
if icns_path is not None:
|
||||
@ -226,10 +253,16 @@ class ProleInstaller:
|
||||
pass
|
||||
|
||||
# Fallback: Tk icon from image assets (PNG/GIF)
|
||||
# Prefer config-defined icon image
|
||||
try:
|
||||
cfg_icon = inst_config.get_ui_icon_image_path()
|
||||
except Exception:
|
||||
cfg_icon = PROJECT_ROOT / 'img' / 'proleIcon.png'
|
||||
img_candidates = [
|
||||
PROJECT_ROOT / 'img' / 'prole-type.gif',
|
||||
cfg_icon,
|
||||
PROJECT_ROOT / 'img' / 'prole-type.png',
|
||||
PROJECT_ROOT / 'img' / 'ProleStatus.png',
|
||||
PROJECT_ROOT / 'img' / 'prole-type.gif',
|
||||
PROJECT_ROOT / 'img' / 'Prole.png',
|
||||
PROJECT_ROOT / 'img' / 'proleLogoSepia.png',
|
||||
]
|
||||
for p in img_candidates:
|
||||
@ -600,7 +633,7 @@ class ProleInstaller:
|
||||
left = 56
|
||||
if not hasattr(self, 'deploy_steps'):
|
||||
self.deploy_steps = [
|
||||
{'name': 'Build ProleStatus macOS app', 'status': 'pending'},
|
||||
{'name': 'Build Prole macOS app', 'status': 'pending'},
|
||||
{'name': 'Build workstation Vagrant VM', 'status': 'pending'},
|
||||
{'name': 'Check Docker is running', 'status': 'pending'},
|
||||
{'name': 'Check or configure container registry', 'status': 'pending'},
|
||||
@ -664,7 +697,7 @@ class ProleInstaller:
|
||||
self.show_page(target)
|
||||
return
|
||||
if current_id == 'build':
|
||||
# Treat Next as Build for ProleStatus
|
||||
# Treat Next as Build for Prole
|
||||
self.perform_build()
|
||||
return
|
||||
if current_id == 'build_vm':
|
||||
@ -949,7 +982,7 @@ class ProleInstaller:
|
||||
|
||||
# Reuse existing deploy steps UI but on light theme
|
||||
self.deploy_steps = [
|
||||
{'name': 'Build ProleStatus macOS app', 'status': 'pending'},
|
||||
{'name': 'Build Prole macOS app', 'status': 'pending'},
|
||||
{'name': 'Check Docker is running', 'status': 'pending'},
|
||||
{'name': 'Check or configure container registry', 'status': 'pending'},
|
||||
{'name': 'Ensure target cluster', 'status': 'pending'},
|
||||
@ -1033,7 +1066,7 @@ class ProleInstaller:
|
||||
|
||||
# ---------------- Build integration ----------------
|
||||
def perform_build(self):
|
||||
"""Run ProleStatus build in an embedded console (Scopped bash subprocess)."""
|
||||
"""Run Prole build in an embedded console (Scoped bash subprocess)."""
|
||||
# Prepare logs dir and file
|
||||
logs_dir = PROJECT_ROOT / 'logs'
|
||||
try:
|
||||
@ -1570,7 +1603,7 @@ class ProleInstaller:
|
||||
if logp or vlog:
|
||||
self._render_paragraph('Build output was captured from the embedded console.', y=90)
|
||||
if logp:
|
||||
self._render_paragraph('ProleStatus log:', y=120)
|
||||
self._render_paragraph('Prole build log:', y=120)
|
||||
self._render_paragraph(logp, y=140)
|
||||
y_next = 180 if logp else 120
|
||||
if vlog:
|
||||
@ -1579,7 +1612,7 @@ class ProleInstaller:
|
||||
# clickable link
|
||||
link_y = (y_next + 56) if vlog else 200
|
||||
if logp:
|
||||
link1 = self.bg_canvas.create_text(56, link_y, anchor='nw', text='Open ProleStatus log', fill='#0a84ff', font=('Helvetica', 12, 'underline'))
|
||||
link1 = self.bg_canvas.create_text(56, link_y, anchor='nw', text='Open Prole build log', fill='#0a84ff', font=('Helvetica', 12, 'underline'))
|
||||
self._canvas_items.append(link1)
|
||||
if vlog:
|
||||
link2 = self.bg_canvas.create_text(56, link_y + 28, anchor='nw', text='Open Vagrant log', fill='#0a84ff', font=('Helvetica', 12, 'underline'))
|
||||
@ -1993,10 +2026,10 @@ echo "All dependencies installed successfully!"
|
||||
self.deploy_widgets['Ensure target cluster']['step']['name'] = f"Ensure target cluster ({env})"
|
||||
self.deploy_widgets['Ensure target cluster']['label'].master.master.children['!label'].configure(text=f"Ensure target cluster ({env})")
|
||||
|
||||
# Step 0: Build ProleStatus macOS app
|
||||
self.update_deploy_step_status('Build ProleStatus macOS app', 'running')
|
||||
self.build_prole_status_app()
|
||||
self.update_deploy_step_status('Build ProleStatus macOS app', 'completed')
|
||||
# Step 0: Build Prole macOS app
|
||||
self.update_deploy_step_status('Build Prole macOS app', 'running')
|
||||
self.build_prole_app()
|
||||
self.update_deploy_step_status('Build Prole macOS app', 'completed')
|
||||
|
||||
# Step 1: Check Docker
|
||||
self.update_deploy_step_status('Check Docker is running', 'running')
|
||||
@ -2049,9 +2082,9 @@ echo "All dependencies installed successfully!"
|
||||
"""Check if Xcode Command Line Tools are installed via deploy helper."""
|
||||
return inst_deploy.check_xcode_tools()
|
||||
|
||||
def build_prole_status_app(self):
|
||||
def build_prole_app(self):
|
||||
"""Delegate building the native ProleStatus app to deploy helper."""
|
||||
return inst_deploy.build_prole_status_app(PROJECT_ROOT)
|
||||
return inst_deploy.build_prole_app(PROJECT_ROOT)
|
||||
|
||||
def check_docker_running(self):
|
||||
"""Check if Docker is running"""
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""
|
||||
Build helpers for the Prole installer (root-level package).
|
||||
|
||||
UI-agnostic command composition for the ProleStatus build.
|
||||
UI-agnostic command composition for the Prole macOS app build.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -9,11 +9,11 @@ from pathlib import Path
|
||||
|
||||
|
||||
def get_build_command(project_root: Path, env: str | None) -> str:
|
||||
"""Return the ProleStatus build command with an env comment suffix.
|
||||
"""Return the Prole app build command with an env comment suffix.
|
||||
|
||||
Example output:
|
||||
cd "/path/to/repo" && ./proleStatus/build.sh build # Dev
|
||||
cd "/path/to/repo" && ./prole-app/build.sh build # Dev
|
||||
"""
|
||||
env_label = (env or "Dev").strip().title()
|
||||
prole_root = str(project_root)
|
||||
return f"cd \"{prole_root}\" && ./proleStatus/build.sh build # {env_label}"
|
||||
return f"cd \"{prole_root}\" && ./prole-app/build.sh build # {env_label}"
|
||||
|
||||
@ -10,9 +10,82 @@ from pathlib import Path
|
||||
import platform
|
||||
import subprocess
|
||||
from typing import Tuple, Optional
|
||||
import os
|
||||
|
||||
# .properties loader (simple key=value, # comments)
|
||||
def _load_properties(path: Path) -> dict:
|
||||
props: dict[str, str] = {}
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
return props
|
||||
for line in text.splitlines():
|
||||
s = line.strip()
|
||||
if not s or s.startswith("#"):
|
||||
continue
|
||||
if "=" in s:
|
||||
k, v = s.split("=", 1)
|
||||
k = k.strip()
|
||||
v = v.strip()
|
||||
if k:
|
||||
props[k] = v
|
||||
return props
|
||||
|
||||
# Repository root: this file lives at <repo>/installer/config.py → parent is repo
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
PROLE_APP_DIR = PROJECT_ROOT / "prole-app"
|
||||
PROLE_PROPS_PATH = PROLE_APP_DIR / "prole.properties"
|
||||
|
||||
_PROLE_PROPS_CACHE: Optional[dict] = None
|
||||
|
||||
def get_properties() -> dict:
|
||||
"""Load and cache prole.properties from the repo (installer context).
|
||||
|
||||
Order:
|
||||
- Repo default at prole-app/prole.properties
|
||||
- Optional env override: PROLE_PROPERTIES points to a file
|
||||
"""
|
||||
global _PROLE_PROPS_CACHE
|
||||
if _PROLE_PROPS_CACHE is None:
|
||||
props: dict[str, str] = {}
|
||||
# repo default
|
||||
if PROLE_PROPS_PATH.exists():
|
||||
props.update(_load_properties(PROLE_PROPS_PATH))
|
||||
# env override (absolute path)
|
||||
env_path = os.environ.get("PROLE_PROPERTIES")
|
||||
if env_path:
|
||||
p = Path(env_path)
|
||||
if p.exists():
|
||||
props.update(_load_properties(p))
|
||||
_PROLE_PROPS_CACHE = props
|
||||
return dict(_PROLE_PROPS_CACHE)
|
||||
|
||||
def get_config_value(key: str, default: Optional[str] = None) -> Optional[str]:
|
||||
return get_properties().get(key, default)
|
||||
|
||||
def get_ui_icon_image_path() -> Path:
|
||||
"""Return absolute path to the UI icon image (for installer fallback and builds).
|
||||
|
||||
Defaults to img/proleIcon.png under repo root if not set or missing.
|
||||
"""
|
||||
rel = get_config_value("ui.icon", "img/proleIcon.png") or "img/proleIcon.png"
|
||||
p = (PROJECT_ROOT / rel).resolve()
|
||||
if p.exists():
|
||||
return p
|
||||
# fallback
|
||||
return (PROJECT_ROOT / "img/proleIcon.png").resolve()
|
||||
|
||||
def get_ui_background_image_path() -> Path:
|
||||
"""Return absolute path to the UI background image.
|
||||
|
||||
Defaults to img/proleLogoSepia.png under repo root if not set or missing.
|
||||
"""
|
||||
rel = get_config_value("ui.background", "img/proleLogoSepia.png") or "img/proleLogoSepia.png"
|
||||
p = (PROJECT_ROOT / rel).resolve()
|
||||
if p.exists():
|
||||
return p
|
||||
# fallback
|
||||
return (PROJECT_ROOT / "img/proleLogoSepia.png").resolve()
|
||||
|
||||
|
||||
def is_apple_silicon() -> bool:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""
|
||||
Deploy/build helpers for the Prole installer (root-level package).
|
||||
|
||||
Encapsulates Xcode tools check and building the native ProleStatus app.
|
||||
Encapsulates Xcode tools check and building the native Prole app.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -21,37 +21,36 @@ def check_xcode_tools() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def build_prole_status_app(project_root: Path) -> None:
|
||||
def build_prole_app(project_root: Path) -> None:
|
||||
if platform.system() != "Darwin":
|
||||
raise Exception("Building the macOS app requires macOS.")
|
||||
if not check_xcode_tools():
|
||||
raise Exception("Xcode Command Line Tools not found. Please run: xcode-select --install")
|
||||
|
||||
# Only use the top-level proleStatus project location; drop legacy
|
||||
# references to nested paths under prole/ to keep the tree clean.
|
||||
prole_status_dir = Path(project_root) / "proleStatus"
|
||||
build_script = prole_status_dir / "build.sh"
|
||||
# Use the top-level prole-app project directory
|
||||
prole_app_dir = Path(project_root) / "prole-app"
|
||||
build_script = prole_app_dir / "build.sh"
|
||||
if not build_script.exists():
|
||||
raise Exception(
|
||||
f"Build script not found at {build_script}"
|
||||
)
|
||||
|
||||
result = subprocess.run(["bash", str(build_script), "build"], cwd=prole_status_dir, capture_output=True, text=True)
|
||||
result = subprocess.run(["bash", str(build_script), "build"], cwd=prole_app_dir, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"ProleStatus build failed: {result.stderr or result.stdout}")
|
||||
raise Exception(f"Prole build failed: {result.stderr or result.stdout}")
|
||||
|
||||
app_path = prole_status_dir / "dist" / "ProleStatus.app"
|
||||
app_path = prole_app_dir / "dist" / "Prole.app"
|
||||
if not app_path.exists():
|
||||
raise Exception("Build completed but ProleStatus.app was not found in dist/")
|
||||
raise Exception("Build completed but Prole.app was not found in dist/")
|
||||
|
||||
try:
|
||||
msg = (
|
||||
"ProleStatus.app has been built successfully.\n\n"
|
||||
"Prole.app has been built successfully.\n\n"
|
||||
f"Location: {app_path}\n\n"
|
||||
"Would you like to copy it to /Applications?"
|
||||
)
|
||||
if messagebox.askyesno("ProleStatus Built", msg):
|
||||
dest = Path("/Applications") / "ProleStatus.app"
|
||||
if messagebox.askyesno("Prole Built", msg):
|
||||
dest = Path("/Applications") / "Prole.app"
|
||||
subprocess.run(["cp", "-R", str(app_path), str(dest)], check=True)
|
||||
messagebox.showinfo("Copied", f"Copied to {dest}")
|
||||
except Exception as copy_err:
|
||||
|
||||
@ -10,11 +10,11 @@
|
||||
#####################################################
|
||||
```
|
||||
|
||||
ProleStatus
|
||||
Prole
|
||||
— macOS status and control surface for Prole endpoints, with a path to an integrated virtual workstation harness.
|
||||
|
||||
Overview
|
||||
- ProleStatus provides live reachability and latency signals for Prole service endpoints. It operates in two modes: a regular application window for situational awareness and a minimalist status‑bar overlay for persistent at‑a‑glance status.
|
||||
- Prole provides live reachability and latency signals for Prole service endpoints. It operates in two modes: a regular application window for situational awareness and a minimalist status‑bar overlay for persistent at‑a‑glance status.
|
||||
- The application will evolve to include a virtual workstation surface to coordinate work across a network of Prole‑linked LLMs. The current scope is operational visibility and control of endpoints.
|
||||
|
||||
Supported platform
|
||||
@ -35,31 +35,31 @@ Build environment requirements
|
||||
- `plutil` (plist formatting, via xcrun if needed)
|
||||
|
||||
Repository layout (subset)
|
||||
- `proleStatus/` — macOS app sources and build system
|
||||
- `prole-app/` — macOS app sources and build system
|
||||
- `Sources/` — Swift sources (AppKit)
|
||||
- `build.sh` — hermetic CLI build producing a `.app` bundle
|
||||
- `prole.properties` — default endpoint configuration (bundled into Resources)
|
||||
- `dist/ProleStatus.app` — build output
|
||||
- `dist/Prole.app` — build output
|
||||
|
||||
Build script
|
||||
- The build is driven by `proleStatus/build.sh`. Typical usage:
|
||||
- The build is driven by `prole-app/build.sh`. Typical usage:
|
||||
```
|
||||
cd proleStatus
|
||||
cd prole-app
|
||||
./build.sh build # build for host arch
|
||||
./build.sh run # build (if needed) and open the app
|
||||
./build.sh build-universal # produce a universal (arm64+x86_64) binary
|
||||
./build.sh debug # run in foreground with verbose logs
|
||||
./build.sh clean # remove build artifacts
|
||||
./build.sh package # zip dist/ProleStatus.app into dist/ProleStatus.zip
|
||||
./build.sh package # zip dist/Prole.app into dist/Prole.zip
|
||||
```
|
||||
|
||||
What the script does
|
||||
- Compiles all Swift sources with `swiftc` (AppKit, Carbon, Network frameworks).
|
||||
- Generates `Contents/Info.plist` with `LSUIElement=false` so the app can present a standard menu when in Application Window mode.
|
||||
- Generates an application icon (`ProleStatus.icns`) and a template status glyph as needed.
|
||||
- Generates an application icon (`Prole.icns`) and a template status glyph as needed.
|
||||
- Copies resources:
|
||||
- `www/images/prole-type.gif` → `Contents/Resources/prole-type.gif` (for the startup tip splash)
|
||||
- `proleStatus/prole.properties` → `Contents/Resources/prole.properties`
|
||||
- `prole-app/prole.properties` → `Contents/Resources/prole.properties`
|
||||
- Performs ad‑hoc code signing of the `.app` bundle.
|
||||
|
||||
Alternate build path (installer UI)
|
||||
@ -67,7 +67,7 @@ Alternate build path (installer UI)
|
||||
```
|
||||
python3 install.py
|
||||
```
|
||||
- Use the “Build ProleStatus macOS app” step. The installer will produce `proleStatus/dist/ProleStatus.app` and can optionally copy it to `/Applications`.
|
||||
- Use the “Build Prole macOS app” step. The installer will produce `prole-app/dist/Prole.app` and can optionally copy it to `/Applications`.
|
||||
|
||||
Run modes and controls
|
||||
- Modes:
|
||||
@ -81,8 +81,8 @@ Run modes and controls
|
||||
|
||||
Configuration
|
||||
- Endpoint configuration is provided via Java‑style `key=value` properties. Two locations are read at startup; the user override has precedence:
|
||||
1. Bundled defaults: `ProleStatus.app/Contents/Resources/prole.properties`
|
||||
2. User override (optional): `~/Library/Application Support/ProleStatus/prole.properties`
|
||||
1. Bundled defaults: `Prole.app/Contents/Resources/prole.properties`
|
||||
2. User override (optional): `~/Library/Application Support/Prole/prole.properties`
|
||||
- Default keys:
|
||||
- `svc.host`, `svc.port`
|
||||
- `k3s.retropie.host`, `k3s.retropie.port`
|
||||
@ -116,7 +116,7 @@ Security & signing
|
||||
- The `.app` bundle is ad‑hoc signed by default. For distribution, replace with a Developer ID signature and notarize as appropriate for your environment.
|
||||
|
||||
Roadmap
|
||||
- Integration of a virtual workstation to orchestrate a network of Prole‑linked LLMs from within the ProleStatus surface.
|
||||
- Integration of a virtual workstation to orchestrate a network of Prole‑linked LLMs from within the Prole surface.
|
||||
|
||||
License
|
||||
- See the repository `LICENSE` file.
|
||||
@ -28,7 +28,7 @@ final class Config {
|
||||
// Load user override from Application Support
|
||||
let fm = FileManager.default
|
||||
if let appSupport = try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
|
||||
let dir = appSupport.appendingPathComponent("ProleStatus", isDirectory: true)
|
||||
let dir = appSupport.appendingPathComponent("Prole", isDirectory: true)
|
||||
let url = dir.appendingPathComponent("prole.properties")
|
||||
if fm.fileExists(atPath: url.path) {
|
||||
merge(loadProperties(url: url))
|
||||
@ -68,4 +68,14 @@ final class Config {
|
||||
|
||||
var localHost: String { string("k3d.local.host", default: "localhost") }
|
||||
var localPort: Int { int("k3d.local.port", default: 6443) }
|
||||
|
||||
// UI asset config
|
||||
// Background image file name relative to the app bundle Resources. If a path is provided, only the last component is used.
|
||||
var uiBackground: String {
|
||||
let raw = string("ui.background", default: "img/proleLogoSepia.png")
|
||||
if let lastSlash = raw.lastIndex(of: "/") {
|
||||
return String(raw[raw.index(after: lastSlash)...])
|
||||
}
|
||||
return raw
|
||||
}
|
||||
}
|
||||
@ -67,13 +67,24 @@ final class SplashTipWindowController: NSWindowController {
|
||||
counterLabel.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -4)
|
||||
])
|
||||
|
||||
// Load the animated GIF from bundle resources if present
|
||||
if let url = Bundle.main.url(forResource: "prole-type", withExtension: "gif"),
|
||||
let img = NSImage(contentsOf: url) {
|
||||
imageView.image = img
|
||||
} else {
|
||||
// Fallback: show placeholder text if the gif is missing
|
||||
let placeholder = NSTextField(labelWithString: "prole-type.gif not found")
|
||||
// Load background/splash image from config (bundled in Resources) if present
|
||||
let bgName = Config.shared.uiBackground
|
||||
let candidates: [(String, String?)] = [
|
||||
(bgName, nil),
|
||||
("prole-type", "gif")
|
||||
]
|
||||
var loaded = false
|
||||
for (res, ext) in candidates {
|
||||
if let url = Bundle.main.url(forResource: res, withExtension: ext),
|
||||
let img = NSImage(contentsOf: url) {
|
||||
imageView.image = img
|
||||
loaded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !loaded {
|
||||
// Fallback: show placeholder text if the resource is missing
|
||||
let placeholder = NSTextField(labelWithString: "Splash background not found")
|
||||
placeholder.textColor = .secondaryLabelColor
|
||||
placeholder.alignment = .center
|
||||
placeholder.translatesAutoresizingMaskIntoConstraints = false
|
||||
@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ProleStatus build script — builds a macOS .app bundle without launching Xcode
|
||||
# Prole build script — builds a macOS .app bundle without launching Xcode
|
||||
# Requirements: Xcode Command Line Tools (swiftc, codesign, plutil, lipo)
|
||||
|
||||
APP_NAME="ProleStatus"
|
||||
APP_NAME="Prole"
|
||||
BUNDLE_ID="org.prole.${APP_NAME}"
|
||||
MIN_MACOS="12.0"
|
||||
|
||||
@ -50,6 +50,21 @@ ensure_dirs() {
|
||||
mkdir -p "$BUILD_DIR" "$DIST_DIR" "$MACOS_DIR" "$RESOURCES_DIR"
|
||||
}
|
||||
|
||||
# Read a key from prole.properties (very simple parser)
|
||||
prop_get() {
|
||||
local key="$1"
|
||||
local file="$ROOT_DIR/prole.properties"
|
||||
if [[ -f "$file" ]]; then
|
||||
local line
|
||||
line=$(grep -E "^${key}=" "$file" | tail -n1 || true)
|
||||
if [[ -n "$line" ]]; then
|
||||
echo "${line#*=}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
gen_statusbar_icon_png() {
|
||||
# Generates a small monochrome template PNG for the status bar (18x18)
|
||||
local out_png="$RESOURCES_DIR/statusIcon.png"
|
||||
@ -89,15 +104,24 @@ SWIFT
|
||||
}
|
||||
|
||||
gen_app_icns() {
|
||||
# Create a basic .icns with a bold 'P' on transparent background
|
||||
# Create an .icns for the app. Prefer a configured source image (ui.icon),
|
||||
# otherwise fall back to generating a bold 'P'.
|
||||
local icon_name="${APP_NAME}"
|
||||
local iconset_dir="$BUILD_DIR/${icon_name}.iconset"
|
||||
rm -rf "$iconset_dir"
|
||||
mkdir -p "$iconset_dir"
|
||||
|
||||
# Generate a 1024 base PNG using AppKit so we don't depend on ImageMagick/Pillow
|
||||
local base_png="$BUILD_DIR/${icon_name}_1024.png"
|
||||
/usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$base_png"
|
||||
|
||||
# Try configured ui.icon relative to repo root
|
||||
local ui_icon
|
||||
ui_icon="$(prop_get ui.icon || true)"
|
||||
if [[ -n "$ui_icon" && -f "$PARENT_DIR/$ui_icon" ]]; then
|
||||
# Use the provided icon image as base; if not already 1024x1024, sips will resize for each size
|
||||
cp "$PARENT_DIR/$ui_icon" "$base_png"
|
||||
else
|
||||
# Generate a 1024 base PNG using AppKit so we don't depend on ImageMagick/Pillow
|
||||
/usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$base_png"
|
||||
import AppKit
|
||||
import Foundation
|
||||
|
||||
@ -137,6 +161,7 @@ guard let tiff = img.tiffRepresentation,
|
||||
}
|
||||
try! png.write(to: URL(fileURLWithPath: outPath))
|
||||
SWIFT
|
||||
fi
|
||||
|
||||
# Create all iconset sizes from the base image
|
||||
for s in 16 32 128 256 512; do
|
||||
@ -185,6 +210,19 @@ copy_extra_resources() {
|
||||
echo "[resources] prole-type.gif not found at $gif_src (skipping)"
|
||||
fi
|
||||
|
||||
# Copy configured background image (ui.background) into Resources, keeping basename
|
||||
local ui_bg
|
||||
ui_bg="$(prop_get ui.background || true)"
|
||||
if [[ -n "$ui_bg" && -f "$PARENT_DIR/$ui_bg" ]]; then
|
||||
mkdir -p "$RESOURCES_DIR"
|
||||
local base
|
||||
base="$(basename "$ui_bg")"
|
||||
cp "$PARENT_DIR/$ui_bg" "$RESOURCES_DIR/$base"
|
||||
echo "[resources] Copied background image ($base) into Resources"
|
||||
else
|
||||
echo "[resources] ui.background not set or file missing (skipping)"
|
||||
fi
|
||||
|
||||
# Copy default properties file if present
|
||||
local props_src="$ROOT_DIR/prole.properties"
|
||||
if [[ -f "$props_src" ]]; then
|
||||
33
prole-app/prole.properties
Normal file
33
prole-app/prole.properties
Normal file
@ -0,0 +1,33 @@
|
||||
# Prole default endpoints (bundled). Override in:
|
||||
# ~/Library/Application Support/Prole/prole.properties
|
||||
# using the same key=value format.
|
||||
|
||||
# UI assets (relative to repo root when building; file name within app bundle at runtime)
|
||||
# Icon image used for app icon generation and installer UI fallback
|
||||
ui.icon=img/proleIcon.png
|
||||
# Background image used by the app splash/tip and installer background
|
||||
ui.background=img/proleLogoSepia.png
|
||||
|
||||
# Core service
|
||||
svc.host=svc.prole.org
|
||||
svc.port=443
|
||||
|
||||
# k3s aggregate ? replace legacy raspberry with retropie
|
||||
k3s.retropie.host=retropie.prole.org
|
||||
k3s.retropie.port=6443
|
||||
# k3s.pi.host=pi.prole.org
|
||||
# k3s.pi.port=6443
|
||||
|
||||
# local k3d
|
||||
k3d.local.host=localhost
|
||||
k3d.local.port=6443
|
||||
|
||||
# internal postgres
|
||||
postgres.host=k3s.prole.org
|
||||
postgres.port=5432
|
||||
|
||||
# Reserved for future cloud endpoints
|
||||
# aws.eks.host=
|
||||
# aws.eks.port=
|
||||
# gcp.gke.host=
|
||||
# gcp.gke.port=
|
||||
@ -1,23 +0,0 @@
|
||||
# ProleStatus default endpoints (bundled). Override in:
|
||||
# ~/Library/Application Support/ProleStatus/prole.properties
|
||||
# using the same key=value format.
|
||||
|
||||
# Core service
|
||||
svc.host=svc.prole.org
|
||||
svc.port=443
|
||||
|
||||
# k3s aggregate ? replace legacy raspberry with retropie
|
||||
k3s.retropie.host=retropie.prole.org
|
||||
k3s.retropie.port=6443
|
||||
# k3s.pi.host=pi.prole.org
|
||||
# k3s.pi.port=6443
|
||||
|
||||
# local k3d
|
||||
k3d.local.host=localhost
|
||||
k3d.local.port=6443
|
||||
|
||||
# Reserved for future cloud endpoints
|
||||
# aws.eks.host=
|
||||
# aws.eks.port=
|
||||
# gcp.gke.host=
|
||||
# gcp.gke.port=
|
||||
Loading…
Reference in New Issue
Block a user