Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }}
Expand Down Expand Up @@ -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
Expand Down
141 changes: 141 additions & 0 deletions docs/SHIPPING_MAC.md
Original file line number Diff line number Diff line change
@@ -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 <submission-id> --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).
127 changes: 127 additions & 0 deletions docs/VOICE_INPUT.md
Original file line number Diff line number Diff line change
@@ -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-<random>.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 <path>`.
- **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"
```
Loading