Apple's fm CLI: Foundation Models in the macOS Terminal, with 14 One-Liners
macOS 27 ships a language model in /usr/bin. Here is what it does well, where it breaks, and the commands I actually use.
What fm is
fm is a command-line tool that Apple ships with macOS 27. It gives the terminal direct access to Apple's Foundation Models: the on-device language model that also powers Apple Intelligence, and optionally the larger model on Private Cloud Compute. It lives at /usr/bin/fm, needs no install, no API key and no account, and costs nothing per request.
In practice it is three things in one binary:
- a pipe-friendly prompt tool (
fm respond), so you can put a language model between two Unix commands, - an interactive chat (
fm chat) with saved sessions, - a local server (
fm serve) that speaks the OpenAI-style Chat Completions API, so existing tools and scripts can talk to the on-device model without knowing it is Apple's.
The man page says it plainly: by default every command uses the on-device system model. Your prompt does not leave the machine unless you ask for --model pcc.
Everything below was run on my own Mac on macOS 27.2 (build 26B5101f). Where something failed, I say so.
Which macOS versions have fm
| Status | |
|---|---|
| macOS 27 | Included in /usr/bin/fm, man page fm(1) dated June 8, 2026. Tested here on 27.2 |
| macOS 26 and earlier | Not available. The Foundation Models framework exists on 26, the CLI does not |
| Intel Macs | Not supported. The on-device model needs Apple silicon |
| iOS, iPadOS, visionOS | No. It is a macOS command-line tool |
So the real requirements are: macOS 27, an Apple silicon Mac, Apple Intelligence enabled in System Settings, and a one-time license agreement. fm models tells you in one line whether you are good to go. On my machine it reports the on-device model as AFM 3 Core.
One thing to know before your first run: fm refuses to do anything until the Legal Notice and Terms are accepted, and the agreement applies to every user on the Mac, so it needs admin rights:
sudo fm licenseAnswer yes. Any other answer exits with status 69. fm license --status shows whether the machine has agreed, fm license --show prints the text without the prompt.
All fm commands at a glance
| Command | What it does |
|---|---|
fm respond |
Single prompt in, response out. Reads stdin, takes images, supports structured output via JSON schema |
fm chat |
Interactive chat. Sessions are saved in ~/.fm/sessions/, resume with --resume <name> or --continue |
fm count-tokens |
Counts tokens of a prompt, instructions, images or a saved transcript. Prints a bare integer when piped |
fm schema object |
Generates a JSON generation schema from flags (--string, --integer, --double, --boolean, --object, --anyOf, --array, --optional) |
fm serve |
Starts a Chat Completions API server: GET /health, GET /v1/models, POST /v1/chat/completions. TCP or Unix socket |
fm models |
Shows which models are available, and why not if they are not |
fm quota-usage |
Shows the Private Cloud Compute quota. The on-device model has no quota |
fm config |
Reads and writes ~/.fm/config.json: default model, default model provider, named providers |
fm license |
Shows and accepts the license. The only command that is never gated |
The models you can pick with -m / --model:
| Model | Where it runs |
|---|---|
system |
On-device Apple Foundation Model (default) |
pcc |
Apple Foundation Model on Private Cloud Compute, subject to a quota |
<model> |
Any model behind a Chat Completions endpoint you register with --model-provider |
The options of fm respond worth knowing:
| Option | Meaning |
|---|---|
-i, --instructions |
System instructions, separate from the prompt |
--image <path>, --text <t>, --label |
Multimodal input. --label names the image it is paired with |
--tool barcode, --tool ocr |
Built-in tools the model can call. Repeatable |
--schema <file or json> |
Structured output. Accepts a file path or an inline JSON string |
--save-transcript <f>, --resume <f> |
Persist a conversation and continue it in a later call |
--no-stream |
Wait for the full answer instead of streaming tokens |
-g, --greedy |
Greedy sampling, for repeatable output |
--use-case content-tagging |
Switches the on-device model to its tagging adapter |
--guardrails permissive-content-transformations |
Looser guardrails for rewriting text you supply |
14 one-liners to copy into your Terminal
Every command here was run on macOS 27.2. Outputs are what my Mac returned, they will vary on yours. Image paths are placeholders, swap in your own file.
1. Ask a quick question. The baseline. On my Mac this took 4.418 seconds wall clock, model load included.
fm respond 'Explain the difference between TCP and UDP in two sentences.'2. Translate whatever is in the clipboard, back into the clipboard.
pbpaste | fm respond --no-stream -i 'Translate to German. Output only the translation.' | pbcopy3. Draft a commit message from your staged changes.
git diff --staged | fm respond -i 'Write a one-line commit message for this change. Output only the message.'Keep the diff small. A large diff runs into the context limit (see the weaknesses below).
4. Ask a man page instead of reading it. man tar comes out at 10,775 tokens, which still fit. The answer was tar -xzf yourfile.tar.gz -C /path/to/folder.
man tar | col -b | fm respond 'How do I extract a .tar.gz into a specific folder? One command.'5. Ask a question about an image. My test receipt returned 6.30 EUR.
fm respond --image ~/Desktop/receipt.png --text 'What is the total amount?'6. Let the model use OCR on a screenshot or scan.
fm respond --tool ocr --image ~/Desktop/receipt.png 'List every line item with its price.'7. Read a QR code or barcode from an image.
fm respond --tool barcode --image ~/Desktop/qr.png 'What does this code contain?'8. Get tags as clean JSON. Structured output is where fm gets interesting for scripts: the output is valid JSON matching the schema, ready for jq. Result: {"tags": ["swift concurrency", "actors", "async/await", "macOS"]}.
fm respond --no-stream --schema "$(fm schema object --name Tags --string tags --array)" 'Swift concurrency with actors and async/await on macOS'9. Turn messy text into structured records. A nested schema: an object with an array of orders. Both orders came back correctly, IDs 4711 and 4712 included.
printf 'Order 4711 from Fresh Market, 12 units, due 2026-11-03\nOrder 4712 from Beanery, 3 units, due 2026-11-05\n' | fm respond --no-stream --schema "$(fm schema object --name Orders --object orders --schema "$(fm schema object --name Order --integer id --string customer --integer units --string due)" --array)"Note the order of flags: --object orders --schema ... --array. Put --array before --schema and you get --object 'orders' must be followed by --schema.
10. Use the tagging adapter. Returns short keywords. On my German-locale Mac it answered in German (Kaffeemaschine, Grinder, App, Produkt), even for English input.
fm respond --use-case content-tagging 'Our new espresso machine ships with a built-in grinder and app control.'11. Check whether a file fits into the model before you send it. Piped, count-tokens prints a bare integer. This project's CLAUDE.md is 9,961 tokens.
fm count-tokens -q < README.md12. Continue a conversation across separate calls. The transcript is a JSON file, so a shell script can carry context from one step to the next.
fm respond --save-transcript notes.json 'My name is Marc.' && fm respond --resume notes.json 'What is my name?'13. Have your Mac read the answer out loud.
say "$(fm respond --no-stream 'Write a haiku about Monday mornings.')"14. Run a local OpenAI-compatible endpoint and call it with curl.
fm serve --port 1976 & sleep 2; curl -s localhost:1976/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"system","messages":[{"role":"user","content":"Name two Unix pipes tricks."}]}'The server listens on 127.0.0.1 only unless you pass --host, and logs each request with token counts (61→169 tokens, 4.9 s for this one). Stop it with kill %1. Any client that lets you set a base URL (http://localhost:1976/v1) can now use the on-device model. For local Python bindings the help text recommends a Unix socket instead: fm serve --socket /tmp/fm.sock.
Two more that are not one-liners in the pipe sense but belong in your muscle memory: fm chat --instructions 'You are a terse senior Unix admin' for an interactive session, and fm models plus fm quota-usage to see what is available right now.
Strengths and weaknesses
| Strengths | Weaknesses |
|---|---|
| Ships with macOS 27. No install, no Homebrew, no API key, no account | macOS 27 and Apple silicon only. Not on macOS 26, not on Intel, not on Linux CI runners |
| On-device by default. Prompts, files and images stay on the Mac | Small model. Fine for summarizing, rewriting and extracting, weak at facts. In one-liner 1 it claimed TCP uses ports for addressing and UDP does not. Both do |
| No per-token cost, works offline | Small context window. A 10,775-token man page went through, an 11,781-token file failed with The session's transcript exceeded the model's context size |
| Real Unix citizen: stdin, stdout, bare integers when piped, non-zero exit code on errors | Guardrails trigger on harmless technical words. Asking for three Unix signals stopped after SIGINT and SIGTERM with The model's safety guardrails were triggered, presumably because the third one was SIGKILL |
Structured output via JSON schema, generated by fm schema without hand-writing JSON |
Errors arrive mid-stream. Partial text has already been printed when the guardrail fires, so check the exit code (1), not just the output |
| Images, OCR and barcode tools built in | Large inputs are slow. Prompts around 12,000 tokens took well over a minute before failing |
fm serve gives you an OpenAI-compatible API in one command, bound to loopback only by default |
fm serve streamed its answer even when the request did not ask for streaming. Clients expecting a single JSON body need to handle server-sent events |
| Same CLI can route to Private Cloud Compute or any Chat Completions provider | Private Cloud Compute is quota-bound and refused to run outside Terminal.app: from my automation context it returned Private Cloud Compute is not available in this context. Please use the Terminal app. |
| Saved sessions and transcripts make multi-step shell scripts possible | Model choice is opaque. You get AFM 3 Core or whatever your Mac runs, with no flag to pick a variant |
Frequently asked questions
What is the fm command on macOS?
fm is Apple's command-line interface for Foundation Models, included in macOS 27 at /usr/bin/fm. It sends prompts to Apple's on-device language model (or to Private Cloud Compute with --model pcc), counts tokens, generates JSON schemas for structured output, and can run a local Chat Completions API server with fm serve.
Which macOS version do I need for fm?
macOS 27. The tool is not part of macOS 26 or earlier, even though the Foundation Models framework already exists on macOS 26. You also need an Apple silicon Mac with Apple Intelligence enabled.
Is fm free, and does it need an API key?
Yes, it is free, and no, it needs no API key or account. The on-device model has no quota and no per-token cost. Only the Private Cloud Compute model (--model pcc) is subject to a quota, which fm quota-usage shows.
Does fm send my data to Apple?
Not by default. Every command uses the on-device system model unless you pass --model pcc, which sends the request to Apple's Private Cloud Compute. Without that flag, prompts, piped files and images are processed on the Mac.
Why does fm say I have to agree to a license?
fm is gated behind the Foundation Models CLI Legal Notice and Terms. Run sudo fm license once and answer yes. The agreement applies to all users on the Mac, which is why it needs admin rights. Any other answer exits with status 69.
How large can the input to fm be?
Smaller than you might hope. On macOS 27.2 a 10,775-token input worked and an 11,781-token input failed with "The session's transcript exceeded the model's context size". Check with fm count-tokens -q < file before sending large files.
Can I use fm with tools that expect the OpenAI API?
Yes. fm serve --port 1976 starts a server with GET /v1/models and POST /v1/chat/completions. Point a client at http://localhost:1976/v1 and use system as the model name. Expect streamed responses.
How do I get JSON output from fm?
Generate a schema with fm schema object, for example fm schema object --name Tags --string tags --array, and pass it inline to fm respond --schema "$(...)". The response is JSON that matches the schema, which you can pipe straight into jq.
Why did fm stop in the middle of an answer?
Most likely the safety guardrails fired. They trigger mid-stream, so part of the answer is already printed. The command then exits with status 1. For rewriting text you supply yourself, --guardrails permissive-content-transformations loosens them.
Where this lands in my own work
A language model in /usr/bin changes who can use one. Not the developer with an API key and a billing alert, but anyone with a Mac and a shell script: the support engineer who wants log lines tagged, the ops person who wants an order email turned into JSON, the analyst who wants a CSV column cleaned up before it goes into a report.
And it gets the direction right. The data does not travel to a model in someone else's data center. The model is already on the machine where the data is. That is the same idea behind ColumnLens and IoT Data Flow: the software comes to your data. fm is a small, sometimes clumsy model, but it is the first time Apple has made that idea available as a Unix command, and for extraction, tagging and rewriting on local files, small and local is often exactly enough.