Product Apps Observe Privacy CLI FAQ GitHub

Local-first · Five native apps

See what changed.

Take a snapshot. Use your machine. Take another. Diffuse diffs the two — on this device, in this session, without an account, a cloud, or a telemetry pipeline.

Android · macOS · iOS · iPadOS · watchOS Kotlin and Swift. Genuinely native.
MIT · unsigned CI You own the bits. GitHub hosts the code.
OS versionHardwareDisplays PowerStorage volumesNetwork path InterfacesWi-Fi nameApplications Developer toolsGit metadataProcesses · opt-in OS versionHardwareDisplays PowerStorage volumesNetwork path InterfacesWi-Fi nameApplications Developer toolsGit metadataProcesses · opt-in
No accountNo cloud No telemetryNo Keychain No file contentsNo coordinates Redaction on exportJSON on disk No accountNo cloud No telemetryNo Keychain No file contentsNo coordinates Redaction on exportJSON on disk

The question

Two points in time. One honest diff.

Diffuse is not a dashboard of everything. It is a record of what moved — Node upgraded, a volume vanished, Wi-Fi flipped from Home to Office, a watched repo changed branch.

Diffuse for Mac: overview showing 12 changes, a Studio Display removed, network Home to Office, and a Git branch switch.
macOS · Overview
Diffuse on iPhone: four changes since the previous snapshot, Wi-Fi to Cellular.
iPhone
Diffuse on iPhone compare screen with before and after values.
Compare
Diffuse on Apple Watch showing Δ 4 changes at a glance.

Δ on your wrist

Standalone watch app and complications. No paired iPhone required. The widget never decodes a snapshot — just a count and a peak severity.

Five apps

Written for the device, not scaled down.

Same question everywhere. Adaptive Compose on Android. Split view on Mac. Tabs on iPhone. Three columns on iPad, including portrait. A glance on Watch.

macOS

The full workspace

Navigation split view, menu bar extra, scheduled capture on a timer and on wake, repository watch list, developer-tool collectors. Not sandboxed — because spawning node --version should not hide behind a temporary exception.

Mac compare workspace with a timeline of snapshots and a detailed diff.
iPadOS

Three columns, always

Snapshots, changes, and clusters side by side. Regular width never collapses to a phone layout.

iPad three-column workspace: snapshots, changes, and clusters.
iOS

Phone, not a shrunk Mac

Overview, timeline, compare, settings. Capture on open, on demand, and via background refresh.

iPhone snapshots timeline.
Android

Kotlin, all the way down

Native Compose UI, adaptive navigation, local JSON history, privacy-led export, and WorkManager scheduling. No Swift bridge and no Internet permission.

Diffuse for Android overview showing changes since the previous snapshot.

What it observes

Honest per platform. Not a pretend Mac on a Watch.

A capability is a stable id, a travelling schema, and a collector. Register it only where the hardware can actually see it. Adding Bun is a tool adapter. Adding Bluetooth peripherals would be a new capability.

Mac capabilities list: system, hardware, displays, network, storage, developer tools, and git watch list.
macOS · Capabilities
Diffuse for Android settings: capture cadence, skip-unchanged, and capability toggles. No Internet permission.
Android · Settings

Mac

  • System OS, model
  • Hardware chip, memory
  • Displays resolution, refresh
  • Power battery, adapter
  • Network path, interfaces, SSID
  • Storage all volumes
  • Apps bundle id
  • Tools node, git, docker…
  • Git watched repos
  • Processes opt-in

iPhone & iPad

  • Device model, name
  • System version
  • Battery charge, state
  • Screen size, scale
  • Network path, interfaces
  • Storage app container
  • No process list
  • No git spawn
  • No developer-tool probes

Watch

  • Device model, size
  • Battery charge
  • System version
  • Storage container
  • Network path
  • Standalone — no iPhone required
  • Glance + Δ complications

Android

  • Device model, API, architecture
  • System kernel, locale, memory
  • Battery level, health, temperature
  • Display resolution, density, refresh
  • Storage app data volume
  • Network connectivity, VPN, interfaces
  • No accounts or hardware IDs
  • No installed-app or process inventory
  • No Internet permission
nodepythonswiftrustcgo rubyjavadenobunnpm pnpmyarnbrewdockerterraform gitxcodebuildcargouv

How it works

Capabilities travel with their own schema.

Adding something new to observe is a typed model, a collector, a registry line, and a test. Not a new screen. Not a new diff algorithm.

01 · Capture

Snapshot

Collectors run concurrently with a deadline. One failure never fails the snapshot. Manual captures always persist. Automatic ones can skip if nothing moved.

02 · Compare

Schema-driven diff

Identity matching, semantic versions, relative free-space noise, severity, clustering. diff(A, A) is empty. Change ids are content-derived — no random UUIDs.

03 · Keep

Files you can read

Application Support. One JSON file per snapshot plus a rebuildable index. Retention never deletes the newest. Export redacts by classification.

Schedule

Every four hours

Default cadence, with a 15-minute floor so a laptop that sleeps repeatedly does not flood the timeline.

Wake

System events

Mac captures on unlock. iPhone and iPad use background refresh. Watch uses its own refresh task. Android uses WorkManager. Same decide function.

Quiet

Skip if unchanged

Automatic captures may skip persist when the latest snapshot diffs empty. Pressing capture always saves.

Keep

90 days · 1 GiB

Retention never deletes the newest snapshot. Pinned and labelled ones are protected. Preview before prune.

A workday

The story the fixtures tell.

Same Mac, morning to evening. Diffuse does not infer why — it reports what moved, with severity, and groups changes a few minutes apart.

  1. Scheduled snapshot. Quiet morning: volumes, Wi-Fi Home, tools as yesterday.
  2. Node upgraded. Homebrew prefix unchanged. Significant, not a new capability.
  3. Network Home → Office, VPN off → on. SSID is sensitive on export.
  4. Watched repo switched branch. Git stores host + dirty + ahead/behind — not the diff.
  5. Studio Display gone. Identity is the display, not the name on the bezel.

Scope

Deliberate non-goals.

These are product decisions, recorded as ADRs — not missing features.

Diffuse does

  • Capture and diff on the device that observed it
  • Redact by classification when you export
  • Ship five native apps and a UI-free CLI
  • Keep snapshots as readable JSON you can copy

Diffuse will not

  • Sync Mac history to iPhone — or Android to anything else
  • Open an account or a cloud
  • Put a signing identity in this repository
  • Infer “why” with a model in the diff path

Privacy

Useful on device. Quiet on export.

An SSID is how you see Home → Office. It is also how a gist becomes a map of your life. Collection stores the real value. Sharing applies a policy.

Restricted fields never leave — even on “full detail.” Process listing is off until you turn it on. Git records branch, dirty, ahead/behind, and a remote host. Not file names. Not diffs.

  • Public OS version, model identifier Always included
  • Local Device name Standard export
  • Sensitive SSID, repository path Redacted unless full detail
  • Restricted Never designed to leave Always stripped
Never collected Passwords · tokens · Keychain · SSH/GPG keys · .env values · file contents · messages, mail, photos, browsing · location coordinates · anything sent off-device.

diffuse-dev

The same engine. No UI framework.

Capture, inspect, diff, validate, and print the generated privacy ledger from a terminal. Proof that the product is a domain, not a SwiftUI screen.

diffuse-dev
# What can this Mac observe right now?
swift run diffuse-dev capabilities

# Capture, then compare
swift run diffuse-dev snapshot /tmp/before.json --repos "$HOME/code/Diffuse"
swift run diffuse-dev snapshot /tmp/after.json --label "after lunch"
swift run diffuse-dev diff /tmp/before.json /tmp/after.json --markdown

# Fail a job if the build environment drifted
swift run diffuse-dev diff "$BEFORE" "$AFTER" --fail-on-change
capabilities

What this machine can observe right now, and why a collector is unavailable.

inspect · validate

Summarise a file. Fail if the snapshot does not match the current schema.

privacy

Print the generated ledger — the same contract the in-app Privacy screen uses.

--fail-on-change

Exit 2 when significant changes exist. A CI gate for environment drift.

Start

Clone. Capture. Compare.

macOS, Xcode 16+ / Swift 6, XcodeGen, SwiftFormat for Apple. JDK 17+ and Android SDK 36 for the Kotlin app. A healthy Apple checkout prints Diffuse is healthy.

01

Bootstrap

Generates the Xcode project from project.yml. The .xcodeproj is gitignored on purpose.

./Scripts/bootstrap.sh
02

Verify

Format lint, tests, iOS/watchOS cross-check, unsigned Apple builds, plus a separate Android test, lint, coverage, and build pipeline.

./Scripts/verify.sh
03

Snapshot

Same engine as the Apple apps. Writes a file you name — it does not open Application Support.

swift run diffuse-dev snapshot
04

Android

Independent Kotlin engine. Fixture tests decode the same golden snapshots the Swift suite pins.

cd Android && ./gradlew testDebugUnitTest

FAQ

Straight answers

Does Diffuse sync my Mac to my iPhone?

No. Each device is a closed history. Export if you need a copy — Files, AirDrop, or diffuse-dev — with a redaction policy.

Where do snapshots live?

Application Support / Diffuse/ on that device. Pretty-printed JSON plus a rebuildable index. You can open one in a text editor.

Why isn’t the Mac app sandboxed?

Developer-tool collectors spawn git and node --version. The App Sandbox forbids that without a temporary exception per binary. Documented, not papered over.

Is there telemetry?

No account, no cloud, no crash reporter that uploads snapshots. GitHub is for source and CI artifacts only.

How much does it cost?

Nothing. MIT licensed. Unsigned CI builds; you sign whatever you ship.

How often does it capture?

Default cadence is every four hours, with a 15-minute floor on every trigger including wake. Skip-if-unchanged applies to automatic captures only.

Why are widgets empty in a CI build?

Unsigned builds have no app-group container. The extension still compiles and shows empty rather than crashing.

Do tests need Node or Docker installed?

No. Collectors take a fake process runner. Live capture tests stay on cheap, stable Mac collectors. Android JVM tests read Fixtures/ directly.

Is Android a wrapper around the Apple apps?

No. It is native Kotlin and Jetpack Compose with its own domain engine. Compatibility is schema-v1 JSON and golden fixtures — not a shared runtime, JNI bridge, or Flutter shell.

Does the Android app need Internet?

No. It reads ACCESS_NETWORK_STATE so connectivity can be observed. There is no INTERNET permission, no account, and no backup of snapshots.

Where do Android snapshots live?

The app-private files directory as pretty-printed JSON. They are excluded from Android backup and device transfer. Export is a user-picked share with the same classification rules as Apple.

Your machine, observed by you.

Clone it. Capture once. Capture again. Read the diff. Nothing in this product requires permission from a server.