git clone https://github.com/iambriansreed/easy-otp.git && cd easy-otp && npm installThe build/ folder isn't committed, so generate the app icon once before packaging:
npm run make-iconsThis creates build/icon.icns from assets/icon.svg using macOS's iconutil and sharp. To use a different icon, replace the SVG and run it again.
npm startCompiles the TypeScript into dist/ and launches Electron. The Easy OTP icon appears in the menu bar.
npm run devRestarts the app whenever a file in src/ changes, and runs the website's dev server alongside it. Press Control-C to stop both.
Only one copy of Easy OTP can run at a time. If another copy is already open, a new launch quits straight away, so close the installed app first.
npm run demoRuns the app with the sample accounts in scripts/demo-data.txt instead of your saved ones, and restarts it when src/ changes. Use it for screenshots and for testing without real secrets.
A demo run keeps its data in a separate temporary folder, so it never reads or changes your real accounts, and it can run alongside the installed app. To load a different file, run npm run demo -- path/to/urls.txt.
npm run packBuilds an unpacked .app in dist/. This is the quickest way to test the packaged app.
npm run buildBuilds the distributable .dmg in dist/.
npm run releaseBumps the patch version and pushes the commit and its tag. The tag starts a GitHub Actions workflow that builds an unsigned .dmg and attaches it to a GitHub release.
Accounts are saved as accounts.enc in the app's user data folder, encrypted with Electron's safeStorage. If that file can't be decrypted, it is deleted and the app starts with no accounts.
src/otp.ts generates RFC 6238 codes with 6 digits, a 30-second step, and HMAC-SHA1. The algorithm, digits, and period parameters of an otpauth:// URL are ignored.
src/favicon.ts turns an issuer name into likely domains and asks DuckDuckGo's icon service for each one, then Google's if DuckDuckGo had nothing. Electron can't read .ico files, so the file includes its own ICO decoder. Icons are stored with the account as 32x32 PNG data URLs, and lookups are cached until the app quits.
The menu bar dropdown is a frameless panel window (src/menu.html) rather than a native menu. A native menu can't show grey icons that take on color when highlighted, or use custom row spacing. The comments in src/app.ts explain the focus and Spaces handling this needs, and are worth reading before changing it.
| Script | Description |
|---|---|
npm start | Compile and launch the app |
npm run dev | Relaunch the app on changes and run the website dev server |
npm run demo | Run the app with the sample accounts in scripts/demo-data.txt |
npm run pack | Build an unpacked .app for testing |
npm run build | Build a distributable .dmg |
npm run make-icons | Generate build/icon.icns from assets/icon.svg |
npm run release | Bump the patch version and push a release tag |