Skip to content

Commit bcbaeba

Browse files
author
lucasliu
committed
feat: Homebrew tap distribution + thinking/parser/test improvements
- Add Homebrew formula (Formula/novamlx.rb) with brew services support - Update release workflow to clone vendor deps + support workflow_dispatch - Add bump-formula.sh for automated formula updates on new releases - Add Homebrew as recommended install option in README - Improve ThinkingParser for implicit open tag handling - Enhance ChatPageView with new UI features - Add comprehensive test suites for agents, control tokens, streaming
1 parent 2d5cb59 commit bcbaeba

23 files changed

Lines changed: 3567 additions & 104 deletions

.github/workflows/release.yml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ on:
55
tags: ["v*"]
66
pull_request:
77
branches: [main]
8+
workflow_dispatch:
89

910
concurrency:
1011
group: ${{ github.workflow }}-${{ github.ref }}
1112
cancel-in-progress: true
1213

14+
env:
15+
MLX_SWIFT_REF: "main"
16+
MLX_SWIFT_LM_REF: "main"
17+
1318
jobs:
1419
build:
1520
name: Build (${{ matrix.config.name }})
@@ -20,15 +25,19 @@ jobs:
2025
- name: "Apple Silicon"
2126
runner: macos-15
2227
suffix: "arm64"
23-
- name: "Intel"
24-
runner: macos-15-large
25-
suffix: "x86_64"
2628

2729
steps:
2830
- uses: actions/checkout@v4
2931

30-
- name: Select Xcode
31-
run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer || true
32+
- name: Clone vendor dependencies
33+
run: |
34+
# mlx-swift — Apple's MLX Swift bindings (local fork target)
35+
git clone --depth 1 --branch "${MLX_SWIFT_REF}" \
36+
https://github.com/ml-explore/mlx-swift.git vendors/mlx-swift
37+
38+
# mlx-swift-lm — LM/VLM layer on top of mlx-swift
39+
git clone --depth 1 --branch "${MLX_SWIFT_LM_REF}" \
40+
https://github.com/ml-explore/mlx-swift-lm.git mlx-swift-lm
3241
3342
- name: Build
3443
run: |
@@ -51,26 +60,17 @@ jobs:
5160
path: dist/
5261

5362
release:
54-
name: Create Release
63+
name: Create GitHub Release
5564
needs: build
5665
if: startsWith(github.ref, 'refs/tags/v')
5766
runs-on: macos-15
5867

5968
steps:
60-
- uses: actions/checkout@v4
61-
62-
- name: Select Xcode
63-
run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer || true
64-
65-
- name: Build release
66-
run: |
67-
chmod +x build.sh
68-
./build.sh -c release
69-
70-
- name: Package
71-
run: |
72-
chmod +x Scripts/package.sh
73-
./Scripts/package.sh "${GITHUB_REF_NAME#v}"
69+
- name: Download all artifacts
70+
uses: actions/download-artifact@v4
71+
with:
72+
path: dist/
73+
merge-multiple: true
7474

7575
- name: Create GitHub Release
7676
uses: softprops/action-gh-release@v2

Formula/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# homebrew-novamlx
2+
3+
Homebrew tap for [NovaMLX](https://github.com/cnshsliu/novamlx) — Apple Silicon MLX inference server.
4+
5+
## Install
6+
7+
```bash
8+
brew tap cnshsliu/novamlx
9+
brew install novamlx
10+
```
11+
12+
## Start as a service
13+
14+
```bash
15+
brew services start novamlx
16+
```
17+
18+
## Run manually
19+
20+
```bash
21+
novamlx serve
22+
```
23+
24+
## Uninstall
25+
26+
```bash
27+
brew services stop novamlx
28+
brew uninstall novamlx
29+
brew untap cnshsliu/novamlx
30+
```
31+
32+
## Requirements
33+
34+
- macOS 15.0 (Sequoia) or later
35+
- Apple Silicon (M1/M2/M3/M4)

Formula/novamlx.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class Novamlx < Formula
2+
desc "Apple Silicon MLX inference server — OpenAI & Anthropic compatible API"
3+
homepage "https://github.com/cnshsliu/novamlx"
4+
url "https://github.com/cnshsliu/novamlx/releases/download/v1.0.0/NovaMLX-1.0.0-arm64.tar.gz"
5+
sha256 "PLACEHOLDER_REPLACE_WITH_ACTUAL_SHA256"
6+
version "1.0.0"
7+
8+
depends_on :macos => :sequoia
9+
10+
def install
11+
libexec.install Dir["NovaMLX.app/**"]
12+
bin.install_symlink libexec/"Contents/MacOS/NovaMLX" => "novamlx"
13+
bin.install_symlink libexec/"Contents/MacOS/NovaMLXWorker" => "novamlx-worker"
14+
bin.install_symlink libexec/"Contents/MacOS/nova" => "nova-cli"
15+
end
16+
17+
service do
18+
run [opt_bin/"novamlx", "serve"]
19+
keep_alive true
20+
log_path var/"log/novamlx.log"
21+
error_log_path var/"log/novamlx.error.log"
22+
environment_variables PATH: stdlib_path("usr/bin")
23+
end
24+
25+
def caveats
26+
<<~EOS
27+
NovaMLX requires macOS 15.0 (Sequoia) or later with Apple Silicon.
28+
29+
Start the server:
30+
brew services start novamlx
31+
32+
Or run manually:
33+
novamlx serve
34+
35+
Configuration is stored in ~/.nova/config.json
36+
Models are downloaded to ~/.nova/models/
37+
38+
Documentation: https://github.com/cnshsliu/novamlx#readme
39+
EOS
40+
end
41+
42+
test do
43+
assert_match "NovaMLX", shell_output("#{bin}/novamlx --version", 0)
44+
rescue
45+
# --version may not be implemented yet, fallback check binary exists
46+
assert_path_exists bin/"novamlx"
47+
end
48+
end

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ let package = Package(
195195
),
196196
.testTarget(
197197
name: "NovaMLXAPITests",
198-
dependencies: ["NovaMLXAPI"],
198+
dependencies: ["NovaMLXAPI", "NovaMLXEngine"],
199199
swiftSettings: concurrencySettings
200200
),
201201
.testTarget(

README.md

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,28 @@ Run 50+ model families — Llama, Qwen, Gemma, DeepSeek, Mistral — natively on
2222

2323
## Install
2424

25-
**Option 1: Download** (recommended)
26-
27-
Go to [Releases](https://github.com/nova/nova/releases) and download the latest `NovaMLX-vX.X.X-arm64.tar.gz`:
25+
**Option 1: Homebrew** (recommended)
2826

2927
```bash
30-
tar -xzf NovaMLX-*.tar.gz
31-
sudo mv NovaMLX /usr/local/bin/
32-
sudo mv nova /usr/local/bin/
28+
brew tap cnshsliu/novamlx
29+
brew install novamlx
30+
brew services start novamlx
3331
```
3432

35-
**Option 2: Build from source**
33+
**Option 2: Download DMG**
34+
35+
Go to [Releases](https://github.com/cnshsliu/novamlx/releases) and download the latest `NovaMLX-X.X.X-arm64.dmg`:
36+
37+
1. Open the `.dmg` file
38+
2. Drag **NovaMLX** to your **Applications** folder
39+
3. Launch NovaMLX — the menu bar icon appears and the server starts on `localhost:8080`
40+
41+
**Option 3: Build from source**
3642

3743
```bash
38-
git clone https://github.com/nova/nova.git
39-
cd nova
44+
git clone https://github.com/cnshsliu/novamlx.git
45+
cd novamlx
4046
./build.sh -c release
41-
sudo cp .build/release/NovaMLX /usr/local/bin/
42-
sudo cp .build/release/nova /usr/local/bin/
4347
```
4448

4549
> Requires macOS 15 (Sequoia), Apple Silicon, and Xcode 16+.
@@ -50,25 +54,31 @@ sudo cp .build/release/nova /usr/local/bin/
5054

5155
### 1. Start the server
5256

53-
```bash
54-
NovaMLX
55-
```
57+
Launch **NovaMLX** from your Applications folder (or Spotlight).
5658

5759
A **menu bar icon** appears. The server runs on `localhost:8080`.
5860

59-
### 2. Download a model
61+
### 2. (Optional) Add `nova` CLI to your PATH
62+
63+
The `nova` CLI is bundled inside the app. Symlink it for easy access:
64+
65+
```bash
66+
sudo ln -s /Applications/NovaMLX.app/Contents/MacOS/nova /usr/local/bin/nova
67+
```
68+
69+
### 3. Download a model
6070

6171
```bash
6272
nova download mlx-community/Meta-Llama-3.1-8B-Instruct-4bit
6373
```
6474

65-
### 3. Load it
75+
### 4. Load it
6676

6777
```bash
6878
nova load mlx-community/Meta-Llama-3.1-8B-Instruct-4bit
6979
```
7080

71-
### 4. Use it
81+
### 5. Use it
7282

7383
```bash
7484
# Interactive chat
@@ -125,10 +135,10 @@ Add to your opencode config (`~/.config/opencode/config.json`):
125135

126136
Settings → Models → OpenAI API Compatible:
127137

128-
| Field | Value |
129-
|-------|-------|
130-
| Base URL | `http://localhost:8080/v1` |
131-
| API Key | `unused` |
138+
| Field | Value |
139+
| -------- | ----------------------------------------------- |
140+
| Base URL | `http://localhost:8080/v1` |
141+
| API Key | `unused` |
132142
| Model ID | `mlx-community/Meta-Llama-3.1-8B-Instruct-4bit` |
133143

134144
### Continue.dev
@@ -137,13 +147,15 @@ Add to `~/.continue/config.json`:
137147

138148
```json
139149
{
140-
"models": [{
141-
"title": "NovaMLX Local",
142-
"provider": "openai",
143-
"apiBase": "http://localhost:8080/v1",
144-
"apiKey": "unused",
145-
"model": "mlx-community/Meta-Llama-3.1-8B-Instruct-4bit"
146-
}]
150+
"models": [
151+
{
152+
"title": "NovaMLX Local",
153+
"provider": "openai",
154+
"apiBase": "http://localhost:8080/v1",
155+
"apiKey": "unused",
156+
"model": "mlx-community/Meta-Llama-3.1-8B-Instruct-4bit"
157+
}
158+
]
147159
}
148160
```
149161

@@ -276,6 +288,7 @@ nova bench status # Check benchmark progress
276288
### macOS Menu Bar App
277289

278290
When you start `NovaMLX`, a menu bar icon appears showing:
291+
279292
- Server status (running/stopped)
280293
- Loaded models
281294
- GPU memory usage
@@ -375,28 +388,28 @@ curl http://localhost:8080/v1/audio/speech \
375388

376389
Same server, both APIs:
377390

378-
| API | Endpoint |
379-
|-----|----------|
380-
| OpenAI Chat | `POST /v1/chat/completions` |
381-
| OpenAI Completions | `POST /v1/completions` |
382-
| OpenAI Responses | `POST /v1/responses` |
383-
| OpenAI Embeddings | `POST /v1/embeddings` |
384-
| Anthropic Messages | `POST /v1/messages` |
391+
| API | Endpoint |
392+
| ------------------ | --------------------------- |
393+
| OpenAI Chat | `POST /v1/chat/completions` |
394+
| OpenAI Completions | `POST /v1/completions` |
395+
| OpenAI Responses | `POST /v1/responses` |
396+
| OpenAI Embeddings | `POST /v1/embeddings` |
397+
| Anthropic Messages | `POST /v1/messages` |
385398

386399
---
387400

388401
## Supported Models
389402

390403
Any SafeTensors model from HuggingFace in 4-bit, 8-bit, or FP16. Popular choices:
391404

392-
| Model | Size | Download Command |
393-
|-------|------|-----------------|
394-
| Llama 3.1 8B | ~4.5 GB | `nova download mlx-community/Meta-Llama-3.1-8B-Instruct-4bit` |
395-
| Qwen 2.5 7B | ~4.5 GB | `nova download mlx-community/Qwen2.5-7B-Instruct-4bit` |
396-
| Gemma 2 9B | ~5.5 GB | `nova download mlx-community/gemma-2-9b-it-4bit` |
397-
| Phi 3.5 Mini | ~2 GB | `nova download mlx-community/Phi-3.5-mini-instruct-4bit` |
398-
| Mistral 7B | ~4 GB | `nova download mlx-community/Mistral-7B-Instruct-v0.3-4bit` |
399-
| Qwen 2.5 VL 7B | ~4.5 GB | `nova download mlx-community/Qwen2.5-VL-7B-Instruct-4bit` |
405+
| Model | Size | Download Command |
406+
| -------------- | ------- | ------------------------------------------------------------- |
407+
| Llama 3.1 8B | ~4.5 GB | `nova download mlx-community/Meta-Llama-3.1-8B-Instruct-4bit` |
408+
| Qwen 2.5 7B | ~4.5 GB | `nova download mlx-community/Qwen2.5-7B-Instruct-4bit` |
409+
| Gemma 2 9B | ~5.5 GB | `nova download mlx-community/gemma-2-9b-it-4bit` |
410+
| Phi 3.5 Mini | ~2 GB | `nova download mlx-community/Phi-3.5-mini-instruct-4bit` |
411+
| Mistral 7B | ~4 GB | `nova download mlx-community/Mistral-7B-Instruct-v0.3-4bit` |
412+
| Qwen 2.5 VL 7B | ~4.5 GB | `nova download mlx-community/Qwen2.5-VL-7B-Instruct-4bit` |
400413

401414
Search for more: `nova search "your model name"`
402415

@@ -421,7 +434,7 @@ curl -X PUT http://localhost:8081/admin/models/my-model/settings \
421434

422435
### Config File
423436

424-
`~/Library/Application Support/NovaMLX/config.json`:
437+
`~/.config/opencode/config.json`:
425438

426439
```json
427440
{
@@ -445,6 +458,7 @@ curl -X PUT http://localhost:8081/admin/models/my-model/settings \
445458
## For Developers
446459

447460
See [DEVELOPMENT.md](DEVELOPMENT.md) for:
461+
448462
- Architecture overview (11-module design)
449463
- Building from source
450464
- Running tests

0 commit comments

Comments
 (0)