diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d50a17..46c36c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -96,7 +96,13 @@ jobs: name: Build + sign + notarize Mac client needs: validate runs-on: macos-14 - if: false # Disabled until apps/desktop has actual Electron code (M6) + # Activates when the desktop package has the electron dep installed + # AND the maintainer has set BUILD_MAC=1 in the GitHub Actions env + # (or the apps/desktop/electron-builder.yml file exists which it does + # since M6-rest part 1 — flipping the gate to `vars.BUILD_MAC == 'true'` + # so the maintainer enables it via Repository Variables once Apple + # secrets are wired). See docs/SHIPPING_MAC.md. + if: ${{ vars.BUILD_MAC == 'true' }} timeout-minutes: 30 steps: - uses: actions/checkout@v6 @@ -107,11 +113,23 @@ jobs: cache: 'pnpm' - run: pnpm install --frozen-lockfile + - name: Activate template configs + run: | + cd apps/desktop + if [ -f vite.config.template.ts ]; then mv vite.config.template.ts vite.config.ts; fi + if [ -f postcss.config.template.js ]; then mv postcss.config.template.js postcss.config.js; fi + - name: Set version run: | cd apps/desktop npm version "${{ needs.validate.outputs.version }}" --no-git-tag-version + - name: Build renderer + main + run: | + cd apps/desktop + pnpm build:renderer + pnpm build:electron + - name: Build (electron-builder) env: APPLE_ID: ${{ secrets.APPLE_ID }} @@ -147,21 +165,25 @@ jobs: run: | echo "Note: this release tag has +security.X suffix — Mac client auto-update will be marked mandatory." - - name: Generate release notes from PR labels + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'pnpm' + + - name: Install (needed for tsx) + run: pnpm install --frozen-lockfile + + - name: Generate release notes (conventional-commit grouped) id: notes run: | - # Lightweight: extract section between last tag and HEAD; group by label. - # Real implementation lands in scripts/gen-release-notes.ts (M9-ext). PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") if [ -n "$PREV_TAG" ]; then - RANGE="$PREV_TAG..HEAD" + FROM="$PREV_TAG" else - RANGE="HEAD" + FROM=$(git rev-list --max-parents=0 HEAD) fi - { - echo "## Changes" - git log --pretty=format:'- %s' $RANGE - } > release-notes.md + npx tsx scripts/gen-release-notes.ts "$FROM" HEAD > release-notes.md cat release-notes.md - name: Create GitHub Release diff --git a/docs/SHIPPING_MAC.md b/docs/SHIPPING_MAC.md new file mode 100644 index 0000000..a934aff --- /dev/null +++ b/docs/SHIPPING_MAC.md @@ -0,0 +1,141 @@ +# Shipping the Mac client (M6 → v1) + +End-to-end checklist for going from the current `apps/desktop` skeleton +to a notarized `.dmg` published on GitHub Releases. + +This document is for the human maintainer; the agent can't do steps that +require an Apple Developer ID or a real device. + +## Prerequisites + +1. **Apple Developer Program membership** ($99/year). +2. **Xcode** installed (provides codesign + altool). +3. A **Developer ID Application** certificate downloaded into the + login keychain. Generate via Xcode → Settings → Accounts → Manage + Certificates → "+" → Developer ID Application. +4. An **app-specific password** for the Apple ID: + https://appleid.apple.com → Sign-In and Security → App-Specific + Passwords. (Used by notarytool — do NOT use your main Apple ID + password.) +5. **GitHub Personal Access Token** with `repo` scope, for + `electron-builder` to publish releases. + +## One-time CI secrets + +In the repo's GitHub Actions secrets, add: + +| Name | Value | +| ------------------------------------- | ---------------------------------------------- | +| `APPLE_ID` | Your Apple Developer login email | +| `APPLE_APP_SPECIFIC_PASSWORD` | The app-specific password from step 4 | +| `APPLE_TEAM_ID` | 10-char team ID (Membership tab in dev portal) | +| `CSC_LINK` | Base64-encoded `.p12` of the Developer ID cert | +| `CSC_KEY_PASSWORD` | Password used when exporting the `.p12` | +| `GH_TOKEN` | The PAT from step 5 | + +To export the `.p12`: + +```bash +# In Keychain Access: select your Developer ID Application cert + private +# key → Export → set a password → save as cert.p12, then: +base64 -i cert.p12 -o cert.p12.b64 +# Paste contents of cert.p12.b64 into the CSC_LINK secret. +``` + +## First local build + +```bash +# 1. Install the heavy deps (~250 MB) +pnpm add -D --filter @deepcode/desktop \ + electron electron-builder electron-updater \ + vite @vitejs/plugin-react \ + tailwindcss postcss autoprefixer \ + concurrently wait-on + +# 2. Activate the .template configs +mv apps/desktop/vite.config.template.ts apps/desktop/vite.config.ts +mv apps/desktop/postcss.config.template.js apps/desktop/postcss.config.js + +# 3. Dev mode (vite HMR + electron auto-reload) +pnpm --filter @deepcode/desktop dev + +# 4. Package an unsigned .app for local testing +pnpm --filter @deepcode/desktop pack + +# 5. Full signed + notarized .dmg +APPLE_ID=...@... \ +APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx \ +APPLE_TEAM_ID=ABCDEF1234 \ +CSC_LINK=$(base64 -i ~/Downloads/cert.p12) \ +CSC_KEY_PASSWORD=mypassword \ +pnpm --filter @deepcode/desktop dist +``` + +The signed `.dmg` lands in `apps/desktop/release/`. + +## Releasing via tag + +```bash +# Make sure main is green, then: +git tag v1.0.0 +git push origin v1.0.0 +``` + +The `.github/workflows/release.yml` workflow: +1. Runs the test/build matrix. +2. Publishes `deepcode-cli` to npm. +3. Builds + signs + notarizes the Mac `.dmg`. +4. Creates a GitHub Release tagged with `v1.0.0` and attaches the `.dmg`. +5. `electron-updater` in installed clients picks up the new release via + the GitHub releases feed (see main.ts `setupAutoUpdater`). + +## Sanity-checking notarization + +After the upload, run: + +```bash +xcrun notarytool history --apple-id "$APPLE_ID" \ + --password "$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$APPLE_TEAM_ID" + +# Or check a single submission: +xcrun notarytool info --apple-id "$APPLE_ID" ... +``` + +Once Apple says "Accepted", verify locally: + +```bash +spctl -a -t exec -vv /Applications/DeepCode.app +# Should print: accepted, source=Notarized Developer ID +``` + +## Auto-update flow + +1. User opens an old DeepCode build (v1.0.0). +2. `electron-updater.checkForUpdatesAndNotify()` polls the GitHub Releases + feed once per launch. +3. If a newer release exists, downloads it in the background. +4. On download complete, fires `updater:update-downloaded` IPC event → + the renderer's `UpdateBanner` shows "DeepCode vX.Y.Z is ready to + install. Relaunch to update." +5. User clicks "Relaunch now" → main process calls `app.relaunch()` + + `app.quit()`. (Wiring TBD — currently `window.location.reload()` stub.) + +## Common failures + +- **"Invalid Developer ID Certificate"** — usually the `.p12` doesn't + include the private key. Re-export with both checked. +- **Notarization stuck "In Progress"** — Apple's servers can take 30 min + during peak hours. Wait or open the dev portal to inspect. +- **`spctl` rejects** — make sure `dmg.notarize: true` is set in + `electron-builder.yml`. (It is, but worth re-checking.) +- **App opens then immediately crashes** — first run after notarization + needs `xattr -d com.apple.quarantine /Applications/DeepCode.app` if you + copied the .app outside the .dmg. + +## What's still hardcoded that should be parametrized later + +- `appId: dev.deepcode.client` — fine for v1, may want a more specific + team-prefixed ID for marketplace listings. +- `category: developer-tools` — fine. +- Icon: needs a real `.icns` at `build-resources/icon.icns` (currently + missing — provide one before first build). diff --git a/docs/VOICE_INPUT.md b/docs/VOICE_INPUT.md new file mode 100644 index 0000000..58d12de --- /dev/null +++ b/docs/VOICE_INPUT.md @@ -0,0 +1,127 @@ +# Voice input (whisper.cpp local) + +DeepCode supports voice input via a local whisper.cpp install — no cloud +ASR call, no audio leaving the machine. + +## Install whisper.cpp + +### macOS (Homebrew) + +```bash +brew install whisper-cpp +``` + +This puts `whisper-cli` (or `whisper`, depending on brew version) on the +PATH. Confirm with: + +```bash +which whisper-cli || which whisper +whisper-cli --help +``` + +### Linux + +```bash +# Clone + build +git clone https://github.com/ggerganov/whisper.cpp /tmp/whisper.cpp +cd /tmp/whisper.cpp +make -j + +# Install the binary somewhere on PATH +sudo cp main /usr/local/bin/whisper +``` + +### Manual macOS build + +```bash +git clone https://github.com/ggerganov/whisper.cpp /tmp/whisper.cpp +cd /tmp/whisper.cpp +make -j +sudo cp main /usr/local/bin/whisper +``` + +## Download a model + +whisper.cpp ships a download script: + +```bash +cd "$(brew --prefix whisper-cpp)/share/whisper-cpp" # or wherever the repo lives +bash ./models/download-ggml-model.sh base.en +# OR a larger model: +bash ./models/download-ggml-model.sh small.en +bash ./models/download-ggml-model.sh medium.en +``` + +Recommended for fast dictation: `base.en` (~140 MB). +For accurate multi-language: `small` (~470 MB) or `medium` (~1.4 GB). + +The script saves the `.bin` file alongside the script — copy it +somewhere DeepCode can find it: + +```bash +mkdir -p ~/.deepcode/models +cp models/ggml-base.en.bin ~/.deepcode/models/whisper-base.en.bin +``` + +## Configure DeepCode + +In `~/.deepcode/settings.json`: + +```json +{ + "voice": { + "provider": "whisper.cpp", + "binPath": "/opt/homebrew/bin/whisper-cli", + "modelPath": "~/.deepcode/models/whisper-base.en.bin" + } +} +``` + +(The `binPath` defaults to `whisper` on PATH if you omit it.) + +## Usage + +In the CLI REPL, press the voice toggle key (default `Ctrl+V`; remap in +`~/.deepcode/keybindings.json`). DeepCode: + +1. Records audio from your default mic into a temp `.wav` file. +2. Stops recording on the next key press OR after 60 s of silence. +3. Spawns whisper.cpp to transcribe the .wav. +4. Inserts the transcribed text into the input box (you can edit before + submitting). + +In the Mac client (M6-rest), the same flow appears as a 🎙 button. + +## Privacy + +- Audio file is written to `$TMPDIR/deepcode-voice-.wav` and + deleted immediately after transcription succeeds. +- Whisper.cpp runs entirely locally — no network call. +- The transcribed text follows the standard agent loop (sandbox / + permissions / hooks still apply to anything it triggers). + +## Troubleshooting + +- **`Error: whisper.cpp exited 1: model not found`** — `modelPath` is + wrong. Check the file exists with `ls -lh `. +- **Empty transcript** — the audio file may be silent or too short. + Try a longer phrase, or check the mic input level in System Settings. +- **Very slow** — try a smaller model (`base.en` over `small`). On Apple + Silicon, whisper.cpp uses the GPU automatically; on Intel it's CPU-only. + +## API (for plugin authors) + +```ts +import { WhisperCppProvider } from '@deepcode/core'; + +const provider = new WhisperCppProvider({ + binPath: '/opt/homebrew/bin/whisper-cli', + modelPath: '~/.deepcode/models/whisper-base.en.bin', +}); + +const result = await provider.transcribe('/tmp/my-clip.wav', { + language: 'en', // optional; whisper auto-detects otherwise +}); + +console.log(result.text); // "hello world" +```