⚠ apfelpad is experimental — v0.3.x, things change fast, expect rough edges. File an issue if something breaks.
macOS formula notepad

Like =SUM
but for language.

🔒 100% on-device. Nothing leaves your Mac.

Type =apfel("summarize this") in your markdown. Watch it stream inline. No API keys. No cloud.

MIT Open Source macOS 26 Tahoe Apple Silicon
apfelpad rendering the big sheet — every formula type evaluating inline in pale-green spans: =math with US annotation, =date, =cw, =upper, =lower, =concat, =replace, =split, =sum, =avg, =if, =ref, and nested composition
See it in action

Write formulas. See results inline.

Rendered view
apfelpad rendered view with inline math and AI formula results

=math(365*24) becomes 8760. =apfel() calls stream AI text. Both render as green spans.

Source view
apfelpad source view showing raw formula syntax in markdown

Toggle to see raw markdown. Formula syntax is plain text. Open in any editor.

How it works

Three steps. No setup.

1

Write markdown

Headings, bold, lists — it all renders inline.

2

Drop in a formula

=apfel(summarize this) or =math(365*24) anywhere in your text.

3

See results inline

Pale green spans. Cached. Reproducible via seeds.

The formulas

Twenty-one functions. One syntax. Turing-complete. Reactive.

Every formula is pure. Every result is cached. Type =name(args) anywhere. Smart quotes, anonymous shortcuts, and auto-quoting all work. Formulas compose.

On-device AI + math

=apfel(prompt, seed?)
On-device LLM call via apfel --serve. Seed makes output reproducible.
=(prompt, seed?)
Anonymous shortcut — =(hello) canonicalises to =apfel("hello").
=math(expression)
Arithmetic with US annotation — =math($1,250 + $750), =math(2m + 500k).

Dates and time

=date(offset?)
ISO 8601. =date(+4) four days ahead, =date(-7) a week ago.
=weeknum(offset?)
ISO calendar week. =weeknum(-1) last week, =weeknum(+2) two ahead.
=month() / =day() / =time()
Locale-aware month name, weekday name, and HH:mm time.

Text formulas (Google Sheets–style, pure Swift)

=upper(text)
Uppercase. =upper("hello")HELLO
=lower(text)
Lowercase. =lower("HELLO")hello
=trim(text)
Strip leading / trailing whitespace.
=len(text)
Grapheme count — =len("🎉")1
=concatenate(a, b, …)
Variadic string join.
=substitute(t, f, r)
First-occurrence substitution.
=split(t, d, i?)
Return the i-th piece (default 0).
=if(cond, then, else)
Branching — empty / 0 / false / no are falsy.
=sum(n1, n2, …)
Variadic numeric sum.
=average(n1, n2, …)
Arithmetic mean.

Document references and reactive variables

=ref(@#anchor)
Insert the text of a named heading section. @# = section reference.
=count(@#anchor?)
Word count of the whole document or a named section.
=input(name, type, default?)
Declare a reactive variable. Use @name elsewhere to bind its value.
=show(@name)
Echo the current value of a bound variable.
=clip()
Current clipboard contents (text only).
=file(path)
Read a local text file (max 1 MB).
Document references

=ref pulls content from anywhere in the doc.

Name a heading, then quote its section with =ref(@#name). When you edit the source heading, every =ref that points at it updates automatically. Use @# for section references, @ for input variables.

Source

# Project brief

Build a formula notepad for thinking on macOS.
On-device AI as a first-class function.
Every span is cached. Every span is reproducible.

# Key numbers

Target launch: v1.0 in 12 weeks.
That's =math(12*7) days to ship.
Or =math(12*5) business days.
Budget: =math(40*12*150) dollars.

# Summary

Goal: =ref(@#project-brief)

Timeline: =ref(@#key-numbers)

Rendered

apfelpad rendering the =ref demo document — two =ref spans resolving to named heading sections, three =math spans resolving to numbers

Anchor names are slugified: # Project brief becomes @project-brief. Case-insensitive. Subsections scope correctly to the next heading of equal or higher level.

Composition

Formulas nest. apfelpad is Turing-complete.

Every formula can take another formula as an argument. The resolver walks the source bottom-up, substitutes each sub-call's evaluated result as a quoted literal, then runs the outer call. Combined with =if branching and =ref document lookup, this is enough to express any computable function.

Simple composition

=upper(=ref(@#intro))
  → upper + ref
  → HELLO WORLD

=upper(=trim(=lower("   HELLO   ")))
  → three levels deep
  → HELLO

=concatenate(=upper("a"), "-", =lower("B"))
  → siblings
  → A-b

Math + branching + refs

=if(=math(5*5), "big", "small")
  → 25 is truthy
  → big

=sum(=len("abc"), =len("de"), =math(10))
  → 3 + 2 + 10
  → 15

=apfel(=concatenate("summarize: ", =ref(@#intro)))
  → AI reads the intro section

Depth is capped at 10 levels so pathological nesting always terminates. Invalid sub-calls are left in place and surfaced as errors in the outer parse.

The big sheet

Every formula, one document.

This is a regression fixture that ships with apfelpad. Every pale-green span is a live formula — math with US annotation, dates, weekdays, text transforms, aggregates, document references, nested composition. The screenshot is window-only and captured directly from the running .app by scripts/screenshot-big-sheet.sh.

apfelpad rendering the big sheet — every formula category evaluating inline: arithmetic with US annotation, date offsets, text transforms, aggregates, composition, references

Source: Tests/Fixtures/20-the-big-sheet.md · Full reference: docs/formulas.md

Reactive variables (v0.5)

The document IS the app.

=input(name, type) declares a document-level variable. =show(@name) echoes it. Any formula that references @name recomputes in real time when the value changes. Spreadsheet semantics, inside prose.

apfelpad calculator example with reactive input variables and live math formulas

Source

# Freelance Project Calculator

=input("hours", number, "120")
=input("rate", number, "95")
=input("tax_rate", number, "20")
=input("discount", number, "0")

## Project Estimate

| Item | Value |
|------|-------|
| Hours | =show(@hours) |
| Subtotal | =concatenate("$", =math(@hours * @rate)) |
| Discount | =concatenate(@discount, "%") |
| After discount | =concatenate("$", =math(@hours * @rate * (100 - @discount) / 100)) |
| Tax | =concatenate("$", =math(@hours * @rate * @tax_rate / 100)) |
| **Total** | =concatenate("$", =math(@hours * @rate * (100 + @tax_rate) / 100)) |

## Document stats

This document has =count() words.

What it does

  • Each =input span renders as an inline form field (text, number, boolean, or date)
  • Typing a value into the hours input instantly re-evaluates Subtotal, Tax, Total, and =show echoes
  • @name = input variable, @#section = heading reference — clean separation
  • =count() counts words in the whole document; =count(@#section) counts a specific section
  • =clip() snapshots the clipboard; =file(path) reads a local text file
  • Dual editing: Render mode (evaluated formulas inline) and Source mode (raw text)
  • The document is plain markdown. Save it, send it, open it in apfelpad. No cloud, no sign-in.

Example: Examples/Calculator.md

Install

Get apfelpad

Free. Signed and notarised. Apple Silicon only.

Download

Unzip, drag to Applications, open.

Download free (arm64)

Signed and notarised. SHA-256 in each release.

After installing

  1. Open apfelpad from Applications
  2. A welcome document opens with examples
  3. Click a green span to edit its formula
  4. Type =math(2+2) and press Return
Other install options

Homebrew

brew install Arthur-Ficial/tap/apfelpad

Updates with brew upgrade apfelpad.

Curl installer

curl -fsSL https://raw.githubusercontent.com/Arthur-Ficial/apfelpad/main/scripts/install.sh | zsh

Build from source

git clone https://github.com/Arthur-Ficial/apfelpad.git
cd apfelpad && make install
Features

A spreadsheet for prose.

Inline rendering

Results appear right where you type. No output pane.

100% on-device AI

No API keys. No cloud. Nothing leaves your Mac.

Deterministic seeds

=apfel("prompt", 42) gives the same output every time.

Cached results

Re-opening shows results instantly. Change the prompt to re-evaluate.

Auto-quoting

Type =apfel(hello world). The parser adds the quotes.

Plain markdown

.md files on disk. Open in any editor.

Under the hood

Powered by apfel.

apfelpad launches apfel (an OpenAI-compatible server for on-device Foundation Models) on localhost:11450 at startup. All AI inference stays on your Mac. apfelpad handles formula parsing, caching, context resolution, and inline rendering on top.

Requirements

What you need

macOS 26 (Tahoe)
Apple Silicon (M1+)
Apple Intelligence enabled
apfel on PATH

=math() works without AI. Only =apfel() formulas need the on-device model.

Privacy

One network call. Not for inference.

Optional update check via api.github.com (togglable in settings). Every AI call goes to localhost. No telemetry. No accounts. No cloud inference. Ever.

Free and open source.

MIT-licensed. Use it, fork it, ship it.