This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Toolify is an AI-powered voice transcription and translation macOS desktop application. It combines Electron, React, and a native Swift menu-bar helper to provide global shortcut recording with automatic transcription via OpenAI Whisper API or local whisper.cpp models.
Tech Stack: Electron 38.1.2, React 19.1.1, TypeScript 5.9.2, Tailwind CSS 4.1.17, Vite 7.1.6
Platform: macOS-only (requires Accessibility permissions for media control)
npm run dev # Hot reload for both main and renderer processes
npm run start # Preview production build
npm run typecheck # Type-check all TypeScript (node + web)
npm run typecheck:node # Type-check main process only
npm run typecheck:web # Type-check renderer only
npm run lint # Run ESLint
npm run format # Format code with Prettier
npm test # Run vitest in watch mode
npm run test:ui # Run vitest with UI
npm run test:run # Run vitest once (CI mode)npm run build # Full type-check + bundle
npm run build:skip-check # Bundle without type-checking
npm run build:mac # Build macOS .app and .zip
npm run build:dmg # Build DMG installer (fast, skips typecheck)
npm run build:mac:publish # Build and publish to GitHub releasesprebuild:mac/prebuild:dmgexecutescripts/copy-whisper-executables.jsto copy whisper.cpp binaries
src/
├── main/ # Electron main process (Node.js context)
├── preload/ # Context bridge for type-safe IPC
├── renderer/ # React UI (web context)
└── shared/ # Shared TypeScript types
ToolifySwift/ # Native Swift menu-bar helper
Type-Safe IPC Bridge: All renderer-to-main communication flows through window.api exposed via contextBridge in src/preload/index.ts. The renderer never directly accesses Node.js APIs. Bidirectional patterns: invoke() for requests, on() for events.
Dual Transcription Providers:
- Remote: OpenAI Whisper API (
src/main/openai.ts) - Local: whisper.cpp binaries (
src/main/local-whisper.ts) - User toggles via
useLocalModelsetting - Models downloaded on-demand from HuggingFace CDN (never bundled)
Recording Lifecycle:
- Global shortcut triggers (via
uiohook-napi) - Main process pauses all media via AppleScript injection
- Audio recorded through Swift helper
- On stop: media resumes, transcription starts
- Result copied to clipboard, notification sent, history updated
Settings Auto-Save: No explicit "Save" button. Changes propagate immediately via window.api.saveSettings() and persist to ~/Library/Application Support/Toolify/config.json.
API Key Security: API keys are encrypted using Electron's safeStorage module before being persisted. Keys are stored securely in the system keychain and decrypted on-demand.
Local Model Management:
- Models stored in
~/Library/Application Support/Toolify/models/ - Download progress tracked via IPC events
- UI shows size, status, and supports re-download/deletion
- Model types:
base,small,medium,large-v3
Media Control System (src/main/utils/system.ts):
- Pauses Safari, Chrome, Arc, Spotify, Music, QuickTime, YouTube PiP
- Uses AppleScript/JavaScript injection via osascript
- Requires macOS Accessibility permission
Multi-Window Architecture: Managed via src/main/utils/windows.ts
- Main window (hidden by default)
- Settings window (modal, resizable)
- Recording overlay (compact/large styles)
Main Process (src/main/):
index.ts(44KB) - Main entry, recording lifecycle, IPC handlersopenai.ts- OpenAI Whisper API wrapperlocal-whisper.ts(18KB) - Local whisper.cpp model managementauto-updater.ts- GitHub-based auto-update systemutils/system.ts- Media pausing, notifications, soundutils/windows.ts- Window managementutils/settings.ts- Settings persistence via electron-storeutils/history.ts- Transcript history CRUDutils/overlay-template.ts(37KB) - Recording overlay HTML generation
Renderer (src/renderer/):
App.tsx(26KB) - Main app component, settings state managementcomponents/Settings.tsx(24KB) - Main settings containercomponents/History.tsx- Transcript history viewercomponents/Status.tsx- Recording status displaycomponents/settings/- Modular settings panels (General, Dictation, Audio, History, UpdateBanner)
Shared Types (src/shared/types/):
settings.types.ts- Settings interfacelocal-models.types.ts- LocalModelType, LocalModelInfohistory.types.ts- HistoryItem, HistorySettingsupdate.types.ts- UpdateInfo, progress types
- Never bundle Whisper models - Always downloaded post-install to keep bundle small (critical for distribution)
- Settings auto-save - No save button, changes are immediate via
window.api.saveSettings() - Type-safe IPC - Always keep
src/shared/typesin sync with preload layer (src/preload/index.ts) - macOS-only - Uses AppleScript/osascript for media control
- Accessibility permission critical - Media pausing fails without it
- Context isolation enforced - Renderer cannot access Node directly (security requirement)
- Swift helper bundled - ToolifySwift project built alongside Electron
- Global shortcut cooldown - 1-second cooldown between recordings, 1.5-second penalty for rapid presses
- Whisper executables path - Different in dev vs production (handled in
local-whisper.ts) - Metal GPU acceleration -
ggml-metal.metallibrary bundled for local model performance on Apple Silicon - API key encryption - All API keys encrypted with
safeStoragebefore persistence
- TypeScript: Strict mode enforced
- Formatting: Prettier with specific config (singleQuote: true, semi: false, printWidth: 100, trailingComma: none)
- React: Components use PascalCase; hooks/utilities use camelCase
- Tailwind: Class lists composed with
clsx+tailwind-merge - Enums: Use shared enums from
src/shared/types/settings.types.tsinstead of magic numbers - Path Aliases: Use
@rendereror@for renderer imports (configured in vitest.config.ts)
- Follow Conventional Commits:
feat(scope): description - Examples:
feat(ui): redesign local model management,chore: change settings saving type
Vitest configured but minimal test coverage exists. Test setup at src/renderer/src/test/setup.ts with jsdom environment for React component testing.
Manual QA required:
- Test OpenAI ↔ Local Whisper switching
- Verify Local Model download/re-download actions
- Confirm "Pause Media While Recording" stops/resumes Safari/Chrome/Spotify
- Validate clipboard + notification handling
- Test Metal GPU acceleration for local models (macOS)
- Verify API key encryption/decryption flow
- Always run
npm run typecheckandnpm run lintbefore commits
- GitHub releases as update source (auto-updater via
electron-updater) - Homebrew tap:
mehmetsagir/toolifyformula - DMG and ZIP artifacts for macOS arm64/x64
- Not code-signed (user must bypass Gatekeeper with right-click > Open)
- Version display and GitHub link shown in settings UI
The electron-builder.yml file contains critical exclusion patterns to keep bundle size small:
- Excludes ALL model files (
*.bin,*.ggml, etc.) - models downloaded post-install only - Excludes whisper-node dev dependencies - only dist files and executables copied via prebuild script
- Unpacks
resources/**,build/whisper-node-dist/**, andbuild/whisper-executables/**from ASAR
- Dock Icon Feature Removed (commit 3de81e6): The
showDockIconsetting was removed from the codebase. Don't re-implement unless explicitly requested. - API Key Security Enhancement (commit c69d49d): API keys now encrypted with Electron
safeStorageinstead of plain text storage - Metal GPU Acceleration (commit b85cb65): Added
ggml-metal.metallibrary for Apple Silicon GPU acceleration on local models - Tailwind Design Tokens (commit 484660d): Extended design system with custom tokens - follow existing patterns in
tailwind.config.js
All renderer → main communication follows this pattern:
- Define types in
src/shared/types/ - Expose methods in
src/preload/index.tsviacontextBridge - Implement handlers in
src/main/index.tsviaipcMain.on()oripcMain.handle() - Call from renderer via
window.api.[method]()
Never bypass this pattern or directly access Node.js APIs from renderer.