Arthur-Ficial/apfel
apfel 是 MIT 许可的 Swift 6.3 工具,将 Apple Silicon Mac 上的 Apple FoundationModels 暴露为 UNIX CLI 和本地 OpenAI-compatible server,支持 `apfel --chat`、tool calling、MCP、本地 4096-token context,要求 macOS 26 Tahoe+、Apple Intelligence,可用 Homebrew 安装。
apfel
Mac 上已有的免费 AI。
Apple Silicon Mac 通过 Apple FoundationModels 内置了一个 LLM。apfel 将它暴露为一个 UNIX 工具和本地 OpenAI-compatible server。100% 端侧运行。无需 API key,无需 cloud。
| 模式 | 命令 | 你会得到什么 |
|---|---|---|
| UNIX 工具 | apfel "prompt" / echo "text" | apfel |
适合 pipe 的回答、文件附件、JSON 输出、exit code |
| OpenAI-compatible server | apfel --serve |
可直接替换使用的本地 http://localhost:11434/v1 backend,适配 OpenAI SDK |
apfel --chat - 交互式 REPL。
Tool calling 可在所有上下文中使用。4096-token context。
apfel CLI
要求与安装
macOS 26 Tahoe+、Apple Silicon (M1+)、已启用 Apple Intelligence。
brew install apfel
更新:
brew upgrade apfel
从源码构建(带 macOS 26.4 SDK / Swift 6.3 的 Command Line Tools,无需 Xcode):
git clone https://github.com/Arthur-Ficial/apfel.git && cd apfel && make install
Nix、same-day tap、Mint、mise、故障排查:docs/install.md。
快速开始
UNIX 工具
在 zsh/bash 中,带 ! 的 prompt 请用单引号包裹(避免 history expansion):apfel 'Hello, Mac!'。
# Single prompt
apfel "What is the capital of Austria?"
# Permissive mode - reduces guardrail false positives for creative/long prompts
apfel --permissive "Write a dramatic opening for a thriller novel"
# Stream output
apfel --stream "Write a haiku about code"
# Pipe input
echo "Summarize: $(cat README.md)" | apfel
# Attach file content to prompt
apfel -f README.md "Summarize this project"
# Attach multiple files
apfel -f old.swift -f new.swift "What changed between these two files?"
# Combine files with piped input
git diff HEAD~1 | apfel -f CONVENTIONS.md "Review this diff against our conventions"
# JSON output for scripting
apfel -o json "Translate to German: hello" | jq .content
# System prompt
apfel -s "You are a pirate" "What is recursion?"
# System prompt from file
apfel --system-file persona.txt "Explain TCP/IP"
# Quiet mode for shell scripts
result=$(apfel -q "Capital of France? One word.")
OpenAI-compatible server
apfel --serve # foreground
brew services start apfel # background (like Ollama)
brew services stop apfel
APFEL_TOKEN=$(uuidgen) APFEL_MCP=/path/to/tools.py brew services start apfel
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"apple-foundationmodel","messages":[{"role":"user","content":"Hello"}]}'
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="unused")
resp = client.chat.completions.create(
model="apple-foundationmodel",
messages=[{"role": "user", "content": "What is 1+1?"}],
)
print(resp.choices[0].message.content)
后台服务详情:docs/background-service.md。
快速测试 chat
apfel --chat 是一个用于测试 prompt 或 MCP server 的小型 REPL。GUI chat app 见 apfel-chat。
apfel --chat
apfel --chat -s "You are a helpful coding assistant"
apfel --chat --mcp ./mcp/calculator/server.py # chat with MCP tools
apfel --chat --debug # debug output to stderr
Ctrl-C 退出。Context 会自动裁剪(docs/context-strategies.md)。
Demos
demo/ 中的 shell scripts:
cmd - 自然语言转 shell command:
demo/cmd "find all .log files modified today"
# $ find . -name "*.log" -type f -mtime -1
demo/cmd -x "show disk usage sorted by size" # -x = execute after confirm
demo/cmd -c "list open ports" # -c = copy to clipboard
Shell function 版本 - 添加到你的 .zshrc,即可在任何位置使用 cmd:
# cmd - natural language to shell command (apfel). Add to .zshrc:
cmd(){ local x c r a; while [[ $1 == -* ]]; do case $1 in -x)x=1;shift;; -c)c=1;shift;; *)break;; esac; done; r=$(apfel -q -s 'Output only a shell command.' "$*" | sed '/^```/d;/^#/d;s/\x1b\[[0-9;]*[a-zA-Z]//g;s/^[[:space:]]*//;/^$/d' | head -1); [[ $r ]] || { echo "no command generated"; return 1; }; printf '\e[32m$\e[0m %s\n' "$r"; [[ $c ]] && printf %s "$r" | pbcopy && echo "(copied)"; [[ $x ]] && { printf 'Run? [y/N] '; read -r a; [[ $a == y ]] && eval "$r"; }; return 0; }
cmd find all swift files larger than 1MB # shows: $ find . -name "*.swift" -size +1M
cmd -c show disk usage sorted by size # shows command + copies to clipboard
cmd -x what process is using port 3000 # shows command + asks to run it
cmd list all git branches merged into main
cmd count lines of code by language
oneliner - 用自然语言生成复杂 pipe 链:
demo/oneliner "sum the third column of a CSV"
# $ awk -F',' '{sum += $3} END {print sum}' file.csv
demo/oneliner "count unique IPs in access.log"
# $ awk '{print $1}' access.log | sort | uniq -c | sort -rn
mac-narrator - 你的 Mac 的内心独白:
demo/mac-narrator # one-shot: what's happening right now?
demo/mac-narrator --watch # continuous narration every 60s
demo/ 中还有:
- wtd - “这个目录是什么?”即时项目定位
- explain - 解释命令、错误或代码片段
- naming - 为函数、变量、文件提供命名建议
- port - 哪个进程在使用这个端口?
- gitsum - 总结最近的 git 活动
更长的 walkthrough:docs/demos.md。
MCP Tool 支持
使用 --mcp 连接 Model Context Protocol server。apfel 会发现、调用并返回结果。
apfel --mcp ./mcp/calculator/server.py "What is 15 times 27?"
mcp: ./mcp/calculator/server.py - add, subtract, multiply, divide, sqrt, power ← stderr
tool: multiply({"a": 15, "b": 27}) = 405 ← stderr
15 times 27 is 405. ← stdout
使用 -q 可隐藏 tool 信息。
apfel --mcp ./server_a.py --mcp ./server_b.py "Use both tools"
apfel --serve --mcp ./mcp/calculator/server.py
apfel --chat --mcp ./mcp/calculator/server.py
随附一个计算器,位于 mcp/calculator/(docs/mcp-calculator.md)。
Remote MCP servers(Streamable HTTP,MCP spec 2025-03-26):
apfel --mcp https://mcp.example.com/v1 "what tools do you have?"
# bearer token - prefer env var (flag is visible in ps aux)
APFEL_MCP_TOKEN=mytoken apfel --mcp https://mcp.example.com/v1 "..."
# mixed local + remote
apfel --mcp /path/to/local.py --mcp https://remote.example.com/v1 "..."
**安全:**优先使用
APFEL_MCP_TOKEN,不要用--mcp-token(会出现在 ps aux 中)。apfel 拒绝通过明文http://传输 bearer token。
apfel-run:可选配置层
apfel 本身没有配置文件——像任何 UNIX 工具一样使用 flags + env vars。如果你想要 TOML 配置(多个 MCP、profiles、git 中的团队配置),apfel-run 是一个 MIT wrapper,通过 execve drop-in 添加配置能力。
brew install Arthur-Ficial/tap/apfel-run
apfel-run config init # starter ~/.config/apfel/config.toml
alias apfel=apfel-run # optional, every apfel flag still works
OpenAI API 兼容性
Base URL: http://localhost:11434/v1
| Feature | Status | Notes |
|---|---|---|
POST /v1/chat/completions |
支持 | Streaming + non-streaming |
GET /v1/models |
支持 | 返回 apple-foundationmodel |
GET /health |
支持 | Model 可用性、context window、languages |
GET /v1/logs, /v1/logs/stats |
仅 debug | 需要 --debug |
| Tool calling | 支持 | Native ToolDefinition + JSON detection。见 docs/tool-calling-guide.md |
response_format: json_object |
支持 | System-prompt injection;从输出中剥离 markdown fences |
temperature, max_tokens, seed |
支持 | 映射到 GenerationOptions。省略 max_tokens 时使用剩余 context window(可直接替换 OpenAI semantics)- 见 默认响应上限 |
stream: true |
支持 | SSE;仅当 stream_options: {"include_usage": true} 时发送最终 usage chunk(按 OpenAI spec) |
finish_reason |
支持 | stop, tool_calls, length |
| Context strategies | 支持 | x_context_strategy, x_context_max_turns, x_context_output_reserve 扩展字段 |
| CORS | 支持 | 使用 --cors 启用 |
POST /v1/completions |
501 | 不支持 legacy text completions |
POST /v1/embeddings |
501 | Embeddings 在端侧不可用 |
logprobs=true, n>1, stop, presence_penalty, frequency_penalty |
400 | 明确拒绝。n=1 和 logprobs=false 会作为 no-op 接受 |
| Multi-modal (images) | 400 | 拒绝并给出清晰错误 |
Authorization header |
支持 | 设置 --token 时必须提供。见 docs/server-security.md |
完整 API spec:openai/openai-openapi。
默认响应上限(max_tokens)
省略 max_tokens 时,CLI 和 OpenAI-compatible server 行为一致:该值作为 nil 传递,model 使用 4096-token context window 中剩余的空间。这是可直接替换的 OpenAI semantics——没有任意 fallback constant。
端侧 model 有一个 4096-token context window,input 和 output 合计占用。如果生成触顶,response 会以 finish_reason: "length" 干净结束,并返回 partial content(server:HTTP 200;CLI:exit 0 并在 stderr 给出 warning)。当你希望更严格的 latency budget 或 client 需要已知上限时,请显式传入 max_tokens。
示例
# Omitted: uses remaining window, finish_reason: "stop" or "length"
curl -sS http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"apple-foundationmodel",
"messages":[{"role":"user","content":"Reply SKIP, MOVE, or RENAME."}]}'
# Explicit cap (recommended for tight latency budgets)
curl -sS http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"apple-foundationmodel","max_tokens":128,
"messages":[{"role":"user","content":"Summarise: ..."}]}'
如何选择取值
| 使用场景 | max_tokens |
|---|---|
| 单词 / 分类回复 | 16 - 32 |
| 单行指令 | 64 - 128 |
| 短段落 | 256 - 512 |
| 长段落 / 结构化 JSON | 1024 - 2048 |
| 尽可能用满 context window | 省略 |
让 input_tokens + max_tokens 舒适地低于 4096。如果 prompt 本身超过窗口,generation 无法开始,请求会以 [context overflow] 失败(HTTP 400 / CLI exit 4)。Validator 会拒绝非正值(max_tokens <= 0)。
CLI 一致性
CLI 和 server 共用一条规则:省略 = 使用剩余窗口。没有可能漂移的常量。可用 --max-tokens N 或 APFEL_MAX_TOKENS=N 覆盖。
apfel "Reply SKIP." # uses remaining window
apfel --max-tokens 64 "Reply SKIP." # explicit cap
APFEL_MAX_TOKENS=2048 apfel "..." # via env var
Server 的 permissive guardrails
apfel --serve --permissive 会让 server 对进程处理的每个请求使用 Apple 的 .permissiveContentTransformations guardrails。该 flag 与 CLI 的 --permissive 语义相同(docs/PERMISSIVE.md)。没有 per-request override——由 server operator 为整个进程决定。
apfel --serve --permissive # every request uses permissive guardrails
限制
| 约束 | 详情 |
|---|---|
| Context window | 4096 tokens(input + output 合计) |
| Platform | 仅 macOS 26+、Apple Silicon |
| Model | 单一 model(apple-foundationmodel),不可配置 |
| Guardrails | Apple 的 safety system 可能会阻止良性 prompt。--permissive 可减少 false positives(docs/PERMISSIVE.md) |
| Speed | 端侧运行,不是 cloud-scale——每次 response 需要数秒 |
| 无 embeddings / vision | 端侧不可用 |
参考文档
从 Python、Node.js、Ruby、PHP、Bash/curl、Zsh、AppleScript、Swift、Perl、AWK 使用 apfel 的指南——见 docs/guides/index.md。已通过实测;可运行的证明在 apfel-guides-lab。
- docs/install.md - 安装、故障排查和 Apple Intelligence 设置
- docs/cli-reference.md - 每个 flag、exit code 和 environment variable
- docs/background-service.md -
brew services与 launchd 用法 - docs/openai-api-compatibility.md -
/v1/*支持矩阵详解 - docs/server-security.md - origin checks、CORS、tokens 和
--footgun - docs/context-strategies.md - chat 裁剪策略
- docs/mcp-calculator.md - local 和 remote MCP 用法
- docs/tool-calling-guide.md - 详细 tool-calling 行为
- docs/integrations.md - 第三方工具集成(opencode 等)
- docs/local-setup-with-vs-code.md - 使用 apfel + VS Code 中的第二个 edit/apply model 进行本地 review
- docs/demos.md - shell demos 的更长 walkthrough
- docs/EXAMPLES.md - 50+ 个真实 prompt 及未编辑输出
- docs/swift-library.md - 面向下游开发者的
ApfelCoreSwift Package
架构
CLI (single/stream/chat) ──┐
├─→ FoundationModels.SystemLanguageModel
HTTP Server (/v1/*) ───────┘ (100% on-device, zero network)
ContextManager → Transcript API
SchemaConverter → native ToolDefinitions
TokenCounter → real token counts (SDK 26.4)
Swift 6.3 strict concurrency。三个 target:ApfelCore(纯逻辑、可做 unit test,也作为 Swift Package product 提供——见 docs/swift-library.md)、apfel(CLI + server)和 apfel-tests(纯 Swift runner,不使用 XCTest)。
构建与测试
make test # release build + all unit/integration tests
make preflight # full release qualification
make install # build release + install to /usr/local/bin
make build # build release only
make version # print current version
make release # patch release
make release TYPE=minor # minor release
make release TYPE=major # major release
swift build # quick debug build (no version bump)
swift run apfel-tests # unit tests
python3 -m pytest Tests/integration/ -v # integration tests
apfel --benchmark -o json # performance report
.version 是唯一事实来源。只有 make release 会提升版本。Local build 不会改变版本。
apfel tree
基于 apfel 构建的项目。每个项目都有自己的 repo + Homebrew formula。
| Project | 作用 | 安装 |
|---|---|---|
| apfel | 根项目。端侧 FoundationModels CLI + OpenAI-compatible server。 | brew install apfel |
| apfel-chat | macOS chat client:streaming markdown、speech I/O、Apple Vision image analysis。 | brew install Arthur-Ficial/tap/apfel-chat |
| apfel-clip | 菜单栏剪贴板 AI actions:summarize、translate、rewrite。 | brew install Arthur-Ficial/tap/apfel-clip |
| apfel-quick | 即时 AI overlay:按下按键、提问、获得回答、关闭。 | brew install Arthur-Ficial/tap/apfel-quick |
| apfelpad | Formula notepad——把端侧 AI 作为 inline cell function。 | brew install Arthur-Ficial/tap/apfelpad |
| apfel-mcp | 为 4096 window 做 token budget 优化的 MCP:url-fetch、ddg-search、search-and-fetch。 |
brew install Arthur-Ficial/tap/apfel-mcp |
| apfel-gui | SwiftUI debug inspector:request timeline、MCP protocol viewer、TTS/STT。 | brew install Arthur-Ficial/tap/apfel-gui |
| apfel-run | UNIX wrapper,在 apfel 之上添加持久 MCP registry + TOML config。 |
brew install Arthur-Ficial/tap/apfel-run |
| apfel-server-kit | 面向生态工具的 Swift package:发现、启动本地 apfel --serve,并从中 stream。 |
Swift Package |
社区项目
基于 apfel 做了东西?开一个 issue,可以添加到这里。
| Project | 作用 | Links |
|---|---|---|
| apfelclaw by @julianYaman | 本地 AI agent,通过只读工具读取文件、日历、邮件和 Mac 状态 | github - site |
| fruit-chat by @bhaskarvilles | 基于浏览器的 chat UI,通过 OpenAI-compatible API 与 apfel --serve 通信 |
github |
| local-claude by @lucaspwo | Claude Code wrapper,通过一个小型 Anthropic-OpenAI proxy 将 apfel 替换为本地 backend | github |
| apfeller by @hasit | 围绕 apfel 构建的本地 shell apps 管理器 | github - site - catalog |
贡献
欢迎在任何 Arthur-Ficial/apfel* repo 提 issue 和 PR。
#agentswelcome - 接受 AI agent PR。请阅读 repo 的 CLAUDE.md,运行测试,并在 Co-Authored-By trailer 中标注所用工具。标准与人类相同:代码干净、测试通过、如实说明限制。最适合 agent 上手的入口:apfel-mcp(贡献规则)。
License
MIT