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
152 changes: 152 additions & 0 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
name: Desktop Release

# Builds the Folklore desktop app (Tauri) into native installers for all three
# desktop OSes and attaches them to a GitHub release. One codebase → .dmg /
# .msi+.exe / .AppImage+.deb.
#
# Trigger: push a `desktop-v*` tag (kept separate from the CLI's `v*` tags so
# the two release cadences don't couple), or run manually.

on:
push:
tags: ['desktop-v*']
workflow_dispatch:

jobs:
build:
permissions:
contents: write
# 'true'/'false' string — whether macOS notarization secrets are configured.
# secrets.* is valid in a job-level env expression (unlike a step `if`).
env:
SIGN_MAC: ${{ secrets.APPLE_CERTIFICATE != '' }}
strategy:
fail-fast: false
matrix:
include:
- platform: macos-14 # Apple silicon + Intel via universal target
target: universal-apple-darwin
args: '--target universal-apple-darwin'
- platform: ubuntu-22.04 # .AppImage + .deb
target: ''
args: ''
- platform: windows-latest # .msi + NSIS .exe
target: ''
args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v5

- name: Install Linux webview deps
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev \
librsvg2-dev patchelf libssl-dev

- uses: actions/setup-node@v5
with:
node-version: 22

- uses: dtolnay/rust-toolchain@stable
with:
# macOS universal needs both arches available to the toolchain.
targets: ${{ matrix.target == 'universal-apple-darwin' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}

- name: Cache cargo
uses: swatinem/rust-cache@v2
with:
workspaces: client/desktop/src-tauri

- name: Install frontend deps
run: npm install
working-directory: client/desktop

# Bundle a Node runtime into the app so the onboard wizard runs the folklore
# CLI (via npx) with zero Node installed on the user's machine. Extracted
# into resources/node/, which tauri.conf bundles as the app's `node`
# resource; lib.rs prepends its bin dir to the child PATH.
- name: Bundle Node runtime (macOS, universal)
if: matrix.platform == 'macos-14'
run: |
set -euo pipefail
N=22.11.0
DEST=client/desktop/src-tauri/resources/node
for arch in arm64 x64; do
curl -sSL "https://nodejs.org/dist/v$N/node-v$N-darwin-$arch.tar.gz" | tar xz
done
# start from the arm64 dist (npm + lib are arch-independent JS)…
cp -R "node-v$N-darwin-arm64/." "$DEST/"
# …then replace the node binary with a universal (arm64 + x86_64) one
lipo -create "node-v$N-darwin-arm64/bin/node" "node-v$N-darwin-x64/bin/node" -output "$DEST/bin/node"
"$DEST/bin/node" --version

- name: Bundle Node runtime (Linux)
if: matrix.platform == 'ubuntu-22.04'
run: |
set -euo pipefail
N=22.11.0
DEST=client/desktop/src-tauri/resources/node
curl -sSL "https://nodejs.org/dist/v$N/node-v$N-linux-x64.tar.xz" | tar xJ
cp -R "node-v$N-linux-x64/." "$DEST/"
"$DEST/bin/node" --version

- name: Bundle Node runtime (Windows)
if: matrix.platform == 'windows-latest'
shell: pwsh
run: |
$N = "22.11.0"
$DEST = "client/desktop/src-tauri/resources/node"
curl.exe -sSL "https://nodejs.org/dist/v$N/node-v$N-win-x64.zip" -o node.zip
Expand-Archive node.zip -DestinationPath .
Copy-Item -Recurse -Force "node-v$N-win-x64/*" $DEST
& "$DEST/node.exe" --version

# macOS signing + notarization. Passing an empty APPLE_CERTIFICATE makes
# tauri-action attempt a codesign import with invalid data and fail, so the
# signing env is only attached when the certificate secret actually exists.
# Drop these repo secrets to turn notarization on — no code change:
# APPLE_CERTIFICATE (base64 .p12), APPLE_CERTIFICATE_PASSWORD,
# APPLE_SIGNING_IDENTITY, APPLE_ID, APPLE_PASSWORD, APPLE_TEAM_ID
- name: Build + release the app (signed)
if: matrix.platform == 'macos-14' && env.SIGN_MAC == 'true'
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
projectPath: client/desktop
tagName: ${{ github.ref_name }}
releaseName: 'Folklore Desktop ${{ github.ref_name }}'
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}

- name: Build + release the app (unsigned)
if: '!(matrix.platform == ''macos-14'' && env.SIGN_MAC == ''true'')'
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
projectPath: client/desktop
tagName: ${{ github.ref_name }}
releaseName: 'Folklore Desktop ${{ github.ref_name }}'
releaseBody: |
The Folklore desktop app — download, open, and click **Install everything**.
It wires the folklore memory server into every AI coding tool on your
machine (Claude Code, Cursor, Cline, Windsurf, Gemini CLI, Zed, opencode,
Roo) and starts the local daemon. No terminal.

- macOS: `.dmg` (universal — Apple silicon + Intel)
- Windows: `.msi` / setup `.exe`
- Linux: `.AppImage` / `.deb`

Unsigned for now — on first open, allow it in your OS security settings.
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
24 changes: 24 additions & 0 deletions client/desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
62 changes: 62 additions & 0 deletions client/desktop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Folklore desktop

The cross-platform desktop app for folklore — one Tauri (Rust + webview) codebase
that ships as native installers for macOS, Windows, and Linux. It replaces the
macOS-only Swift menubar client (`client/menubar-macos`).

Two surfaces:

- **Setup wizard** (`src/index.html`) — the "download installs everything, no
command" window. One button runs `folklore onboard`, which creates the signed
identity, starts the daemon, and registers the folklore MCP server in every AI
coding tool on the machine (Claude Code, Cursor, Cline, Windsurf, Gemini CLI,
Zed, opencode, Roo). A live list shows which tools were detected.
- **Peer-activity island** (`src/island.html`) — left-click the tray icon for a
near-black island popover (the notch spot on a MacBook; a floating pill
elsewhere) that tails `~/.folklore/activity-feed.jsonl` and shows the latest
trace a peer sent or pulled. Right-click the tray for the menu.

## How it drives folklore

The heavy lifting is the Node CLI (`@usefolklore/folklore`): daemon, MCP server,
`onboard`, `harness`. The Rust shell (`src-tauri/src/lib.rs`) resolves and runs
it, in this order:

1. A system-installed `folklore` on PATH (probing nvm / fnm / volta / bun /
Homebrew / global-npm, since a double-clicked GUI app inherits none of these).
2. **The bundled Node runtime** (zero system Node): the app carries a clean Node
dist under `resources/node`. On first run it uses that node + npm to
`install -g @usefolklore/folklore --prefix <app-data>/cli`, then runs the
installed CLI's `bin/folklore.js` **directly** with the bundled node. (The
dist's `bin/npx`/`bin/npm` symlinks get flattened by the bundler, so invoking
the entry `.js` directly is the reliable path — verified against a shipped
`.dmg` with system Node removed from PATH.)
3. System `npx` on the published package.
4. A clear "install Node" error.

Single-file bundling isn't possible — the CLI has native addons (better-sqlite3,
sqlite-vec, onnx, tree-sitter, sharp) — which is why the Node runtime is bundled
and the deps are fetched (with the platform's native prebuilds) at first run.

## Build

```bash
npm install
npm run tauri dev # run locally (uses system Node; resources/node is empty in the repo)
npm run tauri build # produce installers for the current OS
```

`resources/node` holds only a README in git; the real per-platform Node payload
is fetched and lipo'd (universal on macOS) by the release workflow. A local
`tauri build` therefore falls back to system Node — that's expected.

## Release

`.github/workflows/desktop-release.yml` builds on macOS / Windows / Linux for
every `desktop-v*` tag: bundles the platform Node, then emits `.dmg` (universal),
`.msi` + NSIS `.exe`, and `.AppImage` + `.deb`, attached to a draft GitHub
release. macOS signing/notarization turns on automatically when the `APPLE_*`
repo secrets are set.

Icons (app, installer, tray) are generated from `site/assets/folklore-hero.svg`
— the hero-section hearth — via `tauri icon`.
51 changes: 51 additions & 0 deletions client/desktop/folklore.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Homebrew cask for the Folklore desktop app (Tauri).
#
# Supersedes client/menubar-macos/folklore.rb (the old Swift menubar cask). The
# desktop app is the cross-platform GUI: tray + onboard wizard that wires the
# folklore memory server into every AI tool on the machine.
#
# Why a cask: the .dmg is unsigned/not-notarized until there's an Apple
# Developer ID, so a plain download is quarantined. `brew install --cask` strips
# the quarantine attribute, so it opens without the Privacy & Security prompt.
#
# Not consumed from this repo — Homebrew reads casks from a tap. To publish:
# 1. Create a public repo `usefolklore/homebrew-tap`.
# 2. Copy this file to `Casks/folklore.rb` in it.
# 3. Set `version` to the desktop release (e.g. 0.1.0) and fill `sha256` from
# the published .dmg: shasum -a 256 Folklore_0.1.0_universal.dmg
# 4. Bump both on each desktop-v* release.
#
# Then: brew install --cask usefolklore/tap/folklore
#
cask "folklore" do
version "0.1.0"
sha256 :no_check # replace with the .dmg's shasum -a 256 once desktop-v0.1.0 ships

url "https://github.com/usefolklore/folklore/releases/download/desktop-v#{version}/Folklore_#{version}_universal.dmg"
name "Folklore"
desc "Cross-platform tray app + setup wizard for a local folklore node"
homepage "https://usefolklore.sh/"

depends_on macos: ">= :ventura"

app "Folklore.app"

caveats <<~EOS
On first launch, click "Install everything" — Folklore wires the memory
server into every AI coding tool it finds and starts the local daemon.

It drives the folklore CLI under the hood; if you don't already have it,
the wizard fetches it via npx (Node required). Or install it yourself:

npm install -g @usefolklore/folklore

This build is not notarized. Homebrew has already cleared the quarantine
flag, so it opens without the Privacy & Security prompt.
EOS

zap trash: [
"~/.folklore",
"~/Library/Preferences/sh.usefolklore.desktop.plist",
"~/Library/Application Support/sh.usefolklore.desktop",
]
end
Loading