Install Mimi Remote
gaixianggeng/mimi-remote
安装、配置、配对、迁移、升级、诊断、回滚或卸载 Mimi Remote;在 macOS 上安装和维护 Mimi Remote Mac 菜单栏 App / DMG,或通过 Homebrew、Linux user-systemd 部署 agentd;从源码构建 iPhone/iPad App;配置 Codex 主通道和可选 Claude Code 实验 Runtime。用户提出“安装 Mimi…
Build, test, and publish an afm-next nightly release — full from-scratch build, user testing pause, GitHub release, and Homebrew tap update.
$ npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install scouzi1966/maclocal-api build-afm-nightly-publish --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/scouzi1966/maclocal-api.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/build-afm-nightly-publish .claude/skills/build-afm-nightly-publish && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "build-afm-nightly-publish" agent skill from https://github.com/scouzi1966/maclocal-api/tree/main/.claude/skills/build-afm-nightly-publish into .claude/skills/build-afm-nightly-publish/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "build-afm-nightly-publish", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/scouzi1966/maclocal-api/tree/main/.claude/skills/build-afm-nightly-publishType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install scouzi1966/maclocal-api build-afm-nightly-publish --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/scouzi1966/maclocal-api.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/build-afm-nightly-publish .agents/skills/build-afm-nightly-publish && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "build-afm-nightly-publish" agent skill from https://github.com/scouzi1966/maclocal-api/tree/main/.claude/skills/build-afm-nightly-publish into .agents/skills/build-afm-nightly-publish/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "build-afm-nightly-publish", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install scouzi1966/maclocal-api build-afm-nightly-publish --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/scouzi1966/maclocal-api.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/build-afm-nightly-publish .cursor/skills/build-afm-nightly-publish && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "build-afm-nightly-publish" agent skill from https://github.com/scouzi1966/maclocal-api/tree/main/.claude/skills/build-afm-nightly-publish into .cursor/skills/build-afm-nightly-publish/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "build-afm-nightly-publish", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/scouzi1966/maclocal-api.git --path .claude/skills/build-afm-nightly-publish--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install scouzi1966/maclocal-api build-afm-nightly-publish --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/scouzi1966/maclocal-api.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/build-afm-nightly-publish .gemini/skills/build-afm-nightly-publish && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "build-afm-nightly-publish" agent skill from https://github.com/scouzi1966/maclocal-api/tree/main/.claude/skills/build-afm-nightly-publish into .gemini/skills/build-afm-nightly-publish/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "build-afm-nightly-publish", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install scouzi1966/maclocal-api build-afm-nightly-publishInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/scouzi1966/maclocal-api.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/build-afm-nightly-publish .github/skills/build-afm-nightly-publish && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "build-afm-nightly-publish" agent skill from https://github.com/scouzi1966/maclocal-api/tree/main/.claude/skills/build-afm-nightly-publish into .github/skills/build-afm-nightly-publish/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "build-afm-nightly-publish", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install scouzi1966/maclocal-api build-afm-nightly-publish --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/scouzi1966/maclocal-api.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/build-afm-nightly-publish .opencode/skills/build-afm-nightly-publish && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "build-afm-nightly-publish" agent skill from https://github.com/scouzi1966/maclocal-api/tree/main/.claude/skills/build-afm-nightly-publish into .opencode/skills/build-afm-nightly-publish/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "build-afm-nightly-publish", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
build-afm-nightly-publishBuild, test, and publish an afm-next nightly release — full from-scratch build, user testing pause, GitHub release, and Homebrew tap update.
Build Afm Nightly Publish is an agent skill from scouzi1966/maclocal-api. Build, test, and publish an afm-next nightly release — full from-scratch build, user testing pause, GitHub release, and Homebrew tap update. Use when user types /build-afm-nightly-publish or asks to publish a nightly build.
Its SKILL.md is about 8.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Mobile, covering iOS development and User research. It works with GitHub, Homebrew and macOS. The repository describes itself as: 'afm' command cli: macOS server and single prompt mode that exposes Apple's Foundation and MLX Models and other APIs running on your Mac through a single aggregated…. The licence is MIT.
5 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 138ca5d. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
gitghswiftnpmpython3brewpipnodenpxFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
kruks.aiFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Build Afm Nightly Publish loads about 8.8k tokens when it runs. Until then it costs about 62 tokens; SKILL.md has 2,967 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from scouzi1966/maclocal-api at commit 138ca5d, republished under its MIT licence (© scouzi1966). 2,967 words, ~8,824 tokens.
.claude/skills/build-afm-nightly-publish/SKILL.md (or your agent's skills folder).Build afm from scratch (works from a fresh clone), let the user test it, then publish a GitHub pre-release and update the Homebrew tap.
/build-afm-nightly-publish — full pipeline: build + test + publish/build-afm-nightly-publish --skip-build — skip build, use existing release binaryThe publish script (Scripts/publish-next.sh) requires:
gh CLI authenticated with push access to scouzi1966/maclocal-apihomebrew-afm repo at ../homebrew-afm (relative to repo root) or TAP_DIR env var — auto-cloned if missingvesta-mac repo at ../vesta-mac (relative to repo root) or VESTA_DIR env var — required for PEP 503 wheel index update. Auto-cloned if missing./build-afm (Xcode, Swift, Node.js, etc.)promptfoo CLI (npm install -g promptfoo or npx promptfoo) — required for the promptfoo agentic eval suitewrangler CLI (npm install -g wrangler) — required for Cloudflare Pages deploy of wheel indexRun these checks and present results to the user:
# Build prerequisites
uname -m # must be arm64
sw_vers -productVersion # must be 26.0+
xcode-select -p # must point to Xcode.app
swift --version # Swift 5.9+
git --version
node --version # Node 18+
npm --version
# Promptfoo (required for agentic eval suite)
command -v promptfoo && promptfoo --version 2>/dev/null | head -1
# Must be installed (npm install -g promptfoo)
# Publish prerequisites
gh auth status # must be authenticated
# CRITICAL: Verify the user is the repo owner (scouzi1966)
# This prevents non-owners from accidentally overwriting releases or the brew tap.
GH_USER=$(gh api user -q .login)
echo "GitHub user: $GH_USER"
# Must be "scouzi1966"
# Verify push (write) access to both repos
gh api repos/scouzi1966/maclocal-api -q '.permissions.push' # must be true
gh api repos/scouzi1966/homebrew-afm -q '.permissions.push' # must be true
# Tap repo — auto-clone if missing
TAP_DIR="${TAP_DIR:-$(cd "$(git rev-parse --show-toplevel)/.." && pwd)/homebrew-afm}"
if [ ! -f "$TAP_DIR/afm-next.rb" ]; then
echo "Tap repo missing at $TAP_DIR — cloning..."
gh repo clone scouzi1966/homebrew-afm "$TAP_DIR"
fi
test -f "$TAP_DIR/afm-next.rb" && echo "Tap OK: $TAP_DIR" || echo "FAILED to clone tap repo"
# vesta-mac repo — required for wheel index update. Auto-clone if missing.
VESTA_DIR="${VESTA_DIR:-$(cd "$(git rev-parse --show-toplevel)/.." && pwd)/vesta-mac}"
if [ ! -d "$VESTA_DIR/.git" ]; then
echo "vesta-mac repo missing at $VESTA_DIR — cloning..."
gh repo clone scouzi1966/vesta-mac "$VESTA_DIR"
fi
test -d "$VESTA_DIR/.git" && echo "vesta-mac OK: $VESTA_DIR" || echo "FAILED to clone vesta-mac"Present as a checklist. If the GitHub user is not scouzi1966 or push access is false for either repo, STOP immediately and tell the user:
This skill publishes releases and updates the Homebrew tap for scouzi1966/maclocal-api.
Only the repository owner (scouzi1966) can run it. You are authenticated as: <username>Do NOT proceed unless: (1) all build checks pass, (2) GitHub user has push access to BOTH repos, (3) tap repo is available.
A nightly release must be built from a completely clean state. swift package clean is NOT sufficient — it leaves behind cached modules, package resolution state, and precompiled headers that can mask stale code:
| Cached artifact | Location | What swift package clean does |
|---|---|---|
| Compiled .o/.swiftmodule | .build/arm64-apple-macosx/release/ | Removes |
| Module cache (PCM/PCH) | .build/arm64-apple-macosx/release/ModuleCache/ | Keeps (~400MB) |
| Cloned SPM dependencies | .build/repositories/ | Keeps (~300MB) |
| Package resolution lock | .build/workspace-state.json | Keeps |
| Xcode DerivedData | ~/Library/Developer/Xcode/DerivedData/*maclocal* | Keeps (if exists) |
Before running the build script, nuke all cached state:
# 1. Remove entire SPM build directory (modules, cache, resolution state — everything)
rm -rf .build
# 2. Remove Xcode DerivedData for this project (if anyone opened it in Xcode)
rm -rf ~/Library/Developer/Xcode/DerivedData/*maclocal* \
~/Library/Developer/Xcode/DerivedData/*MacLocal* \
~/Library/Developer/Xcode/DerivedData/*afm* 2>/dev/null || true
# 3. Verify clean state
test -d .build && echo "FAIL: .build still exists" || echo "OK: .build removed"Then run the full build:
./Scripts/build-from-scratch.shIMPORTANT: Never add --skip-submodules, --skip-patches, or --skip-webui. This is a release build — everything must be from scratch.
Why this matters: Stale ModuleCache can cause the compiler to use old .swiftmodule files from a previous build, meaning your patches compile but the binary links against the cached (unpatched) version. Stale workspace-state.json can resolve a different version of MLX Swift than what the pin specifies. Both failures are silent — the build succeeds, the binary runs, but behavior is wrong.
If the user passed --skip-build, skip the clean and build steps, but still run all Step 2b verification checks against the existing binary:
test -x .build/arm64-apple-macosx/release/afm || test -x .build/release/afmThe build script reports success, but do not trust its output alone. Independently verify every critical artifact. The build script could succeed (exit 0) while:
git submodule update)Run all of these checks. Present results as a table. If ANY check fails, STOP and investigate before proceeding.
The patch script says "Applied" but git submodule update can silently revert files. Verify every patch file is byte-for-byte identical to its vendor target using the actual arrays from Scripts/apply-mlx-patches.sh:
python3 -c "
import os
# These arrays MUST match Scripts/apply-mlx-patches.sh — if they drift, the check is wrong.
# Read them from the script itself to stay in sync.
patches = [
('Qwen3VL.swift','Libraries/MLXVLM/Models/Qwen3VL.swift'),
('Qwen3Next.swift','Libraries/MLXLLM/Models/Qwen3Next.swift'),
# ... all 20 entries from PATCH_FILES/TARGET_PATHS arrays ...
]
ok = fail = 0
for pf, tp in patches:
src, tgt = f'Scripts/patches/{pf}', f'vendor/mlx-swift-lm/{tp}'
if not os.path.exists(tgt):
print(f'MISSING: {pf} -> {tp}'); fail += 1
else:
with open(src,'rb') as a, open(tgt,'rb') as b:
if a.read() == b.read():
print(f'MATCH: {pf}'); ok += 1
else:
print(f'MISMATCH: {pf}'); fail += 1
print(f'\n{ok}/{ok+fail} patches verified')
"Why this matters: If even one patch is stale, the compiled binary has upstream code instead of our optimized/fixed version. This has happened when git submodule update --init --recursive runs AFTER apply-mlx-patches.sh — it silently reverts patches.
# Check the pin in source
grep 'mlx-swift.*exact' vendor/mlx-swift-lm/Package.swift
# Must show: exact: "0.30.3"
# 0.30.4+ has SDPA NaN regression — if this shows any other version, STOP.
# Check what SPM actually resolved (the pin could say 0.30.3 but resolution used a cached different version)
python3 -c "
import json
d = json.load(open('Package.resolved'))
for p in d.get('pins', []):
if 'mlx' in p.get('identity','').lower():
print(f'{p[\"identity\"]}: {p[\"state\"].get(\"version\",\"?\")}')
"
# Must show: mlx-swift: 0.30.3
# If version differs from pin, the resolution is stale — this is exactly what rm -rf .build prevents.Why this matters: The pin in Package.swift is a request, but Package.resolved is what was actually fetched and compiled against. A stale workspace-state.json or Package.resolved from a previous build can cause SPM to use a cached resolution even after the pin changes. Nuking .build/ in Step 2 prevents this, but verify anyway.
git submodule status vendor/xgrammar
# Must show a commit hash, NOT a '-' prefix (which means uninitialized)
cd vendor/xgrammar && git describe --tags --always && cd -
# Must show v0.1.32 or the expected pinned tagWhy this matters: xgrammar is a C++ library compiled from source. If the submodule is missing or at the wrong version, the EBNF grammar constraint feature either doesn't exist or has different behavior.
# Verify xgrammar C++ was compiled and linked (not just present as source)
strings .build/arm64-apple-macosx/release/afm | grep -c 'xgrammar/cpp/'
# Must be > 0 (typically 10+)
# Verify our Swift XGrammarService wrapper is in the binary
strings .build/arm64-apple-macosx/release/afm | grep 'XGrammarService'
# Must show: XGrammarService, _TtC11MacLocalAPI15XGrammarService, etc.
# Verify xgrammar C++ symbols are actually linked
nm -a .build/arm64-apple-macosx/release/afm 2>/dev/null | grep -c 'xgrammar'
# Must be > 0 (typically 30+)Why this matters: xgrammar could be in the source tree but excluded from the Swift Package Manager target graph. The binary would build fine but grammar-constrained decoding would silently fail at runtime.
METALLIB=".build/arm64-apple-macosx/release/AFMKit_AFMKitMLX.bundle/default.metallib"
test -f "$METALLIB" && echo "OK: metallib $(du -h "$METALLIB" | cut -f1)" || echo "FAIL: metallib missing"
# Must exist and be > 1MB (typically ~3.7MB)Why this matters: Without the metallib, MLX GPU kernels can't load. The server starts but crashes on first inference. The build script checks this, but verify independently.
test -f "Resources/webui/index.html.gz" && echo "OK: webui assets" || echo "FAIL: webui missing"Why this matters: The llama.cpp web UI is served at / — without it, browser access shows nothing.
grep 'static let version' Sources/AFMKit/BuildInfo.swift
# Must show the base version like: static let version: String? = "v0.9.7"
# Must NOT show a commit SHA like: static let version: String? = "v0.9.7-3d71b40"
git diff Sources/AFMKit/BuildInfo.swift
# Must show no diff (file restored to committed state)Why this matters: The build script injects the git SHA into BuildInfo.swift during compilation then restores it. If restore fails, the working tree is dirty and the next git commit could accidentally commit the injected version.
ls -lh .build/arm64-apple-macosx/release/afm
# Size should be 30-50MB for a stripped release binary
# If > 100MB, it's likely unstripped (debug symbols included)
nm -gU .build/arm64-apple-macosx/release/afm 2>/dev/null | wc -l
# Stripped binary has minimal external symbols (< 500 typically)
# Unstripped has thousandsThis is the most critical distribution check. SPM auto-generates resource_bundle_accessor.swift with a hardcoded absolute build path. If any code path calls Bundle.module, the binary will fatalError when installed via pip or Homebrew (because the build path no longer exists). This has shipped broken nightlies before.
# Simulate pip install: copy binary + loose metallib to a temp dir (NO SPM bundle directory)
TMPDIR=$(mktemp -d)
cp .build/arm64-apple-macosx/release/afm "$TMPDIR/"
cp .build/arm64-apple-macosx/release/AFMKit_AFMKitMLX.bundle/default.metallib "$TMPDIR/"
# Must NOT crash with "could not load resource bundle" fatalError
MACAFM_MLX_MODEL_CACHE=/Volumes/edata/models/vesta-test-cache \
"$TMPDIR/afm" mlx -m mlx-community/Qwen3.5-35B-A3B-4bit -s "hello" --max-tokens 5 2>&1 | head -3
EXIT_CODE=${PIPESTATUS[0]}
rm -rf "$TMPDIR"
if [ "$EXIT_CODE" -ne 0 ]; then
echo "FAIL: Relocated binary crashed (exit $EXIT_CODE)"
echo "This means Bundle.module fatalError is still reachable."
echo "Check MLXMetalLibrary.swift — it must NOT call Bundle.module."
else
echo "PASS: Relocated binary works"
fiWhy this matters: This is the exact layout pip creates: macafm_next/bin/afm + macafm_next/bin/default.metallib. If this test fails, every pip user gets a crash on first run. This check is non-negotiable — if it fails, do NOT publish.
Root cause if it fails: Someone added a Bundle.module call somewhere in the codebase. Search for it:
grep -r 'Bundle\.module' Sources/ --include='*.swift'
# Must return ZERO results (only comments allowed)# Bundle.module uses SPM's auto-generated accessor which fatalError's on relocated binaries.
# It must NEVER be called from our code. Comments referencing it are OK.
HITS=$(grep -r 'Bundle\.module' Sources/ --include='*.swift' | grep -v '//' | grep -v '^\s*//' | wc -l)
if [ "$HITS" -gt 0 ]; then
echo "FAIL: Found $HITS Bundle.module call(s) in source code"
grep -rn 'Bundle\.module' Sources/ --include='*.swift' | grep -v '//'
echo "This WILL crash when installed via pip or Homebrew."
else
echo "PASS: No Bundle.module calls"
fiWhy this matters: Even a single Bundle.module call anywhere in the code path triggers the auto-generated fatalError. This is a regression guard — any future code that adds Bundle.module will be caught here before it ships.
macOS 26 SIGABRTs any process that requests privacy-sensitive APIs (Speech Recognition, microphone, camera, contacts, etc.) without a matching *UsageDescription key in its Info.plist. Currently required for PR #107's Apple Speech feature (afm speech, POST /v1/audio/transcriptions, chat input_audio content parts). Any future privacy-API integration needs its key added here too.
BIN=.build/arm64-apple-macosx/release/afm
# Verify __TEXT,__info_plist section exists (embedded via Package.swift linker flags)
if otool -l "$BIN" | grep -q '__info_plist'; then
PLIST_SIZE=$(otool -l "$BIN" | grep -A4 __info_plist | grep 'size' | awk '{print $2}')
echo "PASS: __info_plist section present ($PLIST_SIZE bytes)"
else
echo "FAIL: Missing __TEXT,__info_plist section"
echo "Check Package.swift linker flags (-Xlinker -sectcreate ...) and Sources/AFMCLI/Info.plist"
fi
# Verify NSSpeechRecognitionUsageDescription key is present
if strings "$BIN" | grep -q 'NSSpeechRecognitionUsageDescription'; then
echo "PASS: NSSpeechRecognitionUsageDescription key embedded"
else
echo "FAIL: NSSpeechRecognitionUsageDescription missing from embedded plist"
echo "afm speech / /v1/audio/transcriptions will SIGABRT on macOS 26"
fi
# Verify plist structure is parseable (not corrupted during build)
plutil -lint Sources/AFMCLI/Info.plistWhy this matters: Without the embedded plist, running afm speech -f foo.wav (or any endpoint that calls SFSpeechRecognizer) crashes before returning any output. The build script Scripts/build-from-scratch.sh already enforces this — but also check here so the publish flow fails loudly if someone bypasses the build script or if Package.swift's linker flags get reverted in a merge.
Root cause if it fails:
Sources/AFMCLI/Info.plist was deleted or renamedlinkerSettings lost the -Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist -Xlinker … flags*UsageDescription key to Info.plistFull plist must also include CFBundleIdentifier, CFBundleName, CFBundleExecutable — these establish TCC identity. Changing CFBundleIdentifier later would force existing users to re-grant Speech Recognition permission.
Present the exact version of every submodule and SPM dependency so the user can verify the build reproduces the expected dependency tree. Also fetch the latest release tag from each upstream repo to show if we're behind.
echo "=== Git Submodules ==="
git submodule status
echo "=== SPM Resolved Versions ==="
python3 -c "
import json
d = json.load(open('Package.resolved'))
for p in sorted(d.get('pins', []), key=lambda x: x.get('identity','')):
v = p['state'].get('version') or p['state'].get('revision','?')[:12]
print(f' {p[\"identity\"]}: {v}')
"
echo "=== Upstream Latest Releases ==="
for repo in ml-explore/mlx-swift ml-explore/mlx-swift-lm mlc-ai/xgrammar ggml-org/llama.cpp huggingface/swift-transformers huggingface/swift-huggingface; do
tag=$(gh api "repos/$repo/releases/latest" -q '.tag_name' 2>/dev/null || echo "?")
echo " $repo: $tag"
doneThis is informational — no pass/fail. But if a resolved version is unexpected (e.g., mlx-swift != 0.30.3), STOP.
| # | Check | What could go wrong | Result |
|---|---|---|---|
| 1 | Patches byte-identical (N/N) | submodule update reverted patches | PASS/FAIL |
| 2 | MLX Swift pin + resolved 0.30.3 | stale resolution → SDPA NaN crashes | PASS/FAIL |
| 3 | xgrammar at expected tag | missing submodule → no grammar constraints | PASS/FAIL |
| 4 | xgrammar linked in binary | compiled but not linked → silent runtime failure | PASS/FAIL |
| 5 | Metallib bundle present | missing → crash on first inference | PASS/FAIL |
| 6 | WebUI assets present | missing → no browser UI | PASS/FAIL |
| 7 | BuildInfo.swift clean | dirty working tree → accidental commit | PASS/FAIL |
| 8 | Binary stripped, reasonable size | unstripped → bloated download | PASS/FAIL |
| 9 | Relocated binary works (pip sim) | Bundle.module fatalError → crash on pip install | PASS/FAIL |
| 10 | No Bundle.module in source | regression guard → future crash on relocated binary | PASS/FAIL |
| 11 | Info.plist embedded + NSSpeechRecognitionUsageDescription | macOS 26 SIGABRTs Speech Recognition without UsageDescription key | PASS/FAIL |
Then present two separate tables for vendor pins:
Git Submodules:
| Submodule | Source | Pinned Commit | Upstream Latest | Notes |
|---|---|---|---|---|
vendor/mlx-swift-lm | Submodule | git submodule status hash + tag | gh api repos/.../releases/latest | Our patched fork |
vendor/xgrammar | Submodule | git submodule status hash + tag | gh api repos/.../releases/latest | C++ grammar engine |
vendor/llama.cpp | Submodule | git submodule status hash + tag | gh api repos/.../releases/latest | WebUI only |
SPM Dependencies (from Package.resolved):
| Package | Source | Resolved Version | Upstream Latest | Notes |
|---|---|---|---|---|
mlx-swift | SPM (exact pin) | Package.resolved version | gh api repos/.../releases/latest | 0.30.4+ has SDPA NaN — pinned to exact 0.30.3 |
swift-transformers | SPM (from) | Package.resolved version | gh api repos/.../releases/latest | Tokenizer/chat templates |
swift-huggingface | SPM (from) | Package.resolved version | gh api repos/.../releases/latest | HF hub downloads |
swift-jinja | SPM (transitive) | Package.resolved version | — | Jinja2 template engine |
vapor | SPM (from) | Package.resolved version | — | HTTP framework |
Populate the "Upstream Latest" column by querying gh api repos/OWNER/REPO/releases/latest -q '.tag_name'. This lets the user see at a glance if we're behind upstream on any dependency.
If ANY check fails, STOP. Do not proceed to user testing or publishing.
After all verification checks pass, get the binary path and version:
BIN=".build/arm64-apple-macosx/release/afm"
[ -x "$BIN" ] || BIN=".build/release/afm"
echo "Binary: $(cd "$(dirname "$BIN")" && pwd)/$(basename "$BIN")"
$BIN --versionReport to the user:
Then use AskUserQuestion to pause and let the user decide what to do next:
Question: "The build is verified. What would you like to do?"
Options:
First, list available models in the cache and let the user pick:
MACAFM_MLX_MODEL_CACHE=/Volumes/edata/models/vesta-test-cache ./Scripts/list-models.shQuestion: "Which model to test with?"
Present the available models as options (show model name and size). The user picks one.
Then ask the test scope:
Question: "Which tests to run?"
Options:
/test-afm-assertions with full tier (deterministic pass/fail tests, ~15 min/model)/test-macafm smart analysis (AI-scored quality evaluation)Invoke the appropriate skill(s) with the selected model. Do NOT re-ask the model question — pass it through to the test skill(s).
Run the full promptfoo agentic eval suite after assertions/comprehensive complete:
AFM_MODEL=MODEL \
AFM_BINARY=.build/arm64-apple-macosx/release/afm \
MACAFM_MLX_MODEL_CACHE=/Volumes/edata/models/vesta-test-cache \
./Scripts/feature-promptfoo-agentic/run-promptfoo-agentic.sh allThis manages its own server lifecycle (starts/stops across 8 server profiles) and runs ~137 test cases across 16 configs:
json_schema response format and stress testsOutput: JSON reports in $AFM_PROMPTFOO_OUT_DIR (default: /Volumes/edata/promptfoo/data/maclocal-api/current/).
Interpreting promptfoo results:
--concurrent 2 grammar path — not a release blocker.To extract pass/fail summary from all result files:
python3 -c "
import json, os, glob
files = sorted(glob.glob('$AFM_PROMPTFOO_OUT_DIR/*MODEL_SLUG*.json'))
total_pass = total_fail = 0
for f in files:
name = os.path.basename(f).replace('-MODEL_SLUG.json','')
d = json.load(open(f))
stats = d.get('results', d).get('stats', {})
p, fa = stats.get('successes', 0), stats.get('failures', 0)
total_pass += p; total_fail += fa
status = 'PASS' if fa == 0 else f'FAIL ({fa})'
print(f'{name}: {p}/{p+fa} — {status}')
print(f'\nTOTAL: {total_pass}/{total_pass+total_fail}')
"After tests complete, present results and ask:
Question: "Tests complete. What next?"
Options:
Wait for the user to come back. When they do, ask:
Question: "Ready to proceed?"
Options:
The user will make code changes (or ask you to). After changes are made:
rm -rf .build + ./Scripts/build-from-scratch.sh)This loop repeats until the user selects "Publish" or "Cancel". Each iteration is a full clean rebuild — never do an incremental build for a release.
Before publishing, ask the user two questions via AskUserQuestion:
Question 1 — Version: Determine the suggested version by reading Sources/AFMKit/BuildInfo.swift and extracting the version (strip leading v). Present it to the user:
"Release version? The base version from BuildInfo.swift is X.Y.Z. The full nightly version will be X.Y.Z-next.<sha>.<date>."
Options:
Question 2 — Changelog since: Show both the last nightly tag AND the last stable release tag so the user can choose the right baseline:
# Find the last nightly tag
LAST_NIGHTLY=$(git tag -l 'nightly-*' --sort=-creatordate | head -1)
if [ -n "$LAST_NIGHTLY" ]; then
NIGHTLY_DATE=$(git log -1 --format='%ci' "$LAST_NIGHTLY" 2>/dev/null | cut -d' ' -f1)
NIGHTLY_COUNT=$(git rev-list "${LAST_NIGHTLY}..HEAD" --count 2>/dev/null)
echo "Last nightly: $LAST_NIGHTLY ($NIGHTLY_DATE) — $NIGHTLY_COUNT commits since"
fi
# Find the last stable release tag (v*.*.* without -next or nightly)
LAST_STABLE=$(git tag -l 'v*' --sort=-version:refname | grep -v 'nightly\|next' | head -1)
if [ -n "$LAST_STABLE" ]; then
STABLE_DATE=$(git log -1 --format='%ci' "$LAST_STABLE" 2>/dev/null | cut -d' ' -f1)
STABLE_COUNT=$(git rev-list "${LAST_STABLE}..HEAD" --count 2>/dev/null)
echo "Last stable: $LAST_STABLE ($STABLE_DATE) — $STABLE_COUNT commits since"
fi
# Show commit log from the more recent of the two
echo "--- Commits since last nightly ---"
git log --oneline "${LAST_NIGHTLY}..HEAD" 2>/dev/nullPresent both reference points and ask:
"Generate changelog from which point?"
Options:
<tag> (N commits)" — Default for routine nightlies (incremental changelog)<tag> (N commits)" — Use for the first nightly after a stable release, or when you want the full delta since the last official versionGuidance for which to pick:
v* tag): use "since last stable release" — the changelog shows everything new in this development cycle--since entirely (the script will include all commits)Changelog filtering — exclude reverted/superseded commits: When reviewing the commit list for the changelog, omit commits whose work was later removed or fully replaced. For example, if a commit adds a Python bridge and a later commit removes it, neither should appear in the release notes — the net effect is zero. Only include commits that contribute to the final state of the codebase at HEAD. The publish-next.sh script includes all commits mechanically; you should review and curate the changelog before presenting it to the user.
If the user selects "since last stable release", pass --since <stable-tag-sha> to the publish script.
If the user provides a custom SHA, pass --since <sha>.
If the user selects "since last nightly", no --since flag is needed (the script defaults to this).
Do NOT proceed to Step 4 unless the user selects "Publish".
Run the publish script with --skip-build (already built in Step 2), the confirmed version, and optional --since:
# Without custom since (uses last nightly tag):
./Scripts/publish-next.sh --skip-build --version <confirmed-version>
# With custom since:
./Scripts/publish-next.sh --skip-build --version <confirmed-version> --since <commit-sha>This script handles everything:
afm-next-arm64.tar.gznightly-* release (with both Homebrew and pip install instructions in the release notes)nightly-YYYYMMDD-SHORTSHAnightly tag to point to HEADafm-next.rb in the homebrew-afm tap (url, version, sha256)macafm-next) via Scripts/build-nightly-wheel.shScripts/update-wheel-index.sh (requires vesta-mac repo at ../vesta-mac and wrangler for Cloudflare Pages deploy)After publishing, update both nightly references in README.md. There are two — update both or one goes stale:
(a) Release-notes link (Install table row):
# | **Release notes** | [v0.9.6](...) | [v0.9.7-next](https://github.com/scouzi1966/maclocal-api/releases/tag/nightly-YYYYMMDD-SHORTSHA) |nightly-* tag in the URL with the just-published tag (e.g., nightly-20260614-a92a0fe).v0.9.7-next → v0.9.8-next).(b) Pinned pip wheel example (the "Install a previous version" / pip block):
# macafm-next==0.9.13.dev20260613 # pinned nightlymacafm-next==<base>.dev<YYYYMMDD> (e.g., 0.9.13.dev20260614).
The wheel version is <base>.dev<YYYYMMDD> — same base as the release, date = build date.
(The unpinned pip install … macafm-next command always resolves to latest and needs no change — only the pinned example lags.)Then verify nothing stale remains, commit, and push:
grep -nE 'dev2026[0-9]{4}|nightly-2026[0-9]{4}' README.md # all should show the NEW date/tagUpdate README nightly references to YYYYMMDD-SHORTSHA) and push to remote.Do not skip this step. The README is the main page users see — both the release link and the pinned wheel example must point to the latest nightly.
After the publish script completes, verify and report:
# Verify GitHub release exists
SHORT_SHA=$(git rev-parse --short HEAD)
DATE=$(date -u +%Y%m%d)
RELEASE_TAG="nightly-${DATE}-${SHORT_SHA}"
gh release view "$RELEASE_TAG" --repo scouzi1966/maclocal-api --json tagName,url,assets -q '.url'
# Verify tap was updated
TAP_DIR="${TAP_DIR:-$(cd "$(git rev-parse --show-toplevel)/.." && pwd)/homebrew-afm}"
grep 'version "' "$TAP_DIR/afm-next.rb"Report to the user:
# Homebrew
brew tap scouzi1966/afm
brew install scouzi1966/afm/afm-next # fresh install
brew upgrade afm-next # upgrade
# pip
pip install --extra-index-url https://kruks.ai/afm/wheels/simple/ macafm-nextCopy all test reports from this nightly run to the versioned nightly archive:
DATE=$(date +%Y-%m-%d)
mkdir -p test-reports/nightly/$DATE
cp test-reports/assertions-report-*.html test-reports/assertions-report-*.jsonl \
test-reports/multi-assertions-report-*.html test-reports/multi-assertions-report-*.jsonl \
test-reports/nightly/$DATE/ 2>/dev/null || true
# Also copy promptfoo results if they were run
AFM_PROMPTFOO_OUT_DIR="${AFM_PROMPTFOO_OUT_DIR:-/Volumes/edata/promptfoo/data/maclocal-api/current}"
cp "$AFM_PROMPTFOO_OUT_DIR"/*.json test-reports/nightly/$DATE/ 2>/dev/null || true
# Also copy smart analysis if it was run
cp test-reports/smart-analysis-*.md test-reports/nightly/$DATE/ 2>/dev/null || true
git add test-reports/nightly/$DATE/
git commit -m "Add nightly test results for $DATE ($(git rev-parse --short HEAD))"
git pushThis maintains the test history at test-reports/nightly/YYYY-MM-DD/ for cross-nightly comparison.
/build-afm first to diagnose./Scripts/apply-mlx-patches.sh. If resolution is wrong, rm -rf .build and rebuild.gh auth status, check if tag already exists (gh release view <tag>)../homebrew-afm is on the right branch and has no uncommitted changes© scouzi1966, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/build-afm-nightly-publish of scouzi1966/maclocal-api.
Open the folder on GitHubat commit 138ca5d
Build Afm Nightly Publish next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Build Afm Nightly Publish this skillscouzi1966/maclocal-api | 346 | — | ~8.8k | Automated safety check: Pass | MIT | |
| Install Mimi Remotegaixianggeng/mimi-remote | 104 | — | ~2.8k | Automated safety check: Pass | GPL-3.0 | |
| Releasevaayne/mori | 303 | — | ~1.2k | Automated safety check: Pass | MIT | |
| Tusk ReleaseShape-Machine/tusk-macos | 127 | — | ~822 | Automated safety check: Pass | Custom licence | |
| Releasenikships/droidproxy | 122 | — | ~1.1k | Automated safety check: Pass | MIT | |
| Swift Developmentaiskillstore/marketplace | 433 | — | ~1.8k | Automated safety check: Pass | None |
gaixianggeng/mimi-remote
安装、配置、配对、迁移、升级、诊断、回滚或卸载 Mimi Remote;在 macOS 上安装和维护 Mimi Remote Mac 菜单栏 App / DMG,或通过 Homebrew、Linux user-systemd 部署 agentd;从源码构建 iPhone/iPad App;配置 Codex 主通道和可选 Claude Code 实验 Runtime。用户提出“安装 Mimi…
vaayne/mori
Release workflow for Mori macOS workspace terminal and MoriRemote iOS app.
Shape-Machine/tusk-macos
A skill your agent uses when preparing a full Tusk macOS release: bump the app version, regenerate the Xcode project, build and package the DMG, publish the GitHub release, and update README links.
nikships/droidproxy
Build, sign, notarize, and publish a new DroidProxy release.
aiskillstore/marketplace
Comprehensive Swift development for building, testing, and deploying iOS/macOS applications.
kangraemin/claude-inspector
Claude Inspector macOS 배포 스킬. An agent skill from kangraemin/claude-inspector.
scouzi1966/maclocal-api
Maintain and extend AFM (maclocal-api), a Swift OpenAI-compatible local LLM server and CLI for Apple Foundation Models, MLX models, API gateway proxying, and Vision OCR.
scouzi1966/maclocal-api
Build AFM from scratch — submodules, patches, webui, and Swift build.
scouzi1966/maclocal-api
Run and review the Promptfoo-based AFM agentic evaluation suite.
scouzi1966/maclocal-api
Test a pre-built afm binary at any path — runs pre-flight safety checks, then any combination of unit tests, assertions, smart analysis, promptfoo evals, batch validation, OpenAI compat, GPU…
scouzi1966/maclocal-api
A skill your agent uses when user wants to build a PyPI wheel from an existing compiled afm binary and publish to PyPI.
scouzi1966/maclocal-api
Run the maclocal-api (AFM/MLX) test suite — automated assertions and smart analysis.
Build, test, and publish an afm-next nightly release — full from-scratch build, user testing pause, GitHub release, and Homebrew tap update. Build Afm Nightly Publish is an agent skill from scouzi1966/maclocal-api. Build, test, and publish an afm-next nightly release — full from-scratch build, user testing pause, GitHub release, and Homebrew tap update.
Build Afm Nightly Publish fits situations like: user types /build-afm-nightly-publish; asks to publish a nightly build.
Run `npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a claude-code`. Or copy the skill folder (.claude/skills/build-afm-nightly-publish in scouzi1966/maclocal-api) into .claude/skills/build-afm-nightly-publish in your project. Claude Code loads it when a task matches its description.
Run `npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a codex`. Or copy the skill folder (.claude/skills/build-afm-nightly-publish in scouzi1966/maclocal-api) into .agents/skills/build-afm-nightly-publish in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add scouzi1966/maclocal-api --skill build-afm-nightly-publish -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/build-afm-nightly-publish, .gemini/skills/build-afm-nightly-publish, .github/skills/build-afm-nightly-publish and .opencode/skills/build-afm-nightly-publish in your project.
Going by SKILL.md and its folder, Build Afm Nightly Publish needs the command-line tools its instructions call (git, gh, swift, npm, python3 and brew). Our summary lists: Python 3; Node.js.
SKILL.md names 1 domain. In commands or code: kruks.ai; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Build Afm Nightly Publish is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 8.8k tokens (SKILL.md is roughly 35k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Build Afm Nightly Publish: Install Mimi Remote (gaixianggeng/mimi-remote, 104 stars), Release (vaayne/mori, 303 stars), Tusk Release (Shape-Machine/tusk-macos, 127 stars) and Release (nikships/droidproxy, 122 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
scouzi1966 (a GitHub user) maintains it in scouzi1966/maclocal-api, which has 346 GitHub stars. The repository holds 12 skills in this directory. The repository was last updated on October 10, 2026.
Source: scouzi1966/maclocal-api on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.