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
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
name: freeflow-release
description: Prepare and publish FreeFlow app releases. Use when the user asks to release FreeFlow, prepare a new version, bump the FreeFlow version, write or update FreeFlow changelog entries, validate a FreeFlow semver release, create a vX.Y.Z tag, or publish a signed FreeFlow DMG through the repository GitHub Actions release workflow.
name: fluent-release
description: Prepare and publish Fluent app releases. Use when the user asks to release Fluent, prepare a new version, bump the Fluent version, write or update Fluent changelog entries, validate a Fluent semver release, create a vX.Y.Z tag, or publish a signed Fluent DMG through the repository GitHub Actions release workflow.
---

# FreeFlow Release
# Fluent Release

## Overview

Use this skill to prepare a FreeFlow release from the local repository. FreeFlow releases are semver-tag driven: pushing a tag like `v0.3.1` triggers `.github/workflows/release.yml`, which stamps the app bundle, extracts the matching `CHANGELOG.md` section, builds/signs/notarizes the DMG, and creates the GitHub Release.
Use this skill to prepare a Fluent release from the local repository. Fluent releases are semver-tag driven: pushing a tag like `v0.3.1` triggers `.github/workflows/release.yml`, which stamps the app bundle, extracts the matching `CHANGELOG.md` section, builds/signs/notarizes the DMG, and creates the GitHub Release.

Every release prep must update `CHANGELOG.md` by comparing the previous public semver release with the current release target. The changelog section should describe the user-visible changes that shipped since the previous release, not just summarize the final release-prep commit.

Expand Down Expand Up @@ -68,7 +68,7 @@ Every release prep must update `CHANGELOG.md` by comparing the previous public s
5. Validate locally before commit/tag:
```bash
.github/scripts/changelog-section.sh <version>
.agents/skills/freeflow-release/scripts/freeflow-release-check.sh <version>
.agents/skills/fluent-release/scripts/fluent-release-check.sh <version>
git diff --check
make clean
make ARCH="$(uname -m)" CODESIGN_IDENTITY=-
Expand All @@ -90,10 +90,10 @@ Every release prep must update `CHANGELOG.md` by comparing the previous public s

8. After GitHub Actions finishes, verify:
- GitHub Release `v<version>` exists and is marked latest.
- `FreeFlow.dmg` is attached.
- `Fluent.dmg` is attached.
- Release body starts with the matching `CHANGELOG.md` section.
- A previous app version detects the update and shows What’s New.

## Helper Script

Run `.agents/skills/freeflow-release/scripts/freeflow-release-check.sh <version>` from the FreeFlow repo root to check release preconditions. It validates semver shape, required files, changelog extraction, workflow trigger basics, and tag availability.
Run `.agents/skills/fluent-release/scripts/fluent-release-check.sh <version>` from the Fluent repo root to check release preconditions. It validates semver shape, required files, changelog extraction, workflow trigger basics, and tag availability.
4 changes: 4 additions & 0 deletions .agents/skills/fluent-release/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Fluent Release"
short_description: "Prepare and publish Fluent semver releases."
default_prompt: "Prepare a new Fluent release."
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,21 @@ if ! grep -q 'tags:' .github/workflows/release.yml || ! grep -q 'v\*\.\*\.\*' .g
exit 1
fi

if ! .github/scripts/changelog-section.sh "$version" >/tmp/freeflow-release-notes.md; then
if ! .github/scripts/changelog-section.sh "$version" >/tmp/fluent-release-notes.md; then
echo "Could not extract CHANGELOG.md section for $version" >&2
exit 1
fi

if [[ ! -s /tmp/freeflow-release-notes.md ]]; then
if [[ ! -s /tmp/fluent-release-notes.md ]]; then
echo "Extracted changelog section is empty for $version" >&2
exit 1
fi

if ! grep -q "^## \\[$version\\]" /tmp/freeflow-release-notes.md; then
if ! grep -q "^## \\[$version\\]" /tmp/fluent-release-notes.md; then
echo "Extracted changelog section has an unexpected heading." >&2
exit 1
fi

echo "Release checks passed for $tag"
echo
cat /tmp/freeflow-release-notes.md
cat /tmp/fluent-release-notes.md
Comment on lines +50 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use a unique temp file here.

/tmp/fluent-release-notes.md is predictable, so another local process can pre-create or symlink it and race this checker. That can corrupt the release precheck on shared machines.

🔧 Suggested fix
+tmp_notes="$(mktemp)"
+trap 'rm -f "$tmp_notes"' EXIT
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 59-59: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/fluent-release-notes.md
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 66-66: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/fluent-release-notes.md
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/skills/fluent-release/scripts/fluent-release-check.sh around lines
50 - 67, The release check script is using a predictable temporary path for
changelog output, which can be raced or pre-created by another local process.
Update the logic around the changelog extraction in the fluent-release check
flow to create and use a unique temp file per run, then reuse that same
generated path for the emptiness and heading checks as well as the final cat
output. Keep the existing validation behavior in the script’s changelog handling
block, but avoid any hardcoded shared temp filename.

Source: Linters/SAST tools

4 changes: 0 additions & 4 deletions .agents/skills/freeflow-release/agents/openai.yaml

This file was deleted.

16 changes: 8 additions & 8 deletions .github/workflows/dev-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: |
plutil -replace CFBundleShortVersionString -string "${{ steps.version.outputs.version }}" Info.plist
plutil -replace CFBundleVersion -string "${{ github.run_number }}" Info.plist
plutil -replace FreeFlowBuildTag -string "${{ steps.version.outputs.build_tag }}" Info.plist
plutil -replace FluentBuildTag -string "${{ steps.version.outputs.build_tag }}" Info.plist

- name: Install build tools
run: brew install create-dmg fileicon
Expand Down Expand Up @@ -107,18 +107,18 @@ jobs:
run: make dmg ARCH=universal CODESIGN_IDENTITY="$CODESIGN_IDENTITY"

- name: Sign universal DMG
run: codesign --force --sign "$CODESIGN_IDENTITY" "build/FreeFlow Dev.dmg"
run: codesign --force --sign "$CODESIGN_IDENTITY" "build/Fluent Dev.dmg"

- name: Notarize universal DMG
run: |
xcrun notarytool submit "build/FreeFlow Dev.dmg" \
xcrun notarytool submit "build/Fluent Dev.dmg" \
--keychain-profile "notarytool-profile" \
--keychain "$KEYCHAIN_PATH" \
--wait
xcrun stapler staple "build/FreeFlow Dev.dmg"
xcrun stapler staple "build/Fluent Dev.dmg"

- name: Save universal DMG
run: mv "build/FreeFlow Dev.dmg" FreeFlow-Dev.dmg
run: mv "build/Fluent Dev.dmg" Fluent-Dev.dmg

- name: Move dev tag
run: |
Expand All @@ -130,16 +130,16 @@ jobs:
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
tag_name: ${{ steps.version.outputs.tag }}
name: FreeFlow Dev ${{ steps.version.outputs.short_sha }}
name: Fluent Dev ${{ steps.version.outputs.short_sha }}
body: |
Development build from `${{ github.sha }}`.

**[FreeFlow-Dev.dmg](https://github.com/zachlatta/freeflow/releases/download/${{ steps.version.outputs.tag }}/FreeFlow-Dev.dmg)** - Universal dev build for all Macs (Apple Silicon + Intel).
**[Fluent-Dev.dmg](https://github.com/inhaq/fluent/releases/download/${{ steps.version.outputs.tag }}/Fluent-Dev.dmg)** - Universal dev build for all Macs (Apple Silicon + Intel).
generate_release_notes: false
make_latest: false
prerelease: true
files: |
FreeFlow-Dev.dmg
Fluent-Dev.dmg

# --- Cleanup ---
- name: Cleanup signing artifacts
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
run: |
plutil -replace CFBundleShortVersionString -string "${{ steps.version.outputs.version }}" Info.plist
plutil -replace CFBundleVersion -string "${{ github.run_number }}" Info.plist
plutil -replace FreeFlowBuildTag -string "${{ steps.version.outputs.tag }}" Info.plist
plutil -replace FluentBuildTag -string "${{ steps.version.outputs.tag }}" Info.plist

- name: Build release notes from changelog
id: release_notes
Expand All @@ -46,7 +46,7 @@ jobs:
echo
echo "## Download"
echo
echo "**[FreeFlow.dmg](https://github.com/zachlatta/freeflow/releases/download/${{ steps.version.outputs.tag }}/FreeFlow.dmg)** — Universal build for all Macs (Apple Silicon + Intel)."
echo "**[Fluent.dmg](https://github.com/inhaq/fluent/releases/download/${{ steps.version.outputs.tag }}/Fluent.dmg)** — Universal build for all Macs (Apple Silicon + Intel)."
echo "EOF"
} >> "$GITHUB_OUTPUT"

Expand Down Expand Up @@ -113,36 +113,36 @@ jobs:

# --- Build universal ---
- name: Build universal
run: make ARCH=universal APP_NAME=FreeFlow BUNDLE_ID=com.zachlatta.freeflow CODESIGN_IDENTITY="$CODESIGN_IDENTITY"
run: make ARCH=universal APP_NAME=Fluent BUNDLE_ID=com.inhaq.fluent CODESIGN_IDENTITY="$CODESIGN_IDENTITY"

- name: Create universal DMG
run: make dmg ARCH=universal APP_NAME=FreeFlow BUNDLE_ID=com.zachlatta.freeflow CODESIGN_IDENTITY="$CODESIGN_IDENTITY"
run: make dmg ARCH=universal APP_NAME=Fluent BUNDLE_ID=com.inhaq.fluent CODESIGN_IDENTITY="$CODESIGN_IDENTITY"

- name: Sign universal DMG
run: codesign --force --sign "$CODESIGN_IDENTITY" build/FreeFlow.dmg
run: codesign --force --sign "$CODESIGN_IDENTITY" build/Fluent.dmg

- name: Notarize universal DMG
run: |
xcrun notarytool submit build/FreeFlow.dmg \
xcrun notarytool submit build/Fluent.dmg \
--keychain-profile "notarytool-profile" \
--keychain "$KEYCHAIN_PATH" \
--wait
xcrun stapler staple build/FreeFlow.dmg
xcrun stapler staple build/Fluent.dmg

- name: Save universal DMG
run: mv build/FreeFlow.dmg FreeFlow.dmg
run: mv build/Fluent.dmg Fluent.dmg

# --- Release ---
- name: Create Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
tag_name: ${{ steps.version.outputs.tag }}
name: FreeFlow ${{ steps.version.outputs.version }}
name: Fluent ${{ steps.version.outputs.version }}
body: ${{ steps.release_notes.outputs.body }}
generate_release_notes: false
make_latest: true
files: |
FreeFlow.dmg
Fluent.dmg

# --- Cleanup ---
- name: Cleanup signing artifacts
Expand Down
35 changes: 25 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
# Changelog

All notable changes to FreeFlow are documented here.
All notable changes to Fluent are documented here.

This project uses semantic versioning for public releases. Use `MAJOR.MINOR.PATCH`, where:

- `MAJOR` changes include breaking behavior or major compatibility changes.
- `MINOR` changes add user-visible features and improvements.
- `PATCH` changes fix bugs, polish existing behavior, or make small internal improvements.

## [1.1.0] - 2026-06-03
## [1.2.0] - 2026-06-26

The project is now **Fluent**, a faster, more token-efficient fork of Free Flow. This release rebrands the app and trims the dictation pipeline so each dictation uses fewer tokens and completes more quickly, without changing the core experience.

### Changed

- Renamed the app and project from FreeFlow to Fluent, including the bundle identifier (`com.inhaq.fluent`), app menus, permission prompts, build tooling, and the project website.

### Improved

- Plain dictation now skips screenshot capture entirely and sends only lightweight app and window metadata to the context model, removing the most expensive step from the common dictation path and saving the image tokens it used to spend.
- Screenshots, when still needed (such as in Edit Mode), are captured at a smaller default dimension (768px), JPEG-compressed, and cropped of surrounding whitespace before upload, lowering image token usage.
- The default cleanup model runs with low reasoning effort and reasoning output disabled, reducing token overhead and latency.
- Reasoning blocks are always stripped from model output so reasoning-oriented models stay fast and never paste their internal scratch work.



### Added

Expand All @@ -31,7 +46,7 @@ This project uses semantic versioning for public releases. Use `MAJOR.MINOR.PATC

## [1.0.0] - 2026-05-20

FreeFlow is now considered feature-complete and stable enough for a 1.0 release.
Fluent is now considered feature-complete and stable enough for a 1.0 release.

### Added

Expand Down Expand Up @@ -65,9 +80,9 @@ FreeFlow is now considered feature-complete and stable enough for a 1.0 release.
### Added

- Output Language setting for automatically translating dictated text before it is pasted.
- Transcription Language setting for choosing the language FreeFlow listens for during dictation.
- Recording state flag file for external tools that need to know when FreeFlow is actively recording.
- Distinct FreeFlow Dev app and menu bar icons so development builds are easier to tell apart from release builds.
- Transcription Language setting for choosing the language Fluent listens for during dictation.
- Recording state flag file for external tools that need to know when Fluent is actively recording.
- Distinct Fluent Dev app and menu bar icons so development builds are easier to tell apart from release builds.

### Improved

Expand All @@ -78,7 +93,7 @@ FreeFlow is now considered feature-complete and stable enough for a 1.0 release.
### Fixed

- Fixed audio recording crashes caused by unexpected input formats, resampling, and upload-path conversion.
- Fixed cases where FreeFlow could silently fall back when the selected microphone was unavailable.
- Fixed cases where Fluent could silently fall back when the selected microphone was unavailable.
- Fixed paste shortcuts on Colemak-DH and other non-QWERTY keyboard layouts.
- Fixed output language handling when custom system prompts are enabled.

Expand All @@ -99,7 +114,7 @@ FreeFlow is now considered feature-complete and stable enough for a 1.0 release.
- A voice command for submitting text: say "press enter" at the end of a dictation.
- Audio controls that can mute or pause other audio while you dictate, then restore it when recording stops.
- Build details in Settings for easier troubleshooting.
- Direct shortcuts from FreeFlow to the right macOS permission settings.
- Direct shortcuts from Fluent to the right macOS permission settings.
- A What’s New popup when an update is available.

### Improved
Expand All @@ -109,9 +124,9 @@ FreeFlow is now considered feature-complete and stable enough for a 1.0 release.
- Exported run logs include more useful context for reproducing issues.
- Realtime transcription is more reliable when recordings are cancelled, retried, or finish with no text.
- Provider settings are easier to edit without accidental whitespace or half-saved values.
- FreeFlow now warns you if alert sounds may be hard to hear because system audio is muted or very low.
- Fluent now warns you if alert sounds may be hard to hear because system audio is muted or very low.
- Update prompts now show the version, release date, and release notes more clearly.
- FreeFlow now uses proper version numbers for updates instead of internal build names.
- Fluent now uses proper version numbers for updates instead of internal build names.

### Fixed

Expand Down
File renamed without changes.
14 changes: 7 additions & 7 deletions Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,30 @@
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>FreeFlow</string>
<string>Fluent</string>
<key>CFBundleDisplayName</key>
<string>FreeFlow</string>
<string>Fluent</string>
<key>CFBundleIdentifier</key>
<string>com.zachlatta.freeflow</string>
<string>com.inhaq.fluent</string>
<key>CFBundleVersion</key>
<string>0.3.0</string>
<key>CFBundleShortVersionString</key>
<string>0.3.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleExecutable</key>
<string>FreeFlow</string>
<string>Fluent</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>LSMinimumSystemVersion</key>
<string>13.0</string>
<key>LSUIElement</key>
<true/>
<key>NSMicrophoneUsageDescription</key>
<string>FreeFlow needs microphone access to transcribe your speech.</string>
<string>Fluent needs microphone access to transcribe your speech.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>FreeFlow needs speech recognition to convert your voice to text.</string>
<string>Fluent needs speech recognition to convert your voice to text.</string>
<key>NSAccessibilityUsageDescription</key>
<string>FreeFlow needs accessibility access to detect the text cursor position and paste transcribed text.</string>
<string>Fluent needs accessibility access to detect the text cursor position and paste transcribed text.</string>
</dict>
</plist>
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
APP_NAME ?= FreeFlow Dev
BUNDLE_ID ?= com.zachlatta.freeflow.dev
APP_NAME ?= Fluent Dev
BUNDLE_ID ?= com.inhaq.fluent.dev
BUILD_DIR = build
APP_BUNDLE = $(BUILD_DIR)/$(APP_NAME).app
CODESIGN_IDENTITY ?= FreeFlow Dev
CODESIGN_IDENTITY ?= Fluent Dev
CONTENTS = $(APP_BUNDLE)/Contents
MACOS_DIR = $(CONTENTS)/MacOS
empty :=
Expand All @@ -16,8 +16,8 @@ ARCH ?= $(shell uname -m)

# Pick the icon source based on which bundle we are building. Dev builds get
# a distinct hammer-on-waveform icon so a developer's dock shows at a glance
# which FreeFlow they are running when both are installed side by side.
ifeq ($(APP_NAME),FreeFlow Dev)
# which Fluent they are running when both are installed side by side.
ifeq ($(APP_NAME),Fluent Dev)
ICON_SOURCE = Resources/AppIcon-Dev-Source.png
ICON_ICNS = Resources/AppIcon-Dev.icns
else
Expand Down Expand Up @@ -65,7 +65,7 @@ endif
@plutil -replace NSMicrophoneUsageDescription -string "$(APP_NAME) needs microphone access to transcribe your speech." "$(CONTENTS)/Info.plist"
@plutil -replace NSSpeechRecognitionUsageDescription -string "$(APP_NAME) needs speech recognition to convert your voice to text." "$(CONTENTS)/Info.plist"
@plutil -replace NSAccessibilityUsageDescription -string "$(APP_NAME) needs accessibility access to detect the text cursor position and paste transcribed text." "$(CONTENTS)/Info.plist"
@codesign --force --options runtime --sign "$(CODESIGN_IDENTITY)" --entitlements FreeFlow.entitlements "$(APP_BUNDLE)"
@codesign --force --options runtime --sign "$(CODESIGN_IDENTITY)" --entitlements Fluent.entitlements "$(APP_BUNDLE)"
@echo "Built $(APP_BUNDLE)"

icon: $(ICON_ICNS)
Expand Down
Loading