Skip to content
 
 

Repository files navigation

lemonade-pi-plugin

Pi.dev extension that registers Lemonade — a local LLM server with GPU/NPU acceleration — as a custom provider in Pi.

After install, type /login in Pi, pick Lemonade from the OAuth selector, confirm or override the server URL, optionally enter an API key, then all models exposed by your server are registered automatically.

Features

  • Built-in /login integration — appears as a "Lemonade" choice in Pi's OAuth selector. No custom slash commands needed.
  • UDP beacon discovery — listens for Lemonade's broadcast on port 13305 (the same channel lemonade scan uses) and finds local and LAN servers automatically.
  • HTTP fallback — scans localhost:13305, 8000, 1234, 9000, 8080 if no beacon arrives.
  • API key support — prompted during login, stored by Pi in ~/.pi/agent/auth.json, sent as Authorization: Bearer … on every request.
  • Model admin/lemonade for status, health, list, models, load, unload, pull, delete, refresh, discover, change-ctx.

Install

Via Pi (git)

pi install git:github.com/lemonade-sdk/lemonade-pi-plugin@main

Local (development)

Symlink the repo into Pi's extension directory:

git clone https://github.com/lemonade-sdk/lemonade-pi-plugin.git
cd lemonade-pi-plugin
./scripts/install.sh

The installer creates ~/.pi/agent/extensions/lemonade-provider as a symlink to the repo.

Usage

  1. Make sure a Lemonade server is running somewhere reachable. On the same machine: lemond (defaults to port 13305). On the LAN: any host already running lemond will be discovered automatically.
  2. Start Pi: pi
  3. Type /login → pick Lemonade from the selector.
  4. The extension prompts you in order:
    • Server selection — if one server was found, press Enter to accept; if multiple, type the number; if none, type a URL (default: http://localhost:8000).
    • API keyDo not leave this blank. Enter a dummy value (e.g., lemonade) or paste your actual key. Even if your local server does not enforce authentication, the underlying Pi client wrapper requires a non-empty string to pass its internal validation rules.
  5. After verification, the extension registers every model the server reports, and it appears in Pi's model picker under "Lemonade".

Discovery details

Lemonade broadcasts a JSON beacon every ~1s on UDP 13305:

{"service":"lemonade","hostname":"my-host","url":"http://192.168.1.5:13305/api/v1/"}

The extension binds to 0.0.0.0:13305 with SO_REUSEADDR (so it coexists with lemonade scan running in parallel) and listens for ~2.5 seconds during login. Both loopback and LAN broadcasts are captured.

Admin command: /lemonade

Once logged in, you can manage the connected server without leaving Pi:

Command What it does
/lemonade status Server health: version, currently loaded model, all loaded models, WebSocket port
/lemonade health Server health (rich): detailed display with backend info, recipe options, model limits
/lemonade models / list List every model the server knows about, with size, recipe, and context window
/lemonade load <id> Load a model into memory (POST /api/v1/load)
/lemonade unload [id] Unload one model, or all loaded models if no id
/lemonade pull <id> Download a model (POST /api/v1/pull)
/lemonade delete <id> Delete a model from disk (POST /api/v1/delete)
/lemonade refresh Re-fetch the model list and re-register the provider in Pi
/lemonade discover Print every Lemonade server visible via beacon + HTTP scan
/lemonade change-ctx <ctx_size> [model] Change context size for a loaded model (supports 32k, 64k, 1m, etc.)
/lemonade tune <id> Probe a model's real capabilities (thinking, vision) against the running server, fetch the checkpoint's embedded GGUF sampling metadata, and write a provenance-tracked entry to the per-model catalog

Per-model catalog

Model capabilities and tuning parameters are driven by a per-model catalog — no name heuristics. Models with a catalog entry get exactly what is written; models without an entry get plain upstream behavior (no capability changes, no parameter injection).

Two tiers, merged per model id (user tier wins field-by-field):

  1. User tier~/.pi/agent/model-params.json (override the path with LEMONADE_PARAMS_FILE). Optional. Recognized models (present in examples/model-params.example.json) are seeded here on first use, one model at a time — the model of the request is the only reliable "current model" signal, so seeding follows what you actually run, never a bulk preload. Other models are created with /lemonade tune <id> (probes the running server) or by hand. Unknown models (neither tier) keep sane defaults: recipe-keyword reasoning detection in model sync, plain pi pass-through for tuning — nothing is invented.
  2. Plugin tierlib/model-params.json in this package (empty by design — shipped entries live in examples/model-params.example.json for reference). A first-use seed also refreshes models-store.json (debounced) so the seeded model's reasoning flag — and therefore its available thinking levels — apply without a pi restart.

/lemonade tune <id> writes only what it sourced or proved: probe results for reasoning/vision, and the checkpoint's embedded general.sampling.* values (read from the GGUF file via its HuggingFace pointer) for the sampling row. Every written field is recorded in an _meta provenance block; anything else (budgets, response ceiling, off-switch params) stays unset until you add it — unset fields mean "server defaults stand".

See the example file for the full entry schema (partial rows allowed).

How it works

The extension registers a Pi provider named lemonade with an oauth block:

  • oauth.name = "Lemonade" — what shows in /login.
  • oauth.login(callbacks) — runs the discovery + prompt flow above.
  • oauth.refreshToken(creds) — re-fetches the model list and re-registers the provider, so newly pulled models show up after a Pi reload without re-running /login.
  • oauth.getApiKey(creds) — returns the stored API key for the Authorization: Bearer … header.

The connection details (baseUrl, apiKey, serverName) are encoded as JSON in the OAuth refresh field. Pi handles persistence in ~/.pi/agent/auth.json.

The Pi-side API call goes through Lemonade's OpenAI-compatible /v1/chat/completions endpoint. The extension uses api: "openai-completions" in the provider config.

Project layout

lemonade-pi-plugin/
├── extensions/
│   └── index.ts          Extension entry point (thin, imports from lib/)
├── lib/
│   ├── types.ts          Shared type/interface definitions
│   ├── constants.ts      Provider IDs, ports, TTLs
│   ├── credentials.ts    OAuth credential encoding/decoding
│   ├── url-helpers.ts    URL building and auth header helpers
│   ├── discovery.ts      UDP beacon + HTTP port scanning
│   ├── http.ts           /health and /models API calls
│   ├── models.ts         Model mapping (Lemonade → Pi shape)
│   ├── provider.ts       Provider (re-)registration
│   ├── oauth.ts          /login OAuth flow
│   ├── health.ts         Rich health formatter (used by /lemonade health)
│   ├── change-ctx.ts     Context size change for loaded models
│   └── admin.ts          /lemonade admin command
├── scripts/
│   ├── install.sh        Symlink the repo into ~/.pi/agent/extensions/
│   └── publish.sh        npm version bump + publish
├── package.json          Pi package manifest (pi.extensions field)
├── tsconfig.json
└── README.md

pi.extensions in package.json tells Pi which file to load. No build step required — Pi loads .ts directly via jiti.

Troubleshooting

"Lemonade" doesn't appear in /login. Restart Pi (or /reload). Confirm the symlink exists: ls -la ~/.pi/agent/extensions/lemonade-provider.

No servers discovered. Confirm lemond is running: curl http://localhost:13305/api/v1/health. Confirm port 13305 isn't blocked by a local firewall (UDP). The HTTP fallback should still find a localhost server even if the beacon is blocked.

Models don't appear in the picker after login. Run /lemonade refresh. If still empty, check /lemonade models — if the server reports models there but Pi doesn't show them, your provider model list might be stale; a full Pi restart will re-trigger the OAuth refresh.

API key isn't being sent. Re-run /login and pick Lemonade again, paste the key when prompted. Verify with /lemonade status — if it returns 401, the key is wrong; if it returns the server health, you're authenticated.

License

MIT

About

Lemonade Pi Agent CLI Plugin

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages