The fm command ships with macOS 27: on-device Apple models from the shell, JSON output and a local OpenAI-compatible server. Five recipes, one failure table.
Your Mac now has a free, offline language model you can pipe text through, and most developers have not typed the two letters yet. The fm command ships preinstalled with macOS 27 as a terminal front end to Apple's Foundation Models: the same on-device small model that powers Apple Intelligence, with an opt-in path to the larger Private Cloud Compute model. It needs no account, no API key and charges nothing per token. Enable it once with sudo fm license, then fm chat gives you a conversation with slash commands and fm respond writes a single answer to stdout, which is the mode that matters for scripts. It also speaks structured JSON, keeps transcripts, reports token counts and can run an OpenAI-compatible REST server on localhost.
Short answer: fm is the right tool for small, private, zero-cost text transforms inside shell pipelines: summarising a diff, classifying a log line, drafting a commit message, extracting fields to JSON. It is the wrong tool for long documents, code generation at scale and anything that needs a frontier model's judgement. Below are the five recipes we kept after a week, the failure table, and the decision rule between fm, mlx_lm and Ollama.
Setup in ninety seconds
# one-time
sudo fm license
# sanity check
fm respond "Reply with the single word READY."
# structured output
fm respond --json '{"type":"object","properties":{"lang":{"type":"string"},"score":{"type":"number"}}}' "Detect the language of: Bonjour tout le monde"
# local OpenAI-compatible server for tools that expect one
fm serve --port 8734Flag names follow the WWDC26 session "Build AI-powered scripts with the fm CLI and Python SDK." If a flag differs on your build, fm --help is authoritative; Apple has iterated the surface since the beta and third-party write-ups lag it.
Five recipes worth keeping
1. Commit message from a staged diff
gitmsg() {
git diff --cached | head -c 12000 | fm respond "Write a conventional commit message for this diff. First line under 72 chars, imperative mood, then a blank line and up to 4 bullet points. Output only the message."
}The head -c matters. The on-device context is small; feed it the first 12 KB and let it summarise, or it will truncate silently and describe half the change.
2. Log triage to JSON
tail -n 200 /var/log/app.log | fm respond --json '{"type":"object","properties":{"errors":{"type":"integer"},"top_causes":{"type":"array","items":{"type":"string"}},"needs_human":{"type":"boolean"}}}' "Classify these log lines. Count ERROR lines, list up to 3 distinct causes, set needs_human if any cause mentions data loss or auth."Structured output is where fm earns its place. A JSON schema turns a chatty model into a function you can jq.
3. Rename files by content
for f in ~/Downloads/*.pdf; do
name=$(pdftotext -l 1 "$f" - 2>/dev/null | head -c 4000 | fm respond "Give a 6-word kebab-case filename for this document. Output only the filename, no extension.")
[ -n "$name" ] && mv "$f" ~/Downloads/"$name".pdf
done4. Meeting notes to actions
pbpaste | fm respond "Extract action items as a markdown checklist. Each item: owner in bold, task, due date if stated. Nothing else." | pbcopyClipboard in, clipboard out. This is the recipe that gets used ten times a day once it exists.
5. Pre-filter before an expensive model
is_worth_reading() {
fm respond --json '{"type":"object","properties":{"relevant":{"type":"boolean"}}}' "Is this article about $1? Answer strictly." <<< "$(cat)" | jq -r .relevant
}
# use: curl -s "$url" | html2text | is_worth_reading "Next.js caching"The pattern that saves the most money: let the free local model say no, and send only the yeses to the model you pay for. Our token counter and token estimator will show you how much of a paid budget a filter like this protects.
Comments · 0
Beta: comments are stored locally on your device and not visible to other readers.
No comments yet. Be the first to share your thoughts.