Proof-of-concept CLI tools that demonstrate Apple's Foundation Models framework for local, privacy-first AI.
This is a POC - not production software. It's meant to prove AFM is viable for CLI tools and inspire further development.
- Mission: Make Apple's on-device AI accessible via open source CLI/TUI tools
- Current focus: Integrate AFM with OpenCode as local AI provider
- Stack: OpenCode with
@ai-sdk/openai-compatible+ Swift AFM helper
LocalCode/
├── LocalCode/Sources/afmhelper/ # Swift AFM helper
│ └── main.swift # Apple FoundationModels integration
├── start-afm-server.sh # HTTP middleware (Bun)
└── pre-commit.sh # Pre-commit hook
- AFM Server: Bun HTTP server wrapping Swift AFM helper with OpenAI-compatible API
- AI Layer: Apple FoundationModels framework (Swift)
- TUI Layer: OpenCode with
@ai-sdk/openai-compatibleprovider - Command Flow: You type → AFM suggests (tool call) → OpenCode approval UI → Command executes
No fork needed - use OpenCode's provider config with @ai-sdk/openai-compatible:
- Add AFM provider to
~/.config/opencode/opencode.json - AFM server wraps Swift helper with OpenAI-compatible API
- OpenCode sees AFM as a standard provider
OpenCode repo: https://github.com/anomalyco/opencode
# Build Swift AFM helper
cd LocalCode/Sources/afmhelper
swiftc -o afmhelper main.swift -framework FoundationModels -target arm64-apple-macosx26.0The project includes a pre-commit hook that builds the Swift helper and TypeScript TUI.
To enable:
cp pre-commit.sh .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
git config core.hooksPath .git/hooks- Apple Silicon Mac (M1/M2/M3/M4)
- macOS 26+
- Xcode 26+
- On-device, privacy-first AI (no data leaves device)
- Access via
SystemLanguageModel.default - Sessions (
LanguageModelSession) maintain conversation history - Structured output via
@Generablemacro - Tool calling via the
Toolprotocol
- TypeScript for TUI, Swift for AFM integration
- Async/await for all model interactions
- OpenTUI InputRenderable.ENTER event: The
input.on(InputRenderableEvents.ENTER, ...)callback may not fire reliably in some terminal configurations. Consider using globalrenderer.keyInput.on("keypress", ...)as a workaround.
When wrapping up a session (end of day, user leaving, or session is pausing), write a session note:
sessions/YYYY-MM-DD_HHMM.mdContent should include:
- What's working / recent progress
- Test commands that work
- Known limitations or bugs
- Next steps or things to improve
Commit session notes so they can be reviewed later.
This is a public open source project. Before every commit:
- Update README.md if you added new features, changed architecture, or modified the workflow
- Check examples still work - commands in README should be tested
- Verify docs match code - if you changed how something works, update the docs
README is often the first thing new users see. Outdated docs = bad first impression.