From 4f1696d7cfc0d7eda88ce584f219a2fe487f5af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Go=CC=81mez=20Antonio?= Date: Thu, 16 Jul 2026 16:01:49 +0200 Subject: [PATCH 1/3] fix: install esbuild's macOS platform binaries as optional dependencies @esbuild/darwin-arm64 and @esbuild/darwin-x64 were pinned as regular dependencies, so npm install failed with EBADPLATFORM on any single- architecture Mac (e.g. arm64-only) instead of just skipping the mismatched one. The postinstall script already backfills whichever platform binary is missing for universal builds, so the hard pin was both redundant and breaking fresh installs. --- package-lock.json | 8 ++++++-- package.json | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index cbe18d8a..a6de6f4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supercmd", - "version": "1.0.25-PMH", + "version": "1.0.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "supercmd", - "version": "1.0.25-PMH", + "version": "1.0.26", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -39,6 +39,10 @@ "typescript": "^5.3.3", "vite": "^5.0.11", "wait-on": "^7.2.0" + }, + "optionalDependencies": { + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12" } }, "node_modules/@alloc/quick-lru": { diff --git a/package.json b/package.json index f262a8c1..364e3e7f 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,6 @@ }, "dependencies": { "@aptabase/electron": "^0.3.1", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", "@phosphor-icons/react": "^2.1.10", "@raycast/api": "^1.104.5", "electron-builder-notarize": "^1.5.2", @@ -57,6 +55,10 @@ "react-dom": "^18.2.0", "transliteration": "^2.6.1" }, + "optionalDependencies": { + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12" + }, "build": { "appId": "com.supercmd.app", "productName": "SuperCmd", From 95c2aef152abfa3f75eba03a33852845aa833bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Go=CC=81mez=20Antonio?= Date: Thu, 16 Jul 2026 16:02:02 +0200 Subject: [PATCH 2/3] feat: add Confetti, Fireworks, Snow, and Rain effect commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds four native "fun" system commands modeled after Raycast's built-in Confetti extension, each rendering via a shared transparent always-on-top overlay window helper (showFullScreenOverlayEffect): - Confetti: multi-burst rectangle particles, tuned for wide screen coverage - Fireworks: staggered rocket launches with trails that explode into radial showers - Snow: continuously-recycled snowfall with sway, density scaled to screen size - Rain: wind-angled streaks with ground splashes, same density scaling Bursts stack instead of replacing one another — retriggering a command while a previous burst is still animating layers a new overlay window on top rather than closing the old one, matching Raycast's behavior. --- src/main/commands.ts | 48 +++++ src/main/main.ts | 445 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 451 insertions(+), 42 deletions(-) diff --git a/src/main/commands.ts b/src/main/commands.ts index 4a89f301..2a10ce71 100644 --- a/src/main/commands.ts +++ b/src/main/commands.ts @@ -67,6 +67,22 @@ const SHUTDOWN_ICON_DATA_URL = svgToBase64DataUrl( '' ); +const CONFETTI_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + +const FIREWORKS_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + +const SNOW_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + +const RAIN_ICON_DATA_URL = svgToBase64DataUrl( + '' +); + export interface CommandInfo { id: string; title: string; @@ -1308,6 +1324,38 @@ async function discoverAndBuildCommands(): Promise { keywords: ['emoji', 'picker', 'trigger', 'smiley', 'emoticon'], category: 'system', }, + { + id: 'system-confetti', + title: 'Confetti', + subtitle: 'Celebrate with a burst of confetti', + keywords: ['confetti', 'celebrate', 'party', 'fun', 'congrats', 'congratulations', 'celebration'], + iconDataUrl: CONFETTI_ICON_DATA_URL, + category: 'system', + }, + { + id: 'system-fireworks', + title: 'Fireworks', + subtitle: 'Launch a fireworks show', + keywords: ['fireworks', 'rockets', 'celebrate', 'party', 'fun', 'show', 'pyrotechnics'], + iconDataUrl: FIREWORKS_ICON_DATA_URL, + category: 'system', + }, + { + id: 'system-snow', + title: 'Snow', + subtitle: 'Let it snow on your screen', + keywords: ['snow', 'snowfall', 'winter', 'christmas', 'fun', 'weather'], + iconDataUrl: SNOW_ICON_DATA_URL, + category: 'system', + }, + { + id: 'system-rain', + title: 'Rain', + subtitle: 'Make it rain on your screen', + keywords: ['rain', 'rainfall', 'storm', 'weather', 'fun'], + iconDataUrl: RAIN_ICON_DATA_URL, + category: 'system', + }, { id: 'system-reset-launcher-position', title: 'Reset Launcher Position', diff --git a/src/main/main.ts b/src/main/main.ts index 94985e40..a759d1c7 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -2481,8 +2481,6 @@ let memoryStatusHideTimer: NodeJS.Timeout | null = null; let memoryStatusFadeFinalizeTimer: NodeJS.Timeout | null = null; let memoryStatusRenderSeq = 0; let memoryStatusHideTimerSeq = 0; -let confettiWindow: InstanceType | null = null; -let confettiCloseTimer: NodeJS.Timeout | null = null; let settingsWindow: InstanceType | null = null; let extensionStoreWindow: InstanceType | null = null; let notesWindow: InstanceType | null = null; @@ -2841,24 +2839,25 @@ function getConfettiWindowHtml(): string { const COLORS = ['#ff4d6d', '#ffd166', '#06d6a0', '#4cc9f0', '#f72585', '#b8f2e6', '#ffffff']; const particles = []; - // Two bursts from lower-left and lower-right, plus a center shower + // Two wide bursts from the bottom corners, plus a center shower, sized + // to cover most of the screen rather than three small isolated clumps. const bursts = [ - { x: canvas.width * 0.15, y: canvas.height * 0.85, angle: -Math.PI * 0.30, spread: 0.9 }, - { x: canvas.width * 0.85, y: canvas.height * 0.85, angle: -Math.PI * 0.70, spread: 0.9 }, - { x: canvas.width * 0.50, y: canvas.height * 0.55, angle: -Math.PI * 0.50, spread: 1.1 }, + { x: canvas.width * 0.06, y: canvas.height * 0.95, angle: -Math.PI * 0.28, spread: 1.0 }, + { x: canvas.width * 0.94, y: canvas.height * 0.95, angle: -Math.PI * 0.72, spread: 1.0 }, + { x: canvas.width * 0.50, y: canvas.height * 0.55, angle: -Math.PI * 0.50, spread: 1.3 }, ]; - const gravity = 0.32 * dpr; + const gravity = 0.28 * dpr; for (const b of bursts) { - const count = 110; + const count = 220; for (let i = 0; i < count; i++) { const a = b.angle + (Math.random() - 0.5) * b.spread; - const speed = (9 + Math.random() * 12) * dpr; + const speed = (14 + Math.random() * 22) * dpr; particles.push({ x: b.x, y: b.y, vx: Math.cos(a) * speed, vy: Math.sin(a) * speed, - size: (4 + Math.random() * 7) * dpr, + size: (6 + Math.random() * 10) * dpr, color: COLORS[Math.floor(Math.random() * COLORS.length)], rot: Math.random() * Math.PI * 2, rotSpeed: (Math.random() - 0.5) * 0.3, @@ -2866,7 +2865,7 @@ function getConfettiWindowHtml(): string { } } - const durationMs = 1800; + const durationMs = 2400; const start = performance.now(); function tick(now) { const elapsed = now - start; @@ -2876,7 +2875,7 @@ function getConfettiWindowHtml(): string { p.x += p.vx; p.y += p.vy; p.vy += gravity; - p.vx *= 0.993; + p.vx *= 0.996; p.rot += p.rotSpeed; ctx.save(); ctx.translate(p.x, p.y); @@ -2899,24 +2898,139 @@ function getConfettiWindowHtml(): string { `; } -function closeConfettiWindow(): void { - if (confettiCloseTimer) { - clearTimeout(confettiCloseTimer); - confettiCloseTimer = null; - } - if (confettiWindow && !confettiWindow.isDestroyed()) { - try { confettiWindow.close(); } catch {} - } - confettiWindow = null; +function getFireworksWindowHtml(): string { + return ` + + + + + + + + + +`; } -async function showConfettiBurst(): Promise { +async function showFullScreenOverlayEffect(html: string, durationMs: number, label: string): Promise { + let win: InstanceType | null = null; try { - closeConfettiWindow(); const cursor = screen.getCursorScreenPoint(); const display = screen.getDisplayNearestPoint(cursor) || screen.getPrimaryDisplay(); const { x, y, width, height } = display.bounds; - confettiWindow = new BrowserWindow({ + win = new BrowserWindow({ x, y, width, @@ -2940,30 +3054,253 @@ async function showConfettiBurst(): Promise { sandbox: true, }, }); - disableWindowAnimation(confettiWindow); - try { confettiWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } catch {} - try { confettiWindow.setIgnoreMouseEvents(true, { forward: true }); } catch {} - try { confettiWindow.setAlwaysOnTop(true, 'screen-saver'); } catch {} - const win = confettiWindow; - win.on('closed', () => { - if (confettiWindow === win) confettiWindow = null; - }); - await win.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(getConfettiWindowHtml())}`); - if (win.isDestroyed()) return; + disableWindowAnimation(win); + try { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } catch {} + try { win.setIgnoreMouseEvents(true, { forward: true }); } catch {} + try { win.setAlwaysOnTop(true, 'screen-saver'); } catch {} + const thisWin = win; + await thisWin.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`); + if (thisWin.isDestroyed()) return; try { - if (typeof (win as any).showInactive === 'function') (win as any).showInactive(); - else win.show(); + if (typeof (thisWin as any).showInactive === 'function') (thisWin as any).showInactive(); + else thisWin.show(); } catch {} - confettiCloseTimer = setTimeout(() => { - confettiCloseTimer = null; - closeConfettiWindow(); - }, 2200); + setTimeout(() => { + if (!thisWin.isDestroyed()) { + try { thisWin.close(); } catch {} + } + }, durationMs); } catch (error) { - console.warn('[Confetti] Failed to show confetti burst:', error); - closeConfettiWindow(); + console.warn(`[${label}] Failed to show overlay effect:`, error); + if (win && !win.isDestroyed()) { + try { win.close(); } catch {} + } } } +function getSnowWindowHtml(): string { + return ` + + + + + + + + + +`; +} + +function getRainWindowHtml(): string { + return ` + + + + + + + + + +`; +} + +// Each burst gets its own overlay window rather than replacing a previous +// one — firing the command again while a burst is still animating layers a +// new one on top, matching Raycast's behavior instead of wiping the screen +// clean first. +async function showConfettiBurst(): Promise { + await showFullScreenOverlayEffect(getConfettiWindowHtml(), 2800, 'Confetti'); +} + +async function showFireworksBurst(): Promise { + await showFullScreenOverlayEffect(getFireworksWindowHtml(), 4500, 'Fireworks'); +} + +async function showSnowBurst(): Promise { + await showFullScreenOverlayEffect(getSnowWindowHtml(), 7000, 'Snow'); +} + +async function showRainBurst(): Promise { + await showFullScreenOverlayEffect(getRainWindowHtml(), 7000, 'Rain'); +} + type AppUpdaterState = | 'idle' | 'unsupported' @@ -10868,6 +11205,30 @@ async function runCommandById(commandId: string, source: 'launcher' | 'hotkey' | return true; } + if (commandId === 'system-confetti') { + if (source === 'launcher') hideWindow(); + void showConfettiBurst(); + return true; + } + + if (commandId === 'system-fireworks') { + if (source === 'launcher') hideWindow(); + void showFireworksBurst(); + return true; + } + + if (commandId === 'system-snow') { + if (source === 'launcher') hideWindow(); + void showSnowBurst(); + return true; + } + + if (commandId === 'system-rain') { + if (source === 'launcher') hideWindow(); + void showRainBurst(); + return true; + } + if (commandId === 'system-reset-launcher-position') { clearWindowState(); if (mainWindow && !mainWindow.isDestroyed()) { From eb37e4c0b094da1d105a1236d3f323ba790a918c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Go=CC=81mez=20Antonio?= Date: Fri, 17 Jul 2026 07:26:45 +0200 Subject: [PATCH 3/3] feat: add settings, extension store, updates, and login item to tray menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The menu bar tray only offered Open/Quit. Adds direct access to SuperCmd Settings, the Extension Store, a manual "Check for Updates..." action, and a "Launch at Login" checkbox toggle — all standard menu-bar app affordances. Extracted the app-updater check/download/restart flow and the open-at-login apply+persist pairing into standalone functions (runAppUpdaterCheckAndInstall, setOpenAtLogin) so the tray items and their existing IPC handlers share the same code instead of duplicating it. --- src/main/main.ts | 138 +++++++++++++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 51 deletions(-) diff --git a/src/main/main.ts b/src/main/main.ts index a759d1c7..bcc7e968 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -7830,6 +7830,34 @@ function ensureAppTray(): void { }, }, { type: 'separator' }, + { + label: 'Extension Store...', + click: () => { + openExtensionStoreWindow(); + }, + }, + { + label: 'SuperCmd Settings...', + click: () => { + openSettingsWindow(); + }, + }, + { type: 'separator' }, + { + label: 'Launch at Login', + type: 'checkbox', + checked: Boolean((loadSettings() as any).openAtLogin), + click: (menuItem: any) => { + setOpenAtLogin(menuItem.checked); + }, + }, + { + label: 'Check for Updates...', + click: () => { + void runAppUpdaterCheckAndInstall(); + }, + }, + { type: 'separator' }, { label: 'Quit SuperCmd', click: () => { @@ -13705,6 +13733,55 @@ async function restartAndInstallAppUpdate(): Promise { return appUpdaterRestartPromise; } +async function runAppUpdaterCheckAndInstall(): Promise<{ success: boolean; error?: string; message?: string; state?: string }> { + ensureAppUpdaterConfigured(); + if (!appUpdater) { + void showMemoryStatusBar('error', 'Updater not available.'); + return { success: false, error: 'Updater not available' }; + } + + try { + // Step 1: Check for updates + void showMemoryStatusBar('processing', 'Checking for updates...'); + const checkStatus = await checkForAppUpdates(); + if (checkStatus.state === 'not-available') { + void showMemoryStatusBar('success', 'Already on latest version.'); + return { success: true, message: 'Already on latest version', state: checkStatus.state }; + } + if (checkStatus.state === 'error') { + void showMemoryStatusBar('error', checkStatus.message || 'Failed to check for updates'); + return { success: false, error: checkStatus.message || 'Failed to check for updates' }; + } + + // Step 2: Download if available + if (checkStatus.state === 'available') { + void showMemoryStatusBar('processing', 'Downloading update...'); + const downloadStatus = await downloadAppUpdate(); + if (downloadStatus.state === 'error') { + void showMemoryStatusBar('error', downloadStatus.message || 'Failed to download update'); + return { success: false, error: downloadStatus.message || 'Failed to download update' }; + } + } + + // Step 3: Restart if downloaded + if (appUpdaterStatusSnapshot.state === 'downloaded') { + void showMemoryStatusBar('processing', 'Restarting to install update...'); + const installed = await restartAndInstallAppUpdate(); + if (installed) { + return { success: true, message: 'Restarting to install update...', state: 'restarting' }; + } + void showMemoryStatusBar('error', 'Failed to restart for update installation'); + return { success: false, error: 'Failed to restart for update installation' }; + } + + void showMemoryStatusBar('success', checkStatus.message || 'Update check complete'); + return { success: true, message: checkStatus.message || 'Update check complete', state: checkStatus.state }; + } catch (error: any) { + void showMemoryStatusBar('error', String(error?.message || error || 'Update flow failed')); + return { success: false, error: String(error?.message || error || 'Update flow failed') }; + } +} + // ─── Shortcut Management ──────────────────────────────────────────── function applyOpenAtLogin(enabled: boolean): boolean { @@ -13720,6 +13797,14 @@ function applyOpenAtLogin(enabled: boolean): boolean { } } +function setOpenAtLogin(enabled: boolean): boolean { + const applied = applyOpenAtLogin(Boolean(enabled)); + if (applied) { + saveSettings({ openAtLogin: Boolean(enabled) } as Partial); + } + return applied; +} + function disableMacSpotlightShortcuts(): boolean { if (process.platform !== 'darwin') return false; try { @@ -14750,52 +14835,7 @@ app.whenReady().then(async () => { // Full update flow: check → download → restart ipcMain.handle('app-updater-check-and-install', async () => { - ensureAppUpdaterConfigured(); - if (!appUpdater) { - void showMemoryStatusBar('error', 'Updater not available.'); - return { success: false, error: 'Updater not available' }; - } - - try { - // Step 1: Check for updates - void showMemoryStatusBar('processing', 'Checking for updates...'); - const checkStatus = await checkForAppUpdates(); - if (checkStatus.state === 'not-available') { - void showMemoryStatusBar('success', 'Already on latest version.'); - return { success: true, message: 'Already on latest version', state: checkStatus.state }; - } - if (checkStatus.state === 'error') { - void showMemoryStatusBar('error', checkStatus.message || 'Failed to check for updates'); - return { success: false, error: checkStatus.message || 'Failed to check for updates' }; - } - - // Step 2: Download if available - if (checkStatus.state === 'available') { - void showMemoryStatusBar('processing', 'Downloading update...'); - const downloadStatus = await downloadAppUpdate(); - if (downloadStatus.state === 'error') { - void showMemoryStatusBar('error', downloadStatus.message || 'Failed to download update'); - return { success: false, error: downloadStatus.message || 'Failed to download update' }; - } - } - - // Step 3: Restart if downloaded - if (appUpdaterStatusSnapshot.state === 'downloaded') { - void showMemoryStatusBar('processing', 'Restarting to install update...'); - const installed = await restartAndInstallAppUpdate(); - if (installed) { - return { success: true, message: 'Restarting to install update...', state: 'restarting' }; - } - void showMemoryStatusBar('error', 'Failed to restart for update installation'); - return { success: false, error: 'Failed to restart for update installation' }; - } - - void showMemoryStatusBar('success', checkStatus.message || 'Update check complete'); - return { success: true, message: checkStatus.message || 'Update check complete', state: checkStatus.state }; - } catch (error: any) { - void showMemoryStatusBar('error', String(error?.message || error || 'Update flow failed')); - return { success: false, error: String(error?.message || error || 'Update flow failed') }; - } + return await runAppUpdaterCheckAndInstall(); }); function broadcastSettingsToAllWindows(result: AppSettings): void { @@ -15114,11 +15154,7 @@ app.whenReady().then(async () => { ); ipcMain.handle('set-open-at-login', (_event: any, enabled: boolean) => { - const applied = applyOpenAtLogin(Boolean(enabled)); - if (applied) { - saveSettings({ openAtLogin: Boolean(enabled) } as Partial); - } - return applied; + return setOpenAtLogin(Boolean(enabled)); }); ipcMain.handle('replace-spotlight-with-supercmd', async () => {