Skip to content
Open
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
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: [master]
pull_request:

permissions:
contents: read

jobs:
build:
name: Build (${{ matrix.architecture }})
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
architecture: arm64
- runner: macos-15-intel
architecture: x86_64
runs-on: ${{ matrix.runner }}

steps:
- uses: actions/checkout@v4

- name: Build release binary
run: swift build --configuration release --arch "${{ matrix.architecture }}"

- name: Verify version command and architecture
shell: bash
run: |
set -euo pipefail
binary="$(swift build --configuration release --arch '${{ matrix.architecture }}' --show-bin-path)/quill"
test "$("$binary" --version)" = "$(sed -n 's/.*static let current = "\([^"]*\)".*/\1/p' Sources/quill/Version.swift)"
lipo -archs "$binary" | tr ' ' '\n' | grep -qx '${{ matrix.architecture }}'
105 changes: 105 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: write

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
build:
name: Package (${{ matrix.architecture }})
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
architecture: arm64
- runner: macos-15-intel
architecture: x86_64
runs-on: ${{ matrix.runner }}

steps:
- uses: actions/checkout@v4

- name: Package release binary
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
./scripts/package-release.sh "$version" "${{ matrix.architecture }}"

- uses: actions/upload-artifact@v4
with:
name: quill-macos-${{ matrix.architecture }}
path: dist/quill-macos-${{ matrix.architecture }}.tar.gz
if-no-files-found: error

publish:
name: Publish GitHub release and Homebrew formula
needs: build
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}

steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 0

- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true

- name: Create checksums
working-directory: dist
run: sha256sum quill-macos-*.tar.gz > SHA256SUMS

- name: Create or update GitHub release
shell: bash
run: |
set -euo pipefail
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" dist/quill-macos-*.tar.gz dist/SHA256SUMS \
--repo "$GITHUB_REPOSITORY" --clobber
else
gh release create "$GITHUB_REF_NAME" dist/quill-macos-*.tar.gz dist/SHA256SUMS \
--repo "$GITHUB_REPOSITORY" \
--verify-tag \
--generate-notes \
--title "quill ${GITHUB_REF_NAME#v}"
fi

- name: Update Homebrew formula
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
manifest_sha256="$(sha256sum dist/SHA256SUMS | cut -d' ' -f1)"
arm64_sha256="$(sha256sum dist/quill-macos-arm64.tar.gz | cut -d' ' -f1)"
x86_64_sha256="$(sha256sum dist/quill-macos-x86_64.tar.gz | cut -d' ' -f1)"
./scripts/render-homebrew-formula.sh \
"$version" "$GITHUB_REPOSITORY" "$manifest_sha256" \
"$arm64_sha256" "$x86_64_sha256"

- name: Commit Homebrew formula
shell: bash
run: |
set -euo pipefail
git add Formula/quill.rb
if git diff --cached --quiet; then
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "chore(homebrew): update quill to ${GITHUB_REF_NAME#v}"
git pull --rebase origin "${{ github.event.repository.default_branch }}"
git push origin "HEAD:${{ github.event.repository.default_branch }}"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.build/
.swiftpm/
dist/
.DS_Store
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Changelog

All notable user-visible changes are recorded here. The project follows
[Semantic Versioning](https://semver.org/).

## [0.1.0] - 2026-08-01

Initial public release.

### Added

- Separate microphone and system-audio recording on macOS 15 and later.
- Automatic, local Parakeet transcription with `me` and `them` speaker tags.
- Crash-resistant CAF sessions and filesystem-backed transcription recovery.
- Menu-bar controls, diagnostics, JSON configuration, and post-session hooks.
- Native Apple Silicon and Intel release archives with SHA-256 checksums.
- Homebrew installation through the repository's custom tap.
- `quill --version`, dual-architecture CI, and contributor documentation.

[0.1.0]: https://github.com/digimata/quill/releases/tag/v0.1.0
124 changes: 124 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Contributing to quill

Thanks for helping improve quill. Small, focused pull requests are easiest to
review, especially around audio capture and permissions where macOS behavior
can be subtle.

## Before you start

- Search existing issues before opening a new one.
- For a substantial feature or behavior change, open an issue first so the
approach can be agreed before a large patch is written.
- Never upload a real meeting recording, transcript, config file containing
secrets, or another person's voice. Create a short synthetic audio sample
when a reproduction needs audio.

## Development setup

You need:

- macOS 15 Sequoia or later
- Xcode 16 or later, or matching Command Line Tools
- Swift 6
- A microphone for capture testing; headphones are useful when testing the
separate mic and system tracks

Fork the repository, then clone your fork:

```sh
git clone https://github.com/YOUR-NAME/quill.git
cd quill
git remote add upstream https://github.com/digimata/quill.git
swift package resolve
swift build
```

Check the CLI without starting the menu-bar daemon:

```sh
.build/debug/quill --version
.build/debug/quill doctor
```

Running `.build/debug/quill` starts the daemon. macOS will request microphone
and System Audio Recording permissions on first use. Development and release
binaries may be treated as distinct permission identities after a rebuild, so
recheck System Settings → Privacy & Security when capture unexpectedly becomes
silent.

## Project layout

| Path | Purpose |
|---|---|
| `Sources/quill/Audio` | Microphone and Core Audio process-tap capture |
| `Sources/quill/Transcription` | On-device transcription and job coordination |
| `Sources/quill/UI` | Menu-bar UI |
| `Sources/quill/RecordingSession.swift` | Session lifecycle and on-disk metadata |
| `Sources/quill/Config.swift` | User config parsing and path resolution |
| `scripts` | Release packaging and Homebrew formula generation |
| `.github/workflows` | Intel/Apple Silicon CI and tagged releases |

The filesystem is also the transcription queue: a session with `meta.json`
and no `transcript.json` is pending. Preserve that recovery behavior when
changing session or transcription code.

## Making a change

1. Create a branch from the latest `master`.
2. Keep the change focused and explain behavior changes in the commit or PR.
3. Add or update documentation for user-visible CLI, config, file-format, or
permission changes.
4. Build the production configuration before opening a PR:

```sh
swift build --configuration release
"$(swift build --configuration release --show-bin-path)/quill" --version
```

5. For audio changes, test start, stop, and process termination. Confirm the
two CAF files remain readable and that a later recording can still start.

CI performs a release build on native Apple Silicon and Intel macOS runners.
The project does not yet have a complete automated audio test suite, so include
the exact manual scenarios you ran in the PR description.

## Pull requests

A useful PR description answers:

- What user problem does this solve?
- What changed, and what intentionally did not change?
- How was it tested, including Mac architecture and macOS version?
- Does it change permissions, config, metadata, transcript output, or recovery
after a crash?

Do not include generated `.build` content, downloaded transcription models, or
recordings in commits.

## Maintainer release process

quill uses semantic versions and starts at `0.1.0` while interfaces are still
settling.

1. Update `QuillVersion.current` in `Sources/quill/Version.swift` and
`CFBundleShortVersionString` in `Sources/quill/Info.plist` to the same value.
2. Move the release notes in `CHANGELOG.md` under the new version and date.
3. Run a release build and package the current Mac architecture:

```sh
swift build --configuration release
./scripts/package-release.sh 0.1.0 "$(uname -m)"
```

4. Commit the version bump, create an annotated tag, and push it:

```sh
git tag -a v0.1.0 -m "quill 0.1.0"
git push origin master v0.1.0
```

The release workflow verifies that the tag, Swift version, and Info.plist
version agree. It builds `arm64` and `x86_64` archives, creates a GitHub release
with checksums, renders `Formula/quill.rb`, and commits the updated formula to
the default branch. Repository Actions must have permission to write contents;
branch protection must allow the release workflow's formula update.
74 changes: 68 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,69 @@
# quill

[![CI](https://github.com/digimata/quill/actions/workflows/ci.yml/badge.svg)](https://github.com/digimata/quill/actions/workflows/ci.yml)
[![GitHub release](https://img.shields.io/github/v/release/digimata/quill)](https://github.com/digimata/quill/releases/latest)

A minimal, fully local macOS meeting recorder + transcriber. One menu-bar
click records your mic and all system audio as two separate tracks; when you
stop, quill transcribes both on-device and writes a speaker-tagged transcript.
Nothing ever leaves the machine.

Named for the feather. Sibling of [parrot](https://github.com/digimata/parrot), same skeleton: single
Swift binary, menu-bar tray, no app bundle.
Named for the feather. Sibling of [parrot](https://github.com/digimata/parrot),
same skeleton: single Swift binary, menu-bar tray, no app bundle.

## Install

### Homebrew (recommended)

```sh
brew tap digimata/quill https://github.com/digimata/quill
brew install digimata/quill/quill
quill doctor
```

The custom tap lives in this repository. Each version tag publishes native
Apple Silicon and Intel binaries, then updates the formula with their verified
SHA-256 checksums.

Start quill from a terminal:

```sh
quill
```

Or register its per-user LaunchAgent so it starts when you sign in:

```sh
quill install --launch-at-login
```

### Build from source

You need macOS 15 or later, Xcode 16 or later (or matching Command Line Tools),
and Swift 6:

```sh
git clone https://github.com/digimata/quill.git
cd quill
swift build -c release
sudo cp .build/release/quill /usr/local/bin/quill
quill install --launch-at-login # optional — runs in the background on login
quill doctor
```

**Requires:** macOS 15+ (Core Audio process taps for system audio — no
virtual device, no kernel extension). Apple Silicon recommended for
transcription speed.
If `/usr/local/bin` is not on your `PATH`, install the binary into another
directory that is. Apple Silicon is recommended for transcription speed.

### Uninstall

Remove the login item before removing the binary:

```sh
quill install --uninstall
brew uninstall quill
```

Recordings and config are deliberately left in place. Delete
`~/Recordings` and `~/.config/quill` yourself only if you no longer need them.

## How to use

Expand Down Expand Up @@ -114,6 +158,20 @@ quill install --uninstall
- **FluidAudio / Parakeet** — on-device Core ML transcription
- **NSStatusItem** — the whole UI

## Contributing

Issues and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md)
for the development setup, project layout, validation checklist, and release
process. Please never attach a real private meeting recording to an issue;
use a short synthetic sample when audio is needed to reproduce a bug.

## Releases

quill follows semantic versioning while the CLI and transcript formats settle.
A tag such as `v0.1.0` triggers builds on native Apple Silicon and Intel GitHub
runners, publishes both archives plus `SHA256SUMS`, and refreshes the Homebrew
formula. See [CHANGELOG.md](CHANGELOG.md) for user-visible changes.

## Gotchas

- A global tap records *everything* the Mac plays — notification dings,
Expand All @@ -125,3 +183,7 @@ quill install --uninstall
engine.
- The binary embeds its Info.plist (`__TEXT,__info_plist`) so TCC can
attribute permissions to quill itself when running as a LaunchAgent.

## License

[MIT](LICENSE)
Loading