Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2c7c215
Feat - Trial mode & license
lguichard Jul 9, 2026
7a48d51
Merge branch 'main' into feat/trial-mode
lguichard Jul 9, 2026
b76d8cc
Wip
lguichard Jul 9, 2026
8866258
Wip
lguichard Jul 9, 2026
352ad1f
Merge branch 'main' into feat/trial-mode
lguichard Jul 15, 2026
0744d0f
Merge branch 'main' into feat/trial-mode
lguichard Jul 24, 2026
169e868
Merge branch 'main' into feat/trial-mode
lguichard Sep 2, 2026
d76fd00
Wip
lguichard Sep 9, 2026
e6cfcee
Wip
lguichard Sep 9, 2026
54022c6
Wip
lguichard Sep 9, 2026
06cd781
Merge branch 'main' into feat/trial-mode
lguichard Sep 9, 2026
8954932
Merge branch 'main' into feat/trial-mode
lguichard Oct 3, 2026
824e0ec
Update LicenseManagerTests.swift
lguichard Oct 5, 2026
2b672dc
Wip
lguichard Oct 6, 2026
1947c18
Wip
lguichard Oct 6, 2026
9e3f452
Merge branch 'main' into feat/trial-mode
lguichard Oct 9, 2026
96dc860
Merge branch 'main' into feat/pro-version
lguichard Oct 9, 2026
69f0744
Merge branch 'main' into feat/pro-version
lguichard Oct 9, 2026
c180b48
Merge branch 'main' into feat/pro-version
lguichard Oct 9, 2026
9fa54c1
Merge branch 'main' into feat/pro-version
lguichard Oct 9, 2026
3eaf4f9
Wip
lguichard Oct 10, 2026
3016815
Wip
lguichard Oct 12, 2026
1656d93
Wip
lguichard Oct 13, 2026
97d63f7
Merge branch 'main' into feat/pro-version
lguichard Oct 15, 2026
f299d56
Update README.md
lguichard Oct 16, 2026
b58d85b
Wip
lguichard Oct 16, 2026
32e2506
Wip
lguichard Oct 20, 2026
ecbee55
Wip
lguichard Oct 20, 2026
399c268
Wip
lguichard Oct 20, 2026
a64c09b
Wip
lguichard Oct 21, 2026
c0b0d93
Wip
lguichard Oct 21, 2026
619b1ae
Wip
lguichard Oct 21, 2026
ca9e770
Wip
lguichard Oct 21, 2026
be243dc
Wip
lguichard Oct 22, 2026
a1ba8b2
Merge branch 'main' into feat/pro-version
lguichard Oct 22, 2026
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
2 changes: 2 additions & 0 deletions Forel.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
</dict>
Expand Down
10 changes: 9 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,29 @@ import PackageDescription

let package = Package(
name: "Forel",
platforms: [.macOS(.v13)],
platforms: [.macOS(.v14)],
products: [
.library(name: "ForelCore", targets: ["ForelCore"]),
.executable(name: "ForelApp", targets: ["ForelApp"]),
],
dependencies: [
.package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.18"),
.package(url: "https://github.com/lab421/licensing.git", revision: "1569b2038595862084a466d8dea806b360642608"),
],
targets: [
.target(
name: "ForelCore",
dependencies: [
.product(name: "ZIPFoundation", package: "ZIPFoundation"),
.product(name: "Lab421Licensing", package: "licensing"),
]
),
.executableTarget(
name: "ForelApp",
dependencies: ["ForelCore"],
dependencies: [
"ForelCore",
.product(name: "Lab421Licensing", package: "licensing"),
],
resources: [.copy("Resources")]
),
.testTarget(
Expand Down
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ File matching and processing happen **locally on your Mac**. Forel does not uplo

- **Rule-based automation** — Create flexible rules combining filename patterns, file types, sizes, dates, tags, and Finder color labels.
- **Content matching & OCR** — Match text inside documents, images, and scanned PDFs using local extraction and on-device OCR. See [Content matching](#content-matching) for supported formats and limits.
- **Folder watching** — Monitor any number of folders in real time with native macOS FSEvents.
- **Folder watching** — Monitor folders in real time with native macOS FSEvents: up to 2 in Free, unlimited in Pro.
- **Dry Run & Run Now** — Preview matching files and planned actions without changing files, then run rules manually on existing files when ready.
- **History & Undo** — Review action results and undo supported actions when it is safe to do so.
- **Menu bar app** — Forel lives in your menu bar. Toggle individual rules on/off without opening the main window.
Expand All @@ -85,6 +85,36 @@ File matching and processing happen **locally on your Mac**. Forel does not uplo

## Installation

Forel Free is available indefinitely without a license. Pro is a paid license; there is no trial.

### Free and Pro

| Feature | Free | Pro |
|---|---|---|
| Watched folders | 2, including disabled folders | Unlimited |
| Files processed | 300 per calendar month | Unlimited |
| Rules per folder and actions per rule | Unlimited | Unlimited |
| Background watching and Run Now | Included | Included |
| Move, copy, rename, tags, labels, trash, delete, uncompress, open app | Included | Included |
| Filename, type, size, dates, tags, and download metadata conditions | Included | Included |
| Text matching in documents and PDFs with a text layer | Included | Included |
| On-device OCR for images and scanned PDFs | — | Included |
| Custom scripts and Apple Shortcuts | Included | Included |
| Imports into Photos, Music, and TV | — | Included |
| Dry Run preview, history, and supported Undo | Included | Included |
| File processing on your Mac | Included | Included |
| [Scheduler](#roadmap) | — | Planned |
| [Sync actions](#roadmap) | — | Planned |
| [AI features](#roadmap) | — | Planned |

**Free:** organize your everyday folders automatically with rules, custom scripts, and Apple Shortcuts. **Pro:** automate all your folders, process unlimited files, and recognize scanned documents.

[View pricing and buy Forel Pro](https://forel-app.github.io/), then activate your license in **Settings → License**. The Free badge in the quick panel also opens License settings. Monthly file usage is visible in the app; attempting a Pro action or reaching a quota explains the benefit and offers a purchase or activation link.

Free includes **2 watched folders and 300 processed files per calendar month**, with unlimited rules and actions. Each item counts once per run when an action is applied; Dry Run does not count. The allowance renews on the first day of each month. At the limit, files stay in place and automatic processing resumes after renewal or Pro activation.

Switching back to Free keeps your configuration. Reorder folders to choose which 2 remain available. OCR for images and scanned PDFs, and media library imports, require Pro.

### Homebrew

```bash
Expand Down Expand Up @@ -219,20 +249,22 @@ History / Undo (SQLite)
- [ ] Export / Import rules
- [ ] Toggle extension hidden / visible
- [ ] Move folder based on criteria
- [ ] Scheduler
- [ ] Scheduler — **Pro · Coming soon**
- [ ] Copy rules accros watching folders
- [ ] Compress actions
- [ ] Sync actions
- [ ] Sync actions — **Pro · Coming soon**
- [ ] Dynamic destination folders with date tokens (e.g. `~/Screenshots/{year}/{month}`)
- [ ] Upload actions
- [ ] AI features
- [ ] AI features — **Pro · Coming soon**

## Content matching

The **Contents** condition matches text found *inside* files. Everything is read
locally — no cloud, OCR runs on-device. When a file's text can't be read, the Dry
Run tells you why.

Document text matching is available in Free and Pro. Vision OCR for images and scanned PDFs requires Pro; Free never runs OCR. The technical extraction limits below apply to both plans where the feature is available.

```
File path
│
Expand Down
43 changes: 42 additions & 1 deletion Sources/ForelApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import AppKit
import Combine
import ForelCore
import Lab421Licensing

/// Closing the window hides it instead of quitting; Forel keeps running in
/// the menu bar. Quit is only available from the status item menu.
Expand All @@ -29,6 +32,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
var statusBarController: StatusBarController?
private var model: AppModel?
private var updater: UpdaterManager?
let license = ForelLicenseManager(
isEnabled: BuildCapabilities.current.usesDirectLicensing,
productID: ForelLicensing.productID,
appName: ForelLicensing.appName,
deviceNameFallback: "This Mac",
secureStore: ForelKeychainLicenseStore(
service: ForelLicensing.keychainService,
userDefaultsSuiteName: ForelLicensing.userDefaultsSuite
),
deviceIdentifierProvider: HardwareDeviceIdentifierProvider(userDefaultsSuiteName: ForelLicensing.userDefaultsSuite),
offlineTokenPublicKeyRaw: ForelLicensing.productionPublicKeyRaw,
baseURL: ForelLicensing.baseURL
)
let settingsNavigation = SettingsNavigation()
private var subscriptions = Set<AnyCancellable>()
/// The window hosting `ContentView`, reported by `ForelMacApp` through
/// `mainWindowDidAttach`. Tracked explicitly rather than looked up in
/// `NSApp.windows`, which also holds the menu bar item's own window and
Expand All @@ -48,6 +66,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
model.applyDockIconPreference()
setUpStatusBar()
showMainWindowOnFirstLaunch()

model.setLicenseAccessLimited(!license.hasAccess)
// `dropFirst()` skips the state already accounted for above, so a
// fresh subscriber doesn't double-apply the state it was created
// with — matching the `hasAccess` snapshot immediately before this.
license.$state
.removeDuplicates()
.dropFirst()
.sink { [weak self] state in
guard let self else { return }
self.model?.setLicenseAccessLimited(state == .free)
}
.store(in: &subscriptions)
license.start()
}

/// Called whenever `ContentView` lands in a window — at launch, or later
Expand Down Expand Up @@ -151,10 +183,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
statusBarController = StatusBarController(
model: model,
updater: updater,
onOpenMainWindow: { [weak self] in self?.openMainWindow() }
license: license,
onOpenMainWindow: { [weak self] in self?.openMainWindow() },
onOpenLicense: { [weak self] in self?.prepareLicenseSettings() }
)
}

/// Selects the License tab and activates Forel before the quick panel
/// opens the Settings scene using SwiftUI's openSettings action.
private func prepareLicenseSettings() {
settingsNavigation.selectedCategory = .license
NSApp.activate(ignoringOtherApps: true)
}

private func openMainWindow() {
guard let mainWindow else {
// Started as a login item, Forel has no main window yet; reopening
Expand Down
Loading
Loading