The NemlAI CLI gives you four ways to interact with the platform:
- Web Terminal — a browser-based terminal at /cli
- Local Terminal — install via
pip,yay, oraptand runnemlaifrom your own machine - HTTP API — REST endpoints for
curl, scripts, and AI agents - Python SDK — import
nemlaiin your Python scripts for programmatic access
Quick Start
1. Create an account
curl -X POST https://nemlai.milops.org/cli/api/auth/signup \
-H "Content-Type: application/json" \
-d '{
"name": "Simon",
"email": "[email protected]",
"password": "your-password",
"nemlig_email": "[email protected]",
"nemlig_password": "nemlig-password"
}'
2. Log in and get a token
curl -X POST https://nemlai.milops.org/cli/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "your-password"}'
# Response:
STATUS: ok
TOKEN nemlai_abc123...def456
EXPIRES 2026-03-31T00:00:00Z
3. Use the token
# Save your token
export NEMLAI_TOKEN="nemlai_abc123...def456"
# Use it with any endpoint
curl https://nemlai.milops.org/cli/api/whoami \
-H "Authorization: Bearer $NEMLAI_TOKEN"
Web Terminal
Open /cli in your browser. No installation needed. You get a full terminal with command history (up/down arrows), tab completion, and Ctrl+C to cancel.
The web terminal uses a WebSocket connection, so commands execute instantly with live feedback.
Local Terminal
Install the nemlai package and get the same terminal experience
running on your own machine. All logic stays on the server — the local
CLI is just a thin client that sends commands over HTTP.
Install
# PyPI (all platforms)
pip install nemlai
# Arch Linux (AUR)
yay -S nemlai
# Ubuntu / Debian
sudo add-apt-repository ppa:milops/nemlai
sudo apt install nemlai
Run
# Start the interactive terminal
nemlai
# Or run a single command
nemlai help
nemlai whoami
nemlai basket items
Features
- Same commands as the web terminal
- Tab completion and command history (persisted across sessions)
- Secure login — password input is hidden, token saved to
~/.config/nemlai/token - Colour-coded output (green for success, red for errors)
- Works with
NEMLAI_TOKENenvironment variable for scripts - Single-command mode for piping:
nemlai basket items | grep milk
Configuration
# Point to a different server
nemlai --url http://localhost:8090
# Use a specific token
nemlai --token nemlai_abc123...
# Or use environment variables
export NEMLAI_TOKEN="nemlai_abc123..."
export NEMLAI_URL="http://localhost:8090"
nemlai
Config is stored in ~/.config/nemlai/:
| File | Purpose |
|---|---|
token | Saved auth token (chmod 600) |
base_url | Custom server URL |
history | Command history |
HTTP API
All endpoints live under /cli/api/. Responses are plain text
with a consistent format:
STATUS: ok
<response body>
or on error:
STATUS: error
ERROR <message>
HINT <suggestion>
Public endpoints (no auth required)
| Endpoint | Description |
|---|---|
GET /cli/api/health | Health check |
GET /cli/api/help | List all commands |
POST /cli/api/auth/signup | Create account |
POST /cli/api/auth/login | Log in, get token |
Authenticated endpoints
Include Authorization: Bearer <token> header.
| Endpoint | Description |
|---|---|
GET /cli/api/whoami | Current user info |
POST /cli/api/auth/logout | Invalidate token |
GET /cli/api/household | Household details |
POST /cli/api/orders/refresh | Refresh order history |
GET /cli/api/basket/status | Basket unlock status |
POST /cli/api/basket/generate | Generate a smart basket |
GET /cli/api/basket/items | List basket items |
POST /cli/api/basket/add | Add item to basket |
POST /cli/api/basket/remove | Remove item |
POST /cli/api/basket/adjust | Adjust item quantity |
POST /cli/api/basket/send | Send basket to Nemlig |
POST /cli/api/basket/search | Search products |
GET /cli/api/settings | Show settings |
POST /cli/api/settings | Update a setting |
GET /cli/api/preferences | Show food preferences |
POST /cli/api/preferences | Update a preference |
POST /cli/api/v2/analytics/spend | Aggregate spending by product/category over a date range |
POST /cli/api/v2/analytics/top | Top N products or categories by spend |
POST /cli/api/v2/analytics/trend | Time-series spend buckets (day/week/month) |
POST /cli/api/v2/analytics/drill | Raw order-line breakdown for a product or order |
GET /cli/api/achievements | List achievements |
GET /cli/api/notifications/status | Push notification status |
POST /cli/api/notifications/test | Send test notification |
Generic command endpoint
You can also send any command as text:
curl -X POST https://nemlai.milops.org/cli/api/command \
-H "Authorization: Bearer $NEMLAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command": "basket generate --size medium --date 2026-03-15"}'
/cli/api/command
endpoint accepts any command as plain text. This is the simplest integration
path — just send commands the same way you would type them in the terminal.
Python SDK
Install
pip install nemlai
Usage
from nemlai import NemlAI
# Log in (returns a client with token)
client = NemlAI.login("[email protected]", "password")
# Or use an existing token
client = NemlAI(token="nemlai_abc123...")
# Account
me = client.whoami()
print(me) # {'user': 'Simon', 'email': '...', ...}
# Generate a basket
result = client.basket.generate(size="medium", date="2026-03-15")
print(result)
# List basket items
items = client.basket.items()
# Search and add a product
results = client.basket.search("oat milk")
client.basket.add(query="oat milk")
# Remove or adjust items
client.basket.remove(item=3)
client.basket.adjust(item=7, quantity=2)
# Send basket to Nemlig
url = client.basket.send()
print(url) # https://nemlig.com/basket?skus=...
# Orders
client.orders.refresh(password="nemlig-password")
# Settings and preferences
client.settings.show()
client.preferences.show()
# Household
client.household.info()
Custom base URL
# For local development
client = NemlAI(token="nemlai_...", base_url="http://localhost:8090")
All Commands
Account
| Command | Description |
|---|---|
signup | Create a new account |
login | Log in and get API token |
logout | Invalidate your token |
whoami | Show your account info |
profile update | Update display name or Nemlig email |
password change | Change your app password |
password reset-nemlig | Request a Nemlig password reset |
account delete | Delete your account |
Basket (Generer kurv)
| Command | Description |
|---|---|
basket status | Check if basket generation is unlocked |
basket generate --size <s> [--date <d>] | Generate a smart basket |
basket items | List items in current basket |
basket add --query <q> | Search and add item |
basket remove --item <#> | Remove item by number |
basket adjust --item <#> --quantity <n> | Set item quantity |
basket send | Send basket to Nemlig |
basket search --query <q> | Search products |
Orders
| Command | Description |
|---|---|
orders refresh --password <p> | Refresh your order history |
orders refresh-member --email <e> --password <p> | Refresh household member's orders |
Settings & Preferences
| Command | Description |
|---|---|
settings show | Show current settings |
settings update --key <k> --value <v> | Update a setting |
preferences show | Show food preferences |
preferences update --key <k> --value <v> | Update a preference |
Analytics
Ask questions about your own spending. Filters accept either repeated flags or a comma-separated string, so multi-product or multi-category reports take one call instead of N.
| Command | Description |
|---|---|
analytics guide | Local cheat sheet — date grammar, scopes, formats, multi-value syntax |
analytics spend [--product <name>]... [--category <name>]... [--exclude <name>]... [--last <expr>] [--compare previous-period] |
Aggregate spend over a date range. --product and --category are mutually exclusive but each accepts multiple values. Use --exclude to filter out noise. |
analytics top <products|categories> [--last <expr>] [--limit <n>] [--sub-categories] | Top N products or categories by spend. |
analytics trend [--product <name>]... [--category <name>]... --bucket <day|week|month> [--last <expr>] | Spend over time as buckets; renders with sparklines in table mode. |
analytics drill [--product <name>]... [--category <name>]... [--order <id>] [--last <expr>] [--limit <n>] | Raw order-line entries for a product, category, or specific order. |
Multi-value examples — both forms are equivalent and combine in one round-trip:
# Repeated flags (kubectl / gh / git convention; shell-safe for any name)
nemlai analytics spend --last 12m \
--product vandmelon --product jordbær --product hindbær --product gifler
# Comma-separated (ergonomic shortcut)
nemlai analytics spend --last 12m --product "vandmelon,jordbær,hindbær,gifler"
# Categories work the same way
nemlai analytics spend --last 3m --category "Grønt,Frost"
nemlai analytics trend --category Mejeri --category Drikke --bucket month --last 12m
analytics spend --product "jordbær,hindbær" --exclude "Yoghurt,Hindbærtærte" --last 12m— exclude fuzzy-matched substring noiseanalytics spend --exclude-category "Kød & fisk" --last 30d— standalone category exclusion
"Letmælk 1,5%"). For search terms that may contain commas, use repeated flags
rather than the comma-string shortcut. Category names are comma-free, so --category
is always safe with either form.
Date range grammar — --last 7d, --last 30d, --last 3m, --last 12m, --last 2y, this-month, last-month, ytd, this-year, last-year, or --from 2026-01-01 --to 2026-03-31. All terms must be in Danish (Nemlig stores names in Danish: mælk not milk).
Other
| Command | Description |
|---|---|
household info | Show household details + invite code |
notifications status | Push notification status |
notifications test | Send a test notification |
achievements | List your achievements |
Authentication
The CLI uses token-based authentication. Tokens are long-lived (30 days by default) and can be revoked with logout.
For the Web Terminal
Just type login --email [email protected] --password yourpass. The token is stored in your WebSocket session automatically.
For the HTTP API
# Get a token
TOKEN=$(curl -s -X POST https://nemlai.milops.org/cli/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "pass"}' \
| grep TOKEN | awk '{print $2}')
# Use it
curl https://nemlai.milops.org/cli/api/whoami \
-H "Authorization: Bearer $TOKEN"
For the Python SDK
# Login returns a client with token
client = NemlAI.login("email", "password")
# Or reuse a saved token
client = NemlAI(token="nemlai_...")
For AI Agents
The CLI is designed to be easy for autonomous agents to use:
- Every response starts with
STATUS: okorSTATUS: error— easy to parse - Error messages include
HINTlines with the exact command to fix the issue - The generic
/cli/api/commandendpoint accepts free-text commands - No HTML, no JSON — just plain text that's readable and parseable
- The Python SDK provides typed methods if you prefer structured access
Recommended agent flow
# 1. Check health
curl https://nemlai.milops.org/cli/api/health
# 2. Log in once, save the token
curl -X POST https://nemlai.milops.org/cli/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "...", "password": "..."}'
# 3. Generate a basket
curl -X POST https://nemlai.milops.org/cli/api/basket/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"size": "medium", "date": "2026-03-15"}'
# 4. Review and adjust items
curl https://nemlai.milops.org/cli/api/basket/items \
-H "Authorization: Bearer $TOKEN"
# 5. Send to Nemlig
curl -X POST https://nemlai.milops.org/cli/api/basket/send \
-H "Authorization: Bearer $TOKEN"