diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..a4cd617 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,36 @@ +# Change Summary + +## Version 0.0.2 + +### Cross-Platform Support + +- Added proper Windows platform support +- Made UI and controls adapt to the specific platform +- Implemented platform-specific shortcuts (Command+Shift+Space on macOS, Ctrl+Shift+Space on Windows) +- Updated requirements to reduce deprecated packages warnings + +### User Experience Improvements + +- Made the API key management more clear and fixed empty key handling +- Disabled post-processing by default for new installations +- Improved explanatory text throughout the application + +### History Features + +- Added toggle to enable/disable history recording +- Added "Clear History" button with confirmation dialog +- Implemented secure encryption of history data using system keychain/credential store +- Added status indicator to show encryption availability + +### Security Improvements + +- Utilized Electron's safeStorage API for encrypting sensitive transcript data +- Implemented proper error handling for encryption/decryption operations + +### Other Improvements + +- Various code organization improvements and refactoring + +## Version 0.0.1 + +- Initial release diff --git a/README.md b/README.md index 0f09e10..264882e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # HotMic -A lightweight desktop application that transcribes audio using the Groq API and Whisper-large-v3 model. +A lightweight desktop application that transcribes audio using either Groq or OpenAI APIs with the Whisper-large-v3 model. While an API key is required for transcription, the post-processing feature (which formats the transcription output) is optional. ## Features -- Press a global shortcut to start/stop recording -- Audio is sent to Groq API for transcription +- Cross-platform support for both Windows and macOS +- Press a global shortcut to start/stop recording (platform-specific defaults) +- Audio transcription using Groq or OpenAI Whisper API +- Multiple transcription model options (whisper-large-v3, gpt-4o-transcribe, etc.) +- Optional post-processing with Groq or OpenAI LLM to format transcripts - Results are automatically copied to clipboard - Visual feedback during recording and processing - Configurable keyboard shortcut @@ -14,26 +17,35 @@ A lightweight desktop application that transcribes audio using the Groq API and 1. Clone this repository 2. Install dependencies: - ``` + + ```bash npm install ``` + 3. Run the application: - ``` + + ```bash npm start ``` ## Configuration -1. Sign up for a Groq API account at [https://console.groq.com](https://console.groq.com) -2. Get an API key +1. Choose a provider for speech-to-text (Groq or OpenAI) +2. Get an API key from your chosen provider: + - For Groq, sign up at [https://console.groq.com](https://console.groq.com) + - For OpenAI, sign up at [https://platform.openai.com](https://platform.openai.com) 3. Enter your API key in the app settings +4. Select your preferred transcription model +5. (Optional) Enable post-processing to format transcripts ## Usage -1. Press the configured global shortcut (default: Ctrl+Shift+Space) to start recording +1. Press the configured global shortcut (default: Ctrl+Shift+Space on Windows, Command+Shift+Space on macOS) to start recording 2. Speak into your microphone 3. Press the shortcut again to stop recording and begin transcription 4. Once transcription is complete, the text will be copied to your clipboard + - By default, you'll get the raw transcription text from the Whisper model + - If post-processing is enabled, you'll get formatted text based on a customizable prompt (default: formats text as a professional email) ## Development @@ -48,4 +60,4 @@ A lightweight desktop application that transcribes audio using the Groq API and ## License -MIT \ No newline at end of file +MIT diff --git a/package-lock.json b/package-lock.json index d1e3909..5dfc9b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,28 @@ { - "name": "whisper-transcriber", - "version": "1.0.0", + "name": "hot-mic", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "whisper-transcriber", - "version": "1.0.0", + "name": "hot-mic", + "version": "0.0.2", + "cpu": [ + "x64" + ], "license": "MIT", "os": [ - "darwin" + "darwin", + "win32" ], "dependencies": { - "electron-store": "*", - "form-data": "*", - "undici": "*" + "electron-store": "^8.1.0", + "form-data": "^4.0.0", + "undici": "^6.10.1" }, "devDependencies": { - "electron": "*", - "electron-builder": "*" + "electron": "^35.0.3", + "electron-builder": "^25.1.8" }, "engines": { "node": ">=18.0.0" @@ -386,13 +390,6 @@ "node": ">= 10.0.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true, - "license": "MIT" - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -411,32 +408,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -462,40 +433,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -575,17 +512,16 @@ } }, "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, "license": "ISC", "dependencies": { - "@gar/promisify": "^1.1.3", "semver": "^7.3.5" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/@npmcli/fs/node_modules/semver": { @@ -602,9 +538,9 @@ } }, "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-3.0.0.tgz", + "integrity": "sha512-mOUBUIXsqAQBfn87vGIjBAve6JmD9PkP9Vdq2SayDqQh2Ol60hnXaBSvT4V6IQiho1otw6SipnVV1fulvOiyKQ==", "deprecated": "This functionality has been moved to @npmcli/fs", "dev": true, "license": "MIT", @@ -613,7 +549,7 @@ "rimraf": "^3.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/@pkgjs/parseargs": { @@ -850,9 +786,9 @@ } }, "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -899,13 +835,16 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -1117,18 +1056,14 @@ } }, "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.2.tgz", + "integrity": "sha512-ncSWAawFhKMJDTdoAeOV+jyW1VCMj5QIAwULIBV0SSR7B/RLPPEQiknKcg/RIIZlUQrxELpsxMiTUoAQ4sIUyg==", "deprecated": "This package is no longer supported.", "dev": true, "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/argparse": { @@ -1194,12 +1129,12 @@ } }, "node_modules/atomically": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.3.tgz", - "integrity": "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==", - "dependencies": { - "stubborn-fs": "^1.2.5", - "when-exit": "^2.1.1" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "license": "MIT", + "engines": { + "node": ">=10.12.0" } }, "node_modules/balanced-match": { @@ -1259,15 +1194,6 @@ "bluebird": "^3.5.5" } }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -1427,27 +1353,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/cacache/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -1458,17 +1363,17 @@ "node": ">=12" } }, - "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "yallist": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, "node_modules/cacheable-lookup": { @@ -1632,6 +1537,47 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -1742,23 +1688,24 @@ "license": "MIT" }, "node_modules/conf": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/conf/-/conf-13.1.0.tgz", - "integrity": "sha512-Bi6v586cy1CoTFViVO4lGTtx780lfF96fUmS1lSX6wpZf6330NvHUu6fReVuDP1de8Mg0nkZb01c8tAQdz1o3w==", - "license": "MIT", - "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "atomically": "^2.0.3", - "debounce-fn": "^6.0.0", - "dot-prop": "^9.0.0", - "env-paths": "^3.0.0", - "json-schema-typed": "^8.0.1", - "semver": "^7.6.3", - "uint8array-extras": "^1.4.0" + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "license": "MIT", + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1780,18 +1727,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/conf/node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/conf/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1821,53 +1756,6 @@ "typescript": "^5.4.3" } }, - "node_modules/config-file-ts/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/config-file-ts/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/config-file-ts/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -1938,15 +1826,15 @@ } }, "node_modules/debounce-fn": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-6.0.0.tgz", - "integrity": "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" + "mimic-fn": "^3.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2022,44 +1910,6 @@ "node": ">=10" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2069,13 +1919,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2086,14 +1929,6 @@ "node": ">=8" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -2213,15 +2048,15 @@ } }, "node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", "license": "MIT", "dependencies": { - "type-fest": "^4.18.2" + "is-obj": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2294,9 +2129,9 @@ } }, "node_modules/electron": { - "version": "35.0.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-35.0.2.tgz", - "integrity": "sha512-jo8S4GfBpVIBDGitUrv+Vo/I/ZEEs6IvWprG2KJlxayYIKpufulbQaxDt78cC/79FwFo8MA0JOIwx/b9r5NRag==", + "version": "35.0.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-35.0.3.tgz", + "integrity": "sha512-kjQAYEWXSr2TyK19IZoF85dzFIBaYuX7Yp/C+34b5Y/jmI2z270CGie+RjmEGMMitsy0G8YJKftukhYMuWlK6g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2486,16 +2321,13 @@ } }, "node_modules/electron-store": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-10.0.1.tgz", - "integrity": "sha512-Ok0bF13WWdTzZi9rCtPN8wUfwx+yDMmV6PAnCMqjNRKEXHmklW/rV+6DofV/Vf5qoAh+Bl9Bj7dQ+0W+IL2psg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.2.0.tgz", + "integrity": "sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==", "license": "MIT", "dependencies": { - "conf": "^13.0.0", - "type-fest": "^4.20.0" - }, - "engines": { - "node": ">=20" + "conf": "^10.2.0", + "type-fest": "^2.17.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2533,7 +2365,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2591,14 +2422,6 @@ "node": ">= 0.4" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2609,20 +2432,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/exponential-backoff": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", @@ -2724,6 +2533,18 @@ "node": ">=10" } }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2741,19 +2562,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", @@ -2805,12 +2613,18 @@ "node": ">= 8" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "ISC" + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/function-bind": { "version": "1.1.2", @@ -2822,9 +2636,9 @@ } }, "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.2.tgz", + "integrity": "sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ==", "deprecated": "This package is no longer supported.", "dev": true, "license": "ISC", @@ -2833,13 +2647,36 @@ "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", + "signal-exit": "^4.0.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/get-caller-file": { @@ -2906,100 +2743,40 @@ } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/global-agent/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/gopd": { @@ -3057,20 +2834,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3268,18 +3031,6 @@ "dev": true, "license": "ISC" }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3341,6 +3092,15 @@ "dev": true, "license": "MIT" }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -3476,19 +3236,11 @@ "license": "MIT" }, "node_modules/json-schema-typed": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.1.tgz", - "integrity": "sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", "license": "BSD-2-Clause" }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3579,6 +3331,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3746,18 +3511,17 @@ "node": ">=12" } }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "MIT", - "optional": true, + "license": "ISC", "dependencies": { - "escape-string-regexp": "^4.0.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, "node_modules/math-intrinsics": { @@ -3804,25 +3568,12 @@ } }, "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/mimic-response": { @@ -3862,16 +3613,13 @@ } }, "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-collect": { @@ -3887,6 +3635,19 @@ "node": ">= 8" } }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/minipass-fetch": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", @@ -3905,6 +3666,19 @@ "encoding": "^0.1.13" } }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -3918,23 +3692,23 @@ "node": ">= 8" } }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "yallist": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "license": "ISC", "dependencies": { @@ -3944,6 +3718,45 @@ "node": ">=8" } }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -3958,6 +3771,19 @@ "node": ">= 8" } }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -4125,31 +3951,20 @@ } }, "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz", + "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==", "deprecated": "This package is no longer supported.", "dev": true, "license": "ISC", "dependencies": { - "are-we-there-yet": "^3.0.0", + "are-we-there-yet": "^4.0.0", "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", + "gauge": "^5.0.0", "set-blocking": "^2.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/once": { @@ -4166,7 +3981,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -4178,6 +3992,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -4202,6 +4025,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -4228,6 +4074,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -4244,6 +4117,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4251,14 +4133,13 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/path-key": { @@ -4295,16 +4176,6 @@ "dev": true, "license": "ISC" }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/pe-library": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", @@ -4327,6 +4198,18 @@ "dev": true, "license": "MIT" }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -4539,6 +4422,13 @@ "node": ">=8" } }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -4550,41 +4440,21 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "dev": true, "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4640,45 +4510,6 @@ "semver": "bin/semver.js" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -4710,11 +4541,17 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -4853,6 +4690,19 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", @@ -4904,7 +4754,40 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -4917,6 +4800,22 @@ "node": ">=8" } }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -4931,10 +4830,15 @@ "node": ">=8" } }, - "node_modules/stubborn-fs": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz", - "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==" + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/sumchecker": { "version": "3.0.1", @@ -5088,12 +4992,12 @@ } }, "node_modules/type-fest": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", - "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=16" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5113,25 +5017,13 @@ "node": ">=14.17" } }, - "node_modules/uint8array-extras": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", - "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/undici": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.5.0.tgz", - "integrity": "sha512-NFQG741e8mJ0fLQk90xKxFdaSM7z4+IQpAgsFI36bCDY9Z2+aXXZjVy2uUksMouWfMI9+w5ejOq5zYYTBCQJDQ==", + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", + "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=18.17" } }, "node_modules/undici-types": { @@ -5227,12 +5119,6 @@ "defaults": "^1.0.3" } }, - "node_modules/when-exit": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.4.tgz", - "integrity": "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==", - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5260,18 +5146,18 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -5296,6 +5182,67 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 28cc6e4..3d90315 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hot-mic", - "version": "0.0.1", + "version": "0.0.2", "description": "Desktop app for audio transcription using Groq API and Whisper model", "type": "module", "main": "src/main.js", @@ -15,13 +15,24 @@ "author": "", "license": "MIT", "devDependencies": { - "electron": "*", - "electron-builder": "*" + "electron": "^35.0.3", + "electron-builder": "^25.1.8" }, "dependencies": { - "electron-store": "*", - "form-data": "*", - "undici": "*" + "electron-store": "^8.1.0", + "form-data": "^4.0.0", + "undici": "^6.10.1" + }, + "overrides": { + "inflight": "^2.0.0", + "rimraf": "^5.0.5", + "glob": "^10.3.10", + "@npmcli/move-file": "^3.0.0", + "@npmcli/fs": "^3.1.0", + "npmlog": "^7.0.1", + "are-we-there-yet": "^4.0.2", + "gauge": "^5.0.2", + "boolean": "^4.0.0" }, "build": { "appId": "com.hotmic.app", @@ -53,7 +64,11 @@ } }, "os": [ - "darwin" + "darwin", + "win32" + ], + "cpu": [ + "x64" ], "engines": { "node": ">=18.0.0" diff --git a/public/index.html b/public/index.html index 3fa7acd..2e45287 100644 --- a/public/index.html +++ b/public/index.html @@ -415,6 +415,7 @@ text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; + line-clamp: 2; -webkit-box-orient: vertical; } @@ -534,6 +535,17 @@ cursor: not-allowed; pointer-events: none; } + + button.danger { + background: #ff3b30; + border-color: #ff3b30; + color: white; + font-weight: 600; + } + + button.danger:hover { + filter: brightness(1.1); + } @@ -542,6 +554,7 @@ @@ -573,29 +586,96 @@

How to Use

  • Press the global shortcut to show the recording overlay
  • Speak into your microphone
  • Press the shortcut again to stop recording
  • -
  • Wait for transcription and processing
  • -
  • Processed text will be copied to clipboard
  • +
  • Wait for transcription
  • +
  • The transcribed text will be copied to your clipboard
  • +
  • If post-processing is enabled, you'll get the formatted text; otherwise, you'll get the raw transcript
  • +

    Note: An API key is required for the application to work. Post-processing (text formatting) is optional and can be enabled or disabled.

    - -
    + +
    -

    API Configuration

    - -
    - - +

    Speech-to-Text Provider

    +
    + + +
    +
    + + +
    +

    Groq API Configuration

    +
    + + +
    +
    + +
    + + +
    +
    +
    + + +
    +
    + Note: The Groq API key is required for Groq API transcriptions.
    + + +
    + + +

    Post-Processing

    - - + + +
    +
    + What is post-processing? +

    Post-processing is an optional feature that enhances your transcription. The basic transcription already provides the raw text from the Whisper model.

    +

    When post-processing is enabled, your raw transcript is sent to the selected provider's LLM with the prompt below to format and enhance it. The processed version is what gets copied to your clipboard, not the raw transcript.

    +

    To get the raw transcript instead, either disable post-processing or modify the prompt to: "Return the transcript exactly as provided with no changes."

    @@ -611,6 +691,16 @@

    Post-Processing

    +
    +
    + + +
    + +
    +
    + +
    @@ -625,7 +715,7 @@

    Post-Processing

    -

    HotMic v1.0.0

    +

    HotMic v0.0.2

    @@ -636,11 +726,25 @@

    Post-Processing

    // Application references const elements = { - // API Key elements + // API Provider elements + apiProvider: document.getElementById('apiProvider'), + groqApiSection: document.getElementById('groqApiSection'), + openaiApiSection: document.getElementById('openaiApiSection'), + + // Groq API elements + groqBaseUrl: document.getElementById('groqBaseUrl'), + groqModel: document.getElementById('groqModel'), apiKeyInput: document.getElementById('apiKey'), saveApiKeyBtn: document.getElementById('saveApiKey'), apiKeyStatus: document.getElementById('apiKeyStatus'), + // OpenAI API elements + openaiBaseUrl: document.getElementById('openaiBaseUrl'), + openaiModel: document.getElementById('openaiModel'), + openaiApiKeyInput: document.getElementById('openaiApiKey'), + saveOpenaiApiKeyBtn: document.getElementById('saveOpenaiApiKey'), + openaiApiKeyStatus: document.getElementById('openaiApiKeyStatus'), + // Shortcut elements currentShortcut: document.getElementById('currentShortcut'), editShortcut: document.getElementById('editShortcut'), @@ -654,13 +758,20 @@

    Post-Processing

    enablePostProcessing: document.getElementById('enablePostProcessing'), prompt: document.getElementById('prompt'), resetPrompt: document.getElementById('resetPrompt'), - promptStatus: document.getElementById('promptStatus') + promptStatus: document.getElementById('promptStatus'), + + // History elements + historyEnabled: document.getElementById('historyEnabled'), + clearHistory: document.getElementById('clearHistory'), + encryptionStatus: document.getElementById('encryptionStatus'), + historyList: document.getElementById('historyList') }; // Application state const state = { keys: new Set(), - listeningForShortcut: false + listeningForShortcut: false, + apiProvider: 'groq' // Default provider }; /** @@ -670,15 +781,61 @@

    Post-Processing

    // Load settings from store async function loadSettings() { try { + await loadApiProvider(); await loadApiKey(); + await loadOpenaiApiKey(); + await loadBaseUrls(); + await loadModels(); await loadShortcut(); await loadPromptSettings(); + await loadHistorySettings(); await loadHistory(); } catch (error) { console.error('Error loading settings:', error); } } + // Load API provider from store + async function loadApiProvider() { + const provider = await window.api.getApiProvider(); + if (provider) { + elements.apiProvider.value = provider; + state.apiProvider = provider; + updateApiSectionVisibility(); + } + } + + // Load base URLs from store + async function loadBaseUrls() { + const groqBaseUrl = await window.api.getGroqBaseUrl(); + if (groqBaseUrl) { + elements.groqBaseUrl.value = groqBaseUrl; + } + + const openaiBaseUrl = await window.api.getOpenaiBaseUrl(); + if (openaiBaseUrl) { + elements.openaiBaseUrl.value = openaiBaseUrl; + } + + } + + // Load models from store + async function loadModels() { + try { + const groqModel = await window.api.getGroqModel(); + if (groqModel) { + elements.groqModel.value = groqModel; + } + + const openaiModel = await window.api.getOpenaiModel(); + if (openaiModel) { + elements.openaiModel.value = openaiModel; + } + } catch (error) { + console.error('Error loading models:', error); + } + } + // Load API key from store async function loadApiKey() { const apiKey = await window.api.getApiKey(); @@ -688,6 +845,29 @@

    Post-Processing

    } } + // Load OpenAI API key from store + async function loadOpenaiApiKey() { + const apiKey = await window.api.getOpenaiApiKey(); + if (apiKey) { + elements.openaiApiKeyInput.value = apiKey; + showStatus(elements.openaiApiKeyStatus, 'API key is set', 'success'); + } + } + + // Update API section visibility based on selected provider + function updateApiSectionVisibility() { + // Hide all sections first + elements.groqApiSection.style.display = 'none'; + elements.openaiApiSection.style.display = 'none'; + + // Show selected provider section + if (state.apiProvider === 'groq') { + elements.groqApiSection.style.display = 'block'; + } else if (state.apiProvider === 'openai') { + elements.openaiApiSection.style.display = 'block'; + } + } + // Load shortcut from store async function loadShortcut() { const shortcut = await window.api.getShortcut(); @@ -718,17 +898,42 @@

    Post-Processing

    // Save API key to store async function saveApiKey() { const apiKey = elements.apiKeyInput.value.trim(); + const baseUrl = elements.groqBaseUrl.value.trim(); + const model = elements.groqModel.value; - if (!apiKey) { - showStatus(elements.apiKeyStatus, 'Please enter an API key', 'error'); - return; + try { + await window.api.setApiKey(apiKey); + await window.api.setGroqBaseUrl(baseUrl); + await window.api.setGroqModel(model); + + if (apiKey) { + showStatus(elements.apiKeyStatus, 'Settings saved successfully', 'success'); + } else { + showStatus(elements.apiKeyStatus, 'API key removed', 'success'); + } + } catch (error) { + showStatus(elements.apiKeyStatus, `Error saving settings: ${error.message}`, 'error'); } + } + + // Save OpenAI API key to store + async function saveOpenaiApiKey() { + const apiKey = elements.openaiApiKeyInput.value.trim(); + const baseUrl = elements.openaiBaseUrl.value.trim(); + const model = elements.openaiModel.value; try { - await window.api.setApiKey(apiKey); - showStatus(elements.apiKeyStatus, 'API key saved successfully', 'success'); + await window.api.setOpenaiApiKey(apiKey); + await window.api.setOpenaiBaseUrl(baseUrl); + await window.api.setOpenaiModel(model); + + if (apiKey) { + showStatus(elements.openaiApiKeyStatus, 'Settings saved successfully', 'success'); + } else { + showStatus(elements.openaiApiKeyStatus, 'API key removed', 'success'); + } } catch (error) { - showStatus(elements.apiKeyStatus, `Error saving API key: ${error.message}`, 'error'); + showStatus(elements.openaiApiKeyStatus, `Error saving settings: ${error.message}`, 'error'); } } @@ -892,8 +1097,12 @@

    Raw Transcript

    // Load and display history async function loadHistory() { const history = await window.api.getHistory(); - const historyList = document.getElementById('historyList'); - historyList.innerHTML = ''; + elements.historyList.innerHTML = ''; + + if (history.length === 0) { + elements.historyList.innerHTML = '
    No history items
    '; + return; + } history.forEach(item => { const div = document.createElement('div'); @@ -908,7 +1117,7 @@

    Raw Transcript

    ${preview.substring(0, 100)}${preview.length > 100 ? '...' : ''}
    `; - historyList.appendChild(div); + elements.historyList.appendChild(div); }); } @@ -942,6 +1151,24 @@

    Raw Transcript

    } } + // Load history settings + async function loadHistorySettings() { + try { + const settings = await window.api.getHistorySettings(); + elements.historyEnabled.checked = settings.enabled; + + // Update encryption status + const isEncrypted = await window.api.isHistoryEncrypted(); + if (isEncrypted) { + elements.encryptionStatus.innerHTML = '🔒 History is encrypted'; + } else { + elements.encryptionStatus.innerHTML = '⚠️ History is not encrypted'; + } + } catch (error) { + console.error('Error loading history settings:', error); + } + } + /** * UI Helpers */ @@ -953,66 +1180,110 @@

    Raw Transcript

    } /** - * Event Binding + * Event Listeners */ + function setupEventListeners() { + // Tab navigation + const tabButtons = document.querySelectorAll('.tab-button'); + const tabPanels = document.querySelectorAll('.tab-panel'); + + tabButtons.forEach(button => { + button.addEventListener('click', () => { + const tabId = button.getAttribute('data-tab'); + + // Update active tab button + tabButtons.forEach(btn => btn.classList.remove('active')); + button.classList.add('active'); + + // Show selected tab panel + tabPanels.forEach(panel => panel.style.display = 'none'); + document.getElementById(`${tabId}-panel`).style.display = 'block'; + }); + }); + + // Provider selection + elements.apiProvider.addEventListener('change', function() { + state.apiProvider = this.value; + window.api.setApiProvider(this.value); + updateApiSectionVisibility(); + }); - // Bind all event listeners - function bindEventListeners() { // API Key events elements.saveApiKeyBtn.addEventListener('click', saveApiKey); + elements.saveOpenaiApiKeyBtn.addEventListener('click', saveOpenaiApiKey); + + // Model and URL changes + elements.groqBaseUrl.addEventListener('change', () => elements.saveApiKeyBtn.click()); + elements.groqModel.addEventListener('change', () => elements.saveApiKeyBtn.click()); + + elements.openaiBaseUrl.addEventListener('change', () => elements.saveOpenaiApiKeyBtn.click()); + elements.openaiModel.addEventListener('change', () => elements.saveOpenaiApiKeyBtn.click()); // Shortcut events elements.editShortcut.addEventListener('click', startShortcutListener); - elements.cancelShortcut.addEventListener('click', cancelShortcutChange); elements.saveShortcut.addEventListener('click', saveShortcut); + elements.cancelShortcut.addEventListener('click', cancelShortcutChange); - // Key events for shortcut recording + // Setup key event listeners for capturing shortcut document.addEventListener('keydown', handleKeyDown); document.addEventListener('keyup', handleKeyUp); - // Post-processing settings + // Post-processing events elements.enablePostProcessing.addEventListener('change', () => { updatePromptSectionState(); savePromptSettings(); }); - elements.prompt.addEventListener('change', savePromptSettings); elements.resetPrompt.addEventListener('click', resetPrompt); + // History events + elements.historyEnabled.addEventListener('change', toggleHistoryEnabled); + elements.clearHistory.addEventListener('click', clearHistoryConfirm); + // Error handling window.api.onShortcutError && window.api.onShortcutError((message) => { showStatus(elements.shortcutStatus, `Error: ${message}`, 'error'); }); } - // Add tab switching logic - function setupTabs() { - const tabButtons = document.querySelectorAll('.tab-button'); - - tabButtons.forEach(button => { - button.addEventListener('click', () => { - // Remove active class from all buttons and panels - tabButtons.forEach(btn => btn.classList.remove('active')); - document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.remove('active')); - - // Add active class to clicked button and corresponding panel - button.classList.add('active'); - document.getElementById(`${button.dataset.tab}-panel`).classList.add('active'); - }); - }); - } - // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', () => { loadSettings(); - bindEventListeners(); - setupTabs(); + setupEventListeners(); // Listen for history updates window.api.onHistoryUpdate(() => { loadHistory(); }); }); + + // Toggle history enabled setting + async function toggleHistoryEnabled() { + try { + await window.api.setHistoryEnabled(elements.historyEnabled.checked); + // Reload history if enabled + if (elements.historyEnabled.checked) { + await loadHistory(); + } else { + // Clear display but not actual history + elements.historyList.innerHTML = '
    History is disabled
    '; + } + } catch (error) { + console.error('Error saving history settings:', error); + } + } + + // Show confirmation before clearing history + async function clearHistoryConfirm() { + if (confirm('Are you sure you want to clear all history? This cannot be undone.')) { + try { + await window.api.clearHistory(); + await loadHistory(); + } catch (error) { + console.error('Error clearing history:', error); + } + } + } diff --git a/src/main.js b/src/main.js index da3c43a..f85ba02 100644 --- a/src/main.js +++ b/src/main.js @@ -7,9 +7,17 @@ * - Communication with renderer processes * - Global shortcuts * - Tray icon + * + * CROSS-PLATFORM NOTES: + * This application supports both macOS and Windows, but some features are macOS-specific: + * - Dock icon management (show/hide in dock) + * - macOS-specific window styling (titleBarStyle, vibrancy, visualEffectState) + * + * All macOS-specific code is marked with "MACOS SPECIFIC" comments and guarded + * by the isMacOS constant (process.platform === 'darwin'). */ -import { app, BrowserWindow, ipcMain, globalShortcut, Menu, Tray, clipboard, nativeImage, screen } from 'electron'; +import { app, BrowserWindow, ipcMain, globalShortcut, Menu, Tray, clipboard, nativeImage, screen, safeStorage } from 'electron'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import https from 'node:https'; @@ -24,6 +32,12 @@ import { fetch } from 'undici'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +/** + * Platform Detection + */ +// Detect if we're running on macOS +const isMacOS = process.platform === 'darwin'; + /** * Application Configuration */ @@ -52,19 +66,42 @@ let audioData = []; * History Management */ function cleanupOldHistory() { + // Skip if history is disabled + if (!store.get('historyEnabled', true)) return []; + const history = store.get('history', []); const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000); const newHistory = history.filter(item => item.timestamp > thirtyDaysAgo); store.set('history', newHistory); + return newHistory; } function addToHistory(rawText, processedText) { + // Skip if history is disabled + if (!store.get('historyEnabled', true)) return; + const history = store.get('history', []); + + // Encrypt the transcript data if encryption is available + let encryptedRawText = rawText; + let encryptedProcessedText = processedText; + + if (safeStorage.isEncryptionAvailable()) { + try { + encryptedRawText = safeStorage.encryptString(rawText).toString('base64'); + encryptedProcessedText = safeStorage.encryptString(processedText).toString('base64'); + } catch (error) { + console.error('Error encrypting history data:', error); + } + } + history.unshift({ timestamp: Date.now(), - rawText, - processedText + rawText: encryptedRawText, + processedText: encryptedProcessedText, + encrypted: safeStorage.isEncryptionAvailable() }); + store.set('history', history); // Notify renderer of history update @@ -75,17 +112,69 @@ function addToHistory(rawText, processedText) { cleanupOldHistory(); } +function clearHistory() { + store.set('history', []); + // Notify renderer of history update + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('history-updated'); + } + return true; +} + +function getHistory() { + const history = cleanupOldHistory(); + + // If encryption is available, decrypt the history items + if (safeStorage.isEncryptionAvailable()) { + return history.map(item => { + try { + if (item.encrypted) { + return { + timestamp: item.timestamp, + rawText: safeStorage.decryptString(Buffer.from(item.rawText, 'base64')), + processedText: safeStorage.decryptString(Buffer.from(item.processedText, 'base64')) + }; + } + return item; + } catch (error) { + console.error('Error decrypting history item:', error); + return { + timestamp: item.timestamp, + rawText: 'Error: Could not decrypt transcript', + processedText: 'Error: Could not decrypt transcript' + }; + } + }); + } + + return history; +} + /** - * Post-Processing with Groq + * Post-Processing with LLM */ async function postProcessTranscript(text) { - const apiKey = store.get('apiKey'); + const apiProvider = store.get('apiProvider', 'groq'); + let apiKey = null; + let baseUrl = ''; + let model = ''; + + if (apiProvider === 'groq') { + apiKey = store.get('apiKey'); + baseUrl = store.get('groqBaseUrl', 'https://api.groq.com/openai/v1'); + model = 'llama-3.3-70b-versatile'; + } else if (apiProvider === 'openai') { + apiKey = store.get('openaiApiKey'); + baseUrl = store.get('openaiBaseUrl', 'https://api.openai.com/v1'); + model = 'gpt-4o'; + } + if (!apiKey) { throw new Error('API key not set'); } const promptSettings = store.get('promptSettings', { - enabled: true, + enabled: false, prompt: DEFAULT_PROMPT }); @@ -94,16 +183,18 @@ async function postProcessTranscript(text) { } try { - updateTranscriptionProgress('processing', 'Post-processing with Groq...'); + updateTranscriptionProgress('processing', `Post-processing with ${apiProvider === 'groq' ? 'Groq' : 'OpenAI'}...`); - const response = await fetch('https://api.groq.com/openai/v1/chat/completions', { + const endpoint = `${baseUrl}/chat/completions`.replace(/([^:]\/)\/+/g, "$1"); + + const response = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ - model: 'llama-3.3-70b-versatile', + model: model, messages: [ { role: 'system', @@ -121,7 +212,7 @@ async function postProcessTranscript(text) { if (!response.ok) { const errorData = await response.json().catch(() => ({})); - throw new Error(`Groq API error: ${response.statusText}${errorData.error ? ' - ' + errorData.error.message : ''}`); + throw new Error(`${apiProvider.toUpperCase()} API error: ${response.statusText}${errorData.error ? ' - ' + errorData.error.message : ''}`); } const data = await response.json(); @@ -136,9 +227,21 @@ async function postProcessTranscript(text) { * API and Transcription */ async function transcribeAudio(audioBuffer) { - const apiKey = store.get('apiKey'); - if (!apiKey) { - throw new Error('API key not set. Please configure in settings.'); + const apiProvider = store.get('apiProvider', 'groq'); + let apiKey = null; + + if (apiProvider === 'groq') { + apiKey = store.get('apiKey'); + if (!apiKey) { + throw new Error('Groq API key not set. Please configure in settings.'); + } + } else if (apiProvider === 'openai') { + apiKey = store.get('openaiApiKey'); + if (!apiKey) { + throw new Error('OpenAI API key not set. Please configure in settings.'); + } + } else { + throw new Error('Invalid API provider selected.'); } let tempFile = null; @@ -149,10 +252,22 @@ async function transcribeAudio(audioBuffer) { tempFile = path.join(tempDir, `recording-${Date.now()}.wav`); fs.writeFileSync(tempFile, Buffer.from(audioBuffer)); - updateTranscriptionProgress('api', 'Sending to Groq API...'); + // Determine provider name for progress update + let providerName = ''; + if (apiProvider === 'groq') providerName = 'Groq'; + else if (apiProvider === 'openai') providerName = 'OpenAI'; - // Send to Groq API for transcription - const rawTranscript = await sendToGroqAPI(apiKey, tempFile); + updateTranscriptionProgress('api', `Sending to ${providerName} API...`); + + // Send to selected API for transcription + let rawTranscript; + if (apiProvider === 'groq') { + rawTranscript = await sendToGroqAPI(apiKey, tempFile); + } else if (apiProvider === 'openai') { + rawTranscript = await sendToOpenAIAPI(apiKey, tempFile); + } else { + throw new Error('Invalid API provider selected.'); + } // If we get here and rawTranscript is empty, don't proceed if (!rawTranscript?.trim()) { @@ -202,15 +317,98 @@ async function sendToGroqAPI(apiKey, audioFilePath) { // Create form data for API request const formData = new FormData(); formData.append('file', fs.createReadStream(audioFilePath)); - formData.append('model', 'whisper-large-v3'); + formData.append('model', store.get('groqModel', 'whisper-large-v3')); + + // Get the base URL from store + const baseUrl = new URL(store.get('groqBaseUrl', 'https://api.groq.com/openai/v1')); // Send request to Groq API const response = await new Promise((resolve, reject) => { const formHeaders = formData.getHeaders(); const options = { - hostname: 'api.groq.com', - path: '/openai/v1/audio/transcriptions', + hostname: baseUrl.hostname, + path: `${baseUrl.pathname}/audio/transcriptions`.replace('//', '/'), + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + ...formHeaders + } + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + updateTranscriptionProgress('receiving', 'Receiving transcription...'); + }); + + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve({ ok: true, data }); + } else { + console.error('API Error Response:', { + statusCode: res.statusCode, + data: data + }); + resolve({ ok: false, statusCode: res.statusCode, data }); + } + }); + }); + + req.on('error', (error) => { + console.error('Request Error:', error); + reject(error); + }); + + formData.pipe(req); + }); + + if (!response.ok) { + console.error('API Error:', response.data); + throw new Error(`API error: ${response.data}`); + } + + try { + const result = JSON.parse(response.data); + console.log('API Response:', result); + + if (!result || typeof result !== 'object') { + throw new Error('Invalid API response format'); + } + + const transcript = result.text?.trim(); + console.log('Extracted transcript:', transcript); + + // If no transcript or empty transcript, throw error + if (!transcript) { + throw new Error('No speech detected in audio'); + } + + return transcript; + } catch (error) { + console.error('Error processing API response:', error); + throw new Error(`Failed to process API response: ${error.message}`); + } +} + +async function sendToOpenAIAPI(apiKey, audioFilePath) { + // Create form data for API request + const formData = new FormData(); + formData.append('file', fs.createReadStream(audioFilePath)); + formData.append('model', store.get('openaiModel', 'whisper-large-v3')); + + // Get the base URL from store + const baseUrl = new URL(store.get('openaiBaseUrl', 'https://api.openai.com/v1')); + + // Send request to OpenAI API + const response = await new Promise((resolve, reject) => { + const formHeaders = formData.getHeaders(); + + const options = { + hostname: baseUrl.hostname, + path: `${baseUrl.pathname}/audio/transcriptions`.replace('//', '/'), method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, @@ -292,11 +490,12 @@ function createTray() { // Create tray with template image tray = new Tray(trayIcon); - // Check if we're showing in the dock - const showingInDock = !app.dock.isVisible(); + // MACOS SPECIFIC: Check dock visibility + // This is only available on macOS + const showingInDock = isMacOS && app.dock ? !app.dock.isVisible() : false; - // Create context menu - const contextMenu = Menu.buildFromTemplate([ + // Create context menu items + const menuItems = [ { label: 'Start/Stop Recording', click: toggleRecording @@ -313,21 +512,30 @@ function createTray() { } }, { type: 'separator' }, - { + ]; + + // MACOS SPECIFIC: Add dock toggle option + // Only available on macOS + if (isMacOS) { + menuItems.push({ label: 'Show in Dock', type: 'checkbox', checked: showingInDock, click: () => toggleDockVisibility() - }, - { type: 'separator' }, - { - label: 'Quit', - click: () => { - app.isQuitting = true; - app.quit(); - } + }); + menuItems.push({ type: 'separator' }); + } + + // Add quit option + menuItems.push({ + label: 'Quit', + click: () => { + app.isQuitting = true; + app.quit(); } - ]); + }); + + const contextMenu = Menu.buildFromTemplate(menuItems); tray.setToolTip('HotMic'); tray.setContextMenu(contextMenu); @@ -337,9 +545,13 @@ function createTray() { } /** - * Toggle dock visibility + * Toggle dock visibility (MACOS SPECIFIC) + * This function only works on macOS and manages the app's visibility in the dock */ function toggleDockVisibility() { + // Exit early if not on macOS + if (!isMacOS || !app.dock) return; + if (app.dock.isVisible()) { app.dock.hide(); } else { @@ -348,8 +560,11 @@ function toggleDockVisibility() { // Update the tray menu after toggling if (tray) { - const showingInDock = !app.dock.isVisible(); - const contextMenu = Menu.buildFromTemplate([ + // MACOS SPECIFIC: Check dock visibility + const showingInDock = app.dock ? !app.dock.isVisible() : false; + + // Create context menu items + const menuItems = [ { label: 'Start/Stop Recording', click: toggleRecording @@ -366,21 +581,29 @@ function toggleDockVisibility() { } }, { type: 'separator' }, - { + ]; + + // MACOS SPECIFIC: Add dock toggle option + if (isMacOS) { + menuItems.push({ label: 'Show in Dock', type: 'checkbox', checked: showingInDock, click: () => toggleDockVisibility() - }, - { type: 'separator' }, - { - label: 'Quit', - click: () => { - app.isQuitting = true; - app.quit(); - } + }); + menuItems.push({ type: 'separator' }); + } + + // Add quit option + menuItems.push({ + label: 'Quit', + click: () => { + app.isQuitting = true; + app.quit(); } - ]); + }); + + const contextMenu = Menu.buildFromTemplate(menuItems); tray.setContextMenu(contextMenu); } } @@ -410,7 +633,17 @@ function setupIPCHandlers() { } }); - // API key management + // API provider management + ipcMain.handle('set-api-provider', (event, provider) => { + store.set('apiProvider', provider); + return true; + }); + + ipcMain.handle('get-api-provider', () => { + return store.get('apiProvider', 'groq'); + }); + + // Groq API configuration ipcMain.handle('set-api-key', (event, key) => { store.set('apiKey', key); return true; @@ -420,6 +653,52 @@ function setupIPCHandlers() { return store.get('apiKey') || ''; }); + ipcMain.handle('set-groq-base-url', (event, url) => { + store.set('groqBaseUrl', url); + return true; + }); + + ipcMain.handle('get-groq-base-url', () => { + return store.get('groqBaseUrl', 'https://api.groq.com/openai/v1') || ''; + }); + + ipcMain.handle('set-groq-model', (event, model) => { + store.set('groqModel', model); + return true; + }); + + ipcMain.handle('get-groq-model', () => { + return store.get('groqModel', 'whisper-large-v3') || ''; + }); + + // OpenAI API configuration + ipcMain.handle('set-openai-api-key', (event, key) => { + store.set('openaiApiKey', key); + return true; + }); + + ipcMain.handle('get-openai-api-key', () => { + return store.get('openaiApiKey') || ''; + }); + + ipcMain.handle('set-openai-base-url', (event, url) => { + store.set('openaiBaseUrl', url); + return true; + }); + + ipcMain.handle('get-openai-base-url', () => { + return store.get('openaiBaseUrl', 'https://api.openai.com/v1') || ''; + }); + + ipcMain.handle('set-openai-model', (event, model) => { + store.set('openaiModel', model); + return true; + }); + + ipcMain.handle('get-openai-model', () => { + return store.get('openaiModel', 'whisper-large-v3') || ''; + }); + // Shortcut management ipcMain.handle('set-shortcut', (event, shortcut) => { try { @@ -439,13 +718,15 @@ function setupIPCHandlers() { }); ipcMain.handle('get-shortcut', () => { - return store.get('shortcut') || 'Command+Shift+Space'; + // Use platform-specific default shortcuts + const defaultShortcut = isMacOS ? 'Command+Shift+Space' : 'Ctrl+Shift+Space'; + return store.get('shortcut') || defaultShortcut; }); // Prompt settings ipcMain.handle('get-prompt-settings', () => { return store.get('promptSettings', { - enabled: true, + enabled: false, prompt: DEFAULT_PROMPT }); }); @@ -457,8 +738,27 @@ function setupIPCHandlers() { // History management ipcMain.handle('get-history', () => { - cleanupOldHistory(); - return store.get('history', []); + return getHistory(); + }); + + ipcMain.handle('clear-history', () => { + return clearHistory(); + }); + + ipcMain.handle('is-history-encrypted', () => { + return safeStorage.isEncryptionAvailable(); + }); + + ipcMain.handle('get-history-settings', () => { + return { + enabled: store.get('historyEnabled', true), + encrypted: safeStorage.isEncryptionAvailable() + }; + }); + + ipcMain.handle('set-history-enabled', (event, enabled) => { + store.set('historyEnabled', enabled); + return true; }); // Settings window management @@ -510,8 +810,9 @@ async function initialize() { await app.whenReady(); try { - // Hide dock only if not configured to show - if (!store.get('showInDock', false)) { + // MACOS SPECIFIC: Dock visibility management + // Hide dock icon if not configured to show (macOS only) + if (isMacOS && app.dock && !store.get('showInDock', false)) { app.dock.hide(); } @@ -572,16 +873,19 @@ function createMainWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600, + minWidth: 700, + minHeight: 500, webPreferences: { preload: path.join(__dirname, 'preload.mjs'), contextIsolation: true, - nodeIntegration: false, sandbox: false, + nodeIntegration: false }, show: false, skipTaskbar: false, title: 'HotMic', - titleBarStyle: 'hiddenInset', + // MACOS SPECIFIC: titleBarStyle is only used on macOS + titleBarStyle: isMacOS ? 'hiddenInset' : 'default', backgroundColor: '#00000000' }); @@ -589,14 +893,18 @@ function createMainWindow() { // Show in App Switcher when window is shown mainWindow.on('show', () => { - // Show in dock temporarily while window is open - app.dock.show(); + // MACOS SPECIFIC: Show in dock when window is shown + // On macOS, we show the app in the dock when the settings window is open + if (isMacOS && app.dock && store.get('showInDock', false)) { + app.dock.show(); + } }); // Remove from App Switcher when window is hidden mainWindow.on('hide', () => { - // Hide dock if it's not meant to be visible - if (!store.get('showInDock', false)) { + // MACOS SPECIFIC: Hide dock when window is hidden + // On macOS, we hide the dock icon when the window is hidden (unless configured to show) + if (isMacOS && app.dock && !store.get('showInDock', false)) { app.dock.hide(); } }); @@ -611,9 +919,22 @@ function createMainWindow() { }); mainWindow.once('ready-to-show', () => { - // Only show on first launch or if API key isn't set - if (!store.get('apiKey')) { + // Only show on first launch or if no API key is set + const apiProvider = store.get('apiProvider', 'groq'); + let hasApiKey = false; + + if (apiProvider === 'groq') { + hasApiKey = !!store.get('apiKey'); + } else if (apiProvider === 'openai') { + hasApiKey = !!store.get('openaiApiKey'); + } + + if (!hasApiKey) { mainWindow.show(); + // Show API Providers tab first if no API key + mainWindow.webContents.executeJavaScript(` + document.querySelector('[data-tab="api-providers"]').click(); + `); } }); @@ -636,6 +957,7 @@ function createOverlayWindow() { x: Math.floor(width / 2 - 150), y: Math.floor(height / 2 - 150), frame: false, + // Note: transparent works on both platforms but has better results on macOS transparent: true, backgroundColor: '#00000000', opacity: 1.0, @@ -644,9 +966,8 @@ function createOverlayWindow() { skipTaskbar: true, alwaysOnTop: true, show: false, - frame: false, - vibrancy: null, - visualEffectState: 'active', + vibrancy: isMacOS ? null : undefined, + visualEffectState: isMacOS ? 'active' : undefined, webPreferences: { preload: path.join(__dirname, 'preload.mjs'), contextIsolation: true, diff --git a/src/preload.mjs b/src/preload.mjs index d7bc3dd..435a916 100644 --- a/src/preload.mjs +++ b/src/preload.mjs @@ -13,8 +13,25 @@ import { contextBridge, ipcRenderer } from 'electron'; */ contextBridge.exposeInMainWorld('api', { // Settings management + getApiProvider: () => ipcRenderer.invoke('get-api-provider'), + setApiProvider: (provider) => ipcRenderer.invoke('set-api-provider', provider), + + // Groq API getApiKey: () => ipcRenderer.invoke('get-api-key'), setApiKey: (key) => ipcRenderer.invoke('set-api-key', key), + getGroqBaseUrl: () => ipcRenderer.invoke('get-groq-base-url'), + setGroqBaseUrl: (url) => ipcRenderer.invoke('set-groq-base-url', url), + getGroqModel: () => ipcRenderer.invoke('get-groq-model'), + setGroqModel: (model) => ipcRenderer.invoke('set-groq-model', model), + + // OpenAI API + getOpenaiApiKey: () => ipcRenderer.invoke('get-openai-api-key'), + setOpenaiApiKey: (key) => ipcRenderer.invoke('set-openai-api-key', key), + getOpenaiBaseUrl: () => ipcRenderer.invoke('get-openai-base-url'), + setOpenaiBaseUrl: (url) => ipcRenderer.invoke('set-openai-base-url', url), + getOpenaiModel: () => ipcRenderer.invoke('get-openai-model'), + setOpenaiModel: (model) => ipcRenderer.invoke('set-openai-model', model), + getShortcut: () => ipcRenderer.invoke('get-shortcut'), setShortcut: (shortcut) => ipcRenderer.invoke('set-shortcut', shortcut), getPromptSettings: () => ipcRenderer.invoke('get-prompt-settings'), @@ -22,6 +39,12 @@ contextBridge.exposeInMainWorld('api', { getHistory: () => ipcRenderer.invoke('get-history'), openSettings: () => ipcRenderer.invoke('open-settings'), + // History management + clearHistory: () => ipcRenderer.invoke('clear-history'), + isHistoryEncrypted: () => ipcRenderer.invoke('is-history-encrypted'), + getHistorySettings: () => ipcRenderer.invoke('get-history-settings'), + setHistoryEnabled: (enabled) => ipcRenderer.invoke('set-history-enabled', enabled), + // Recording functionality sendAudioData: (buffer) => ipcRenderer.invoke('audio-data', buffer), sendAudioLevel: (level) => ipcRenderer.invoke('audio-level', level),