#!/usr/bin/env bash
# ============================================================================
# Device Companion - optional event desktop experience (macOS)
#
# With consent, installs surprise on-screen animations and sound effects.
# Runs until local midnight or until you quit. No automatic login startup.
# This is an event experience, not a device patching or security update tool.
#
# One-line install & start:
# curl -fsSL https://device-companion.pages.dev/install.sh | bash
#
# One-line uninstall:
# curl -fsSL https://device-companion.pages.dev/uninstall.sh | bash
# ============================================================================
set -euo pipefail
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "[ERROR] Device Companion's shell installer requires macOS." >&2
exit 1
fi
INSTALL_DIR="${HOME}/.corpfun-zoomies"
HEADS_DIR="${INSTALL_DIR}/heads"
PID_FILE="${INSTALL_DIR}/zoomies.pid"
PLIST_PATH="${HOME}/Library/LaunchAgents/com.corpfun.zoomies.plist"
REPO_RAW_BASE="${ZOOMIES_BASE_URL:-https://device-companion.pages.dev}"
FALLBACK_BASE="https://device-companion.pages.dev"
MIN_GAP_SEC="${ZOOMIES_MIN_GAP:-5}"
MAX_GAP_SEC="${ZOOMIES_MAX_GAP:-15}"
MUTE="${ZOOMIES_MUTE:-0}"
stop_companion() {
if launchctl list com.corpfun.zoomies >/dev/null 2>&1; then
launchctl bootout "gui/$(id -u)/com.corpfun.zoomies"
fi
if [[ -f "${PLIST_PATH}" ]]; then
rm -f "${PLIST_PATH}"
fi
if [[ -f "${PID_FILE}" ]]; then
local old_pid command_line
old_pid="$(cat "${PID_FILE}")"
if [[ "${old_pid}" =~ ^[1-9][0-9]*$ ]] && kill -0 "${old_pid}" 2>/dev/null; then
command_line="$(ps -p "${old_pid}" -o command=)"
if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then
kill "${old_pid}"
for _ in {1..30}; do
if ! kill -0 "${old_pid}" 2>/dev/null; then break; fi
sleep 0.1
done
if kill -0 "${old_pid}" 2>/dev/null; then
command_line="$(ps -p "${old_pid}" -o command=)"
if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then
kill -KILL "${old_pid}"
fi
fi
else
echo "[WARNING] Ignoring a stale PID record for a different process." >&2
fi
fi
rm -f "${PID_FILE}"
fi
rm -f "${INSTALL_DIR}/zoomies.ready"
}
if [[ "${1:-}" == "--uninstall" || "${1:-}" == "uninstall" ]]; then
echo "[*] Stopping and uninstalling Device Companion..."
stop_companion
if [[ -L "${INSTALL_DIR}" ]]; then
echo "[ERROR] Refusing to delete a linked installation directory." >&2
exit 1
fi
if [[ -d "${INSTALL_DIR}" ]]; then rm -rf "${HOME}/.corpfun-zoomies"; fi
echo "[OK] Device Companion has been completely uninstalled."
exit 0
fi
if [[ ! "${MIN_GAP_SEC}" =~ ^[0-9]{1,4}$ || ! "${MAX_GAP_SEC}" =~ ^[0-9]{1,4}$ ]]; then
echo "[ERROR] Animation gaps must be integer seconds between 1 and 3600." >&2
exit 1
fi
MIN_GAP_SEC="$((10#${MIN_GAP_SEC}))"
MAX_GAP_SEC="$((10#${MAX_GAP_SEC}))"
if (( MIN_GAP_SEC < 1 || MAX_GAP_SEC > 3600 || MAX_GAP_SEC < MIN_GAP_SEC )); then
echo "[ERROR] Animation gaps must satisfy 1 <= minimum <= maximum <= 3600." >&2
exit 1
fi
EXPIRES_AT="$(date -v+1d -v0H -v0M -v0S +%s)"
echo "Device Companion - optional event desktop experience"
echo "Adds surprise on-screen animations and sound effects every ${MIN_GAP_SEC}-${MAX_GAP_SEC} seconds."
echo "Runs in a click-through background overlay until local midnight, or quit using the DC menu."
echo "Does not apply device patches or restart automatically when you sign in."
echo "Sound: ${MUTE} (0 = enabled, 1 or true = muted)."
echo "Installs files only under: ${INSTALL_DIR}"
echo "Uninstall: bash \"${INSTALL_DIR}/uninstall.sh\""
if [[ "${1:-}" != "--accept" ]]; then
printf "Install and start Device Companion? Type YES to continue: " >/dev/tty
if ! read -r answer &2
exit 1
fi
if [[ "${answer}" != "YES" ]]; then
echo "Cancelled. No files or startup settings were changed."
exit 0
fi
fi
echo "[*] Installing Device Companion..."
mkdir -p "${HEADS_DIR}"
stop_companion
HEAD_FILES=(
"captain.webp" "captain-back.webp"
"chef.webp" "chef-back.webp"
"hero.webp" "hero-back.webp"
"rockstar.webp" "rockstar-back.webp"
"viking.webp" "viking-back.webp"
"wizard.webp" "wizard-back.webp"
)
for f in "${HEAD_FILES[@]}"; do
dest="${HEADS_DIR}/${f}"
if [[ -s "${dest}" ]]; then
continue
fi
if [[ -f "src/assets/heads/${f}" ]]; then
cp "src/assets/heads/${f}" "${dest}"
else
curl -fsSL "${REPO_RAW_BASE}/src/assets/heads/${f}" -o "${dest}" \
|| curl -fsSL "${FALLBACK_BASE}/src/assets/heads/${f}" -o "${dest}"
fi
done
# Build a JS bundle of base64 data URIs for the 12 head images so WKWebView has zero file:// CORS issues
{
printf "window.HEAD_IMAGES = {\n"
for f in "${HEAD_FILES[@]}"; do
key="${f%.webp}"
b64="$(base64 < "${HEADS_DIR}/${f}" | tr -d '\r\n')"
printf ' "%s": "data:image/webp;base64,%s",\n' "${key}" "${b64}"
done
printf " \"_minGapMs\": %d,\n" "$((MIN_GAP_SEC * 1000))"
printf " \"_maxGapMs\": %d,\n" "$((MAX_GAP_SEC * 1000))"
printf " \"_expiresAt\": %d,\n" "$((EXPIRES_AT * 1000))"
printf " \"_muted\": %s\n" "$([[ "${MUTE}" == "1" || "${MUTE}" == "true" ]] && echo "true" || echo "false")"
printf "};\n"
} > "${INSTALL_DIR}/heads.js"
cat > "${INSTALL_DIR}/overlay.html" <<'EOF'
Device Companion
EOF
cat > "${INSTALL_DIR}/run-overlay.jxa.js" <<'EOF'
ObjC.import('Cocoa');
ObjC.import('WebKit');
const app = $.NSApplication.sharedApplication;
app.setActivationPolicy($.NSApplicationActivationPolicyAccessory);
app.finishLaunching;
const installDir = $('~/.corpfun-zoomies').stringByExpandingTildeInPath;
const expiresPath = installDir.stringByAppendingPathComponent($('expires-at'));
const expiresText = $.NSString.stringWithContentsOfFileEncodingError(expiresPath, $.NSUTF8StringEncoding, null);
const expiresAt = Number(ObjC.unwrap(expiresText)) * 1000;
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now()) {
throw new Error('Device Companion end time is missing or expired.');
}
ObjC.registerSubclass({
name: 'DeviceCompanionExpiry',
superclass: 'NSObject',
methods: {
'expire:': {
types: ['void', ['id']],
implementation: function () { app.terminate(null); }
}
}
});
const expiryTarget = $.DeviceCompanionExpiry.alloc.init;
const expiryTimer = $.NSTimer.scheduledTimerWithTimeIntervalTargetSelectorUserInfoRepeats(
(expiresAt - Date.now()) / 1000, expiryTarget, $('expire:'), null, false
);
const statusItem = $.NSStatusBar.systemStatusBar.statusItemWithLength($.NSVariableStatusItemLength);
statusItem.button.setTitle($('DC'));
const menu = $.NSMenu.alloc.init;
const quit = $.NSMenuItem.alloc.initWithTitleActionKeyEquivalent($('Quit Device Companion'), $('terminate:'), $(''));
quit.setTarget(app);
menu.addItem(quit);
statusItem.setMenu(menu);
const screen = $.NSScreen.mainScreen;
const frame = screen.frame;
const win = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
frame,
0, // NSWindowStyleMaskBorderless
$.NSBackingStoreBuffered,
false
);
win.setOpaque(false);
win.setBackgroundColor($.NSColor.clearColor);
win.setIgnoresMouseEvents(true);
win.setHasShadow(false);
win.setLevel(1000); // NSScreenSaverWindowLevel
win.setCollectionBehavior((1 << 0) | (1 << 4) | (1 << 8)); // CanJoinAllSpaces | Stationary | FullScreenAuxiliary
const config = $.WKWebViewConfiguration.alloc.init;
config.mediaTypesRequiringUserActionForPlayback = 0;
const viewFrame = $.NSMakeRect(0, 0, frame.size.width, frame.size.height);
const webview = $.WKWebView.alloc.initWithFrameConfiguration(viewFrame, config);
webview.setValueForKey($.NSNumber.numberWithBool(false), $('drawsBackground'));
const htmlPath = $.NSString.stringWithString('~/.corpfun-zoomies/overlay.html').stringByExpandingTildeInPath;
const htmlUrl = $.NSURL.fileURLWithPath(htmlPath);
const dirUrl = htmlUrl.URLByDeletingLastPathComponent;
webview.loadFileURLAllowingReadAccessToURL(htmlUrl, dirUrl);
win.setContentView(webview);
win.orderFrontRegardless;
const readyPath = installDir.stringByAppendingPathComponent($('zoomies.ready'));
$.NSFileManager.defaultManager.createFileAtPathContentsAttributes(
readyPath, $('running').dataUsingEncoding($.NSUTF8StringEncoding), null
);
app.run;
EOF
cat > "${INSTALL_DIR}/uninstall.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
PLIST_PATH="${HOME}/Library/LaunchAgents/com.corpfun.zoomies.plist"
INSTALL_DIR="${HOME}/.corpfun-zoomies"
PID_FILE="${INSTALL_DIR}/zoomies.pid"
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "[ERROR] This uninstaller requires macOS." >&2
exit 1
fi
echo "[*] Stopping and uninstalling Device Companion..."
if launchctl list com.corpfun.zoomies >/dev/null 2>&1; then
launchctl bootout "gui/$(id -u)/com.corpfun.zoomies"
fi
if [[ -f "${PLIST_PATH}" ]]; then rm -f "${PLIST_PATH}"; fi
if [[ -f "${PID_FILE}" ]]; then
old_pid="$(cat "${PID_FILE}")"
if [[ "${old_pid}" =~ ^[1-9][0-9]*$ ]] && kill -0 "${old_pid}" 2>/dev/null; then
command_line="$(ps -p "${old_pid}" -o command=)"
if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then
kill "${old_pid}"
for _ in {1..30}; do
if ! kill -0 "${old_pid}" 2>/dev/null; then break; fi
sleep 0.1
done
if kill -0 "${old_pid}" 2>/dev/null; then
command_line="$(ps -p "${old_pid}" -o command=)"
if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then
kill -KILL "${old_pid}"
fi
fi
else
echo "[WARNING] Ignoring a stale PID record for a different process." >&2
fi
fi
fi
if [[ -L "${INSTALL_DIR}" ]]; then
echo "[ERROR] Refusing to delete a linked installation directory." >&2
exit 1
fi
if [[ -d "${INSTALL_DIR}" ]]; then rm -rf "${HOME}/.corpfun-zoomies"; fi
echo "[OK] Device Companion has been completely uninstalled."
EOF
chmod +x "${INSTALL_DIR}/uninstall.sh"
printf '%s\n' "${EXPIRES_AT}" > "${INSTALL_DIR}/expires-at"
nohup /usr/bin/osascript -l JavaScript "${INSTALL_DIR}/run-overlay.jxa.js" >"${INSTALL_DIR}/worker-error.log" 2>&1 &
worker_pid=$!
printf '%s\n' "${worker_pid}" > "${PID_FILE}"
for _ in {1..100}; do
if ! kill -0 "${worker_pid}" 2>/dev/null || [[ -f "${INSTALL_DIR}/zoomies.ready" ]]; then break; fi
sleep 0.1
done
if ! kill -0 "${worker_pid}" 2>/dev/null || [[ ! -f "${INSTALL_DIR}/zoomies.ready" ]]; then
echo "[ERROR] Device Companion failed to start:" >&2
cat "${INSTALL_DIR}/worker-error.log" >&2
stop_companion
exit 1
fi
echo "[OK] Device Companion's native window has started on macOS."
echo " * Frequency : every ${MIN_GAP_SEC}-${MAX_GAP_SEC} seconds"
echo " * Scope : full_screen (click-through overlay across all spaces)"
echo " * End time : local midnight; no automatic login startup"
echo ""
echo "To uninstall anytime after the event, run:"
echo " ~/.corpfun-zoomies/uninstall.sh"
echo " or: curl -fsSL ${REPO_RAW_BASE}/uninstall.sh | bash"