How to use the Apple Foundation Model from Zsh
April 15, 2026 ยท View on GitHub
Call Apple's on-device Foundation Model from Zsh - the default shell on modern macOS. Zsh's parameter expansion, associative arrays, and print -r make raw HTTP calls more concise than the Bash equivalent.
Runnable scripts + tests: Arthur-Ficial/apfel-guides-lab/scripts/zsh.
Prerequisites
- macOS 26+ Tahoe (Zsh 5.9+ ships with the OS)
brew install apfel jqapfel --serverunning (port11434)
1. One-shot
#!/bin/zsh
emulate -L zsh
setopt err_exit pipe_fail no_unset
local -A req=(
model "apple-foundationmodel"
prompt "In one sentence, what is the Swift programming language?"
)
local payload="$(jq -cn --arg m "$req[model]" --arg p "$req[prompt]" \
'{model:$m, messages:[{role:"user", content:$p}], max_tokens:80}')"
curl -sS http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" -d "$payload" \
| jq -r '.choices[0].message.content'
Real output:
Swift is a modern, high-performance programming language developed by Apple for developing apps and systems on iOS, macOS, watchOS, and tvOS.
Lab script: 01_oneshot.zsh.
2. Streaming
#!/bin/zsh
emulate -L zsh
setopt err_exit pipe_fail no_unset no_xtrace no_verbose
curl -sS -N http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"apple-foundationmodel","messages":[{"role":"user","content":"List three Apple silicon chips, one per line."}],"max_tokens":80,"stream":true}' \
| while IFS= read -r line; do
line=${line#data: }
[[ -z $line || $line == "[DONE]" ]] && continue
piece=$(print -r -- "$line" | jq -r '.choices[0].delta.content // empty' 2>/dev/null) || piece=
[[ -n $piece ]] && print -rn -- "$piece"
done
print
Real output:
Apple M1
Apple M2
Apple M2 Pro
Lab script: 02_stream.zsh.
3. JSON mode
Zsh parameter expansion strips markdown fences without calling sed:
#!/bin/zsh
emulate -L zsh
setopt err_exit pipe_fail no_unset
local raw
raw=$(curl -sS http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model":"apple-foundationmodel",
"messages":[{"role":"user","content":"Return JSON with fields chip, year, cores. Describe the Apple M1 chip. Return ONLY JSON."}],
"response_format":{"type":"json_object"},
"max_tokens":120
}' | jq -r '.choices[0].message.content')
raw=${raw#\`\`\`json}
raw=${raw#\`\`\`}
raw=${raw%\`\`\`}
raw=${raw//$'\r'/}
print -r -- "$raw" | jq '.'
Real output:
{
"chip": "Apple M1",
"year": 2020,
"cores": {
"CPU": 8,
"GPU": 8
}
}
Lab script: 03_json.zsh.
4. Error handling
#!/bin/zsh
emulate -L zsh
setopt err_exit pipe_fail no_unset
local tmp=$(mktemp)
local http_status
http_status=$(curl -sS -o "$tmp" -w '%{http_code}' \
http://localhost:11434/v1/embeddings \
-H "Content-Type: application/json" \
-d '{"model":"apple-foundationmodel","input":"apfel runs 100% on-device."}')
if (( http_status >= 400 )); then
local msg=$(jq -r '.error.message // empty' "$tmp" 2>/dev/null) || true
print -r -- "Got expected error: HTTP ${http_status} - ${msg:-see response}"
fi
rm -f "$tmp"
Real output:
Got expected error: HTTP 501 - Embeddings not supported by Apple's on-device model.
Lab script: 04_errors.zsh.
5. Tool calling
#!/bin/zsh
emulate -L zsh
setopt err_exit pipe_fail no_unset
local tools='[{
"type":"function",
"function":{
"name":"get_weather",
"description":"Get the current temperature in Celsius for a city.",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}
}
}]'
local first
first=$(jq -n --argjson tools "$tools" '{
model:"apple-foundationmodel",
messages:[{role:"user", content:"What is the temperature in Vienna right now?"}],
tools:$tools,
max_tokens:256
}' | curl -sS http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" -d @-)
local msg=$(jq -c '.choices[0].message' <<<"$first")
local call=$(jq -c '.tool_calls[0]' <<<"$msg")
local city=$(jq -r '.function.arguments | fromjson | .city' <<<"$call")
local -A fake=(Vienna 14 Cupertino 19 Tokyo 11)
local temp=${fake[$city]:-15}
local tool_result=$(jq -cn --arg c "$city" --argjson t "$temp" '{city:$c, temp_c:$t}')
local tool_msg=$(jq -cn --arg id "$(jq -r '.id' <<<"$call")" --arg content "$tool_result" \
'{role:"tool", tool_call_id:$id, content:$content}')
local final_payload=$(jq -n --argjson msg "$msg" --argjson tool "$tool_msg" '{
model:"apple-foundationmodel",
messages:[{role:"user", content:"What is the temperature in Vienna right now?"}, $msg, $tool],
max_tokens:120
}')
curl -sS http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" -d "$final_payload" \
| jq -r '.choices[0].message.content'
Real output:
The current temperature in Vienna is 14 degrees Celsius.
Lab script: 05_tools.zsh.
6. Real example - summarize stdin
#!/bin/zsh
emulate -L zsh
setopt err_exit pipe_fail no_unset
local text=$(cat)
[[ -z $text ]] && { print -u 2 -- "usage: cat file.txt | zsh 06_example.zsh"; exit 1 }
local payload=$(jq -n --arg text "$text" '{
model:"apple-foundationmodel",
messages:[
{role:"system", content:"You are a concise summarizer. Reply with one short paragraph."},
{role:"user", content: ("Summarize:\n\n" + $text)}
],
max_tokens:150
}')
curl -sS http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" -d "$payload" \
| jq -r '.choices[0].message.content'
Real output:
The Apple M1 chip, released in 2020, was Apple's first ARM-based system-on-a-chip for Mac computers. It features an 8-core CPU with four performance and four efficiency cores, plus an integrated GPU with up to 8 cores, providing significant performance-per-watt improvements over Intel chips.
Lab script: 06_example.zsh.
Troubleshooting
local pieceprints the assignment - Zsh prints declarations when used outside functions withno_unset. Drop thelocalor wrap the block in a function. The streaming script above shows the clean pattern.- Scripts using Bash heredocs don't work - Zsh's quoting rules differ slightly. The scripts above use single-quoted payloads or
jq -n --argto sidestep it.
Tested with
- apfel v1.0.3 / macOS 26.3.1 Apple Silicon
- zsh 5.9 (system) / jq 1.7
- Date: 2026-04-16
Runnable tests: tests/test_zsh.py.
See also
bash-curl.md, applescript.md, swift-scripting.md, apfel-guides-lab