diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 98c62bb5..69afee1d 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -103,6 +103,36 @@ jobs: working-directory: app run: npm ci + - name: Inject build-time secrets + # Writes app/build-config.js (gitignored) from GitHub Actions + # secrets so electron-builder packages the credentials into the + # asar at build time. Local dev fills its own build-config.js + # from build-config.example.js. + # + # Fail loudly if either secret is missing — without this guard, a + # release built before the secrets were added would silently ship + # with empty OAuth credentials and every user's calendar would + # fail to authenticate. + working-directory: app + env: + GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.GOOGLE_OAUTH_CLIENT_ID }} + GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }} + run: | + if [ -z "${GOOGLE_OAUTH_CLIENT_ID}" ] || [ -z "${GOOGLE_OAUTH_CLIENT_SECRET}" ]; then + echo "::error::GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET must be set in repo Settings → Secrets and variables → Actions before releasing." >&2 + exit 1 + fi + # Generate via node so JSON.stringify handles all escaping (quotes, + # backslashes, unicode) — safer than shell string substitution. + node -e ' + const fs = require("fs"); + const cfg = { + GOOGLE_OAUTH_CLIENT_ID: process.env.GOOGLE_OAUTH_CLIENT_ID, + GOOGLE_OAUTH_CLIENT_SECRET: process.env.GOOGLE_OAUTH_CLIENT_SECRET, + }; + fs.writeFileSync("build-config.js", "module.exports = " + JSON.stringify(cfg, null, 2) + ";\n"); + ' + - name: Import code signing certificate uses: apple-actions/import-codesign-certs@v2 with: diff --git a/.gitignore b/.gitignore index 337f41ab..91d3bf23 100644 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,9 @@ celerybeat.pid # Environments .env .venv +# Build-time OAuth credentials — written by CI from GitHub Actions secrets, +# or by contributors locally from build-config.example.js. Never commit. +app/build-config.js env/ venv/ ENV/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec5a7682..4e82a357 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,27 @@ Thank you for your interest in contributing to StenoAI! This guide will help you npm start ``` -5. **Test the setup** +5. **(Optional) Configure Google Calendar OAuth for local dev** + + Calendar integration reads its OAuth credentials from + `app/build-config.js` at startup. CI builds write this file from + GitHub Actions secrets; local builds need your own. + + ```bash + cd app + cp build-config.example.js build-config.js + ``` + + Then fill in `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET` + from your own Google Cloud project (Create an OAuth 2.0 Client ID of + type **Desktop application** at + https://console.cloud.google.com → APIs & Services → Credentials). + + If you skip this step the app launches normally — calendar + integration will surface "calendar not connected" and the rest of + the app (recording, transcription, summarization) works as usual. + +6. **Test the setup** ```bash # Test CLI python simple_recorder.py --help diff --git a/app/build-config.example.js b/app/build-config.example.js new file mode 100644 index 00000000..8a6bc15a --- /dev/null +++ b/app/build-config.example.js @@ -0,0 +1,22 @@ +// Build-time configuration, read by app/main.js at startup. +// +// Copy this file to `build-config.js` and fill in your own values for +// local development. `build-config.js` is gitignored — never commit it. +// +// CI builds write this file directly from GitHub Actions secrets in +// .github/workflows/build-release.yml; this template is only for local +// dev runs (`npm start` / `npm run start:nobuild`). +// +// If you leave these blank, calendar integration is disabled but the +// rest of the app (recording, transcription, summarization) works +// normally. + +module.exports = { + // Google Calendar OAuth credentials. Get your own from + // https://console.cloud.google.com → APIs & Services → Credentials. + // Create an OAuth 2.0 Client ID of type "Desktop application". + // Google treats Desktop-client secrets as public-by-policy; the + // production value is held in the repo's GitHub Actions secrets. + GOOGLE_OAUTH_CLIENT_ID: '', + GOOGLE_OAUTH_CLIENT_SECRET: '', +}; diff --git a/app/main.js b/app/main.js index b8bc2581..af7915f6 100644 --- a/app/main.js +++ b/app/main.js @@ -15,6 +15,23 @@ const { PostHog } = require('posthog-node'); const { initMain } = require('electron-audio-loopback'); const { autoUpdater } = require('electron-updater'); +// Build-time configuration. `build-config.js` is gitignored and written +// by CI from GitHub Actions secrets before electron-builder runs (see +// .github/workflows/build-release.yml). For local dev, copy +// `build-config.example.js` to `build-config.js` and fill in your own +// Google OAuth credentials — calendar integration is "not connected" +// without it but the rest of the app works. +// +// Plain require + try/catch (rather than dotenv) so we don't ship a +// runtime dependency just to read two constants. +let buildConfig = {}; +try { + buildConfig = require('./build-config'); +} catch (_) { + // Missing file in local dev → calendar surfaces as "not connected" + // rather than crashing. +} + // E2E test-harness hooks. Set via env vars; production sees none of these. // STENOAI_USER_DATA_DIR — per-test temp userData dir (must be set before app.whenReady) // STENOAI_E2E=1 — skip tray, auto-updater, PostHog telemetry @@ -356,9 +373,16 @@ let anonymousId = null; const POSTHOG_API_KEY = 'phc_U2cnTyIyKGNSVaK18FyBMltd8nmN7uHxhhm21fAHwqb'; const POSTHOG_HOST = 'https://us.i.posthog.com'; -// Google Calendar OAuth2 configuration -const GOOGLE_CLIENT_ID = '281073275073-20da4u5t9luk2366vd5ai0a2r55d5pf5.apps.googleusercontent.com'; -const GOOGLE_CLIENT_SECRET = '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'; +// Google Calendar OAuth2 configuration. +// +// Both values come from `build-config.js` (see top of file). Empty +// strings in local dev with no `build-config.js` mean Google auth is +// disabled — the renderer surfaces this as "calendar not connected" +// rather than a crash. Rotate the secret in Google Cloud Console + +// update the `GOOGLE_OAUTH_CLIENT_*` GitHub Actions secrets to roll a +// new credential without a code change. +const GOOGLE_CLIENT_ID = buildConfig.GOOGLE_OAUTH_CLIENT_ID || ''; +const GOOGLE_CLIENT_SECRET = buildConfig.GOOGLE_OAUTH_CLIENT_SECRET || ''; const GOOGLE_SCOPES = 'https://www.googleapis.com/auth/calendar.readonly'; const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'; const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token'; diff --git a/app/package-lock.json b/app/package-lock.json index 87bfe804..4df23512 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "stenoai", - "version": "0.2.13", + "version": "0.3.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "stenoai", - "version": "0.2.13", + "version": "0.3.8", "license": "MIT", "dependencies": { "@radix-ui/react-dialog": "^1.1.15", @@ -102,7 +102,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -636,6 +635,7 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, + "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -657,6 +657,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2993,7 +2994,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3004,7 +3004,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3078,7 +3077,6 @@ "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/types": "8.59.2", @@ -3337,7 +3335,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3371,7 +3368,6 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4199,7 +4195,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -4746,7 +4741,8 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -5023,8 +5019,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/didyoumean": { "version": "1.2.2", @@ -5088,7 +5083,6 @@ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", @@ -5204,7 +5198,6 @@ "resolved": "https://registry.npmjs.org/electron/-/electron-42.0.0.tgz", "integrity": "sha512-in5jnW/Ywy3Rh3FPr4MR80exPEkywKYAmDJRZ/gIKlr8VXEi3zXgiAZbf0Si7KRccHTF2y8euVMRz7M6HqTjMA==", "license": "MIT", - "peer": true, "dependencies": { "@electron/get": "^5.0.0", "@types/node": "^24.9.0", @@ -5313,6 +5306,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -5333,6 +5327,7 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -5348,6 +5343,7 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", + "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -5358,6 +5354,7 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4.0.0" } @@ -5683,7 +5680,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8612,6 +8608,7 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -9302,7 +9299,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9487,6 +9483,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -9504,6 +9501,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -9715,7 +9713,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9725,7 +9722,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10107,6 +10103,7 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -11052,6 +11049,7 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -11171,7 +11169,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11386,7 +11383,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11675,7 +11671,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -11998,7 +11993,6 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }