- TypeScript 97%
- JavaScript 2.7%
- Dockerfile 0.3%
| docs | ||
| patches | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| Dockerfile | ||
| fly.toml | ||
| ideas.md | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| tsconfig.json | ||
SMS LLM Agent
A lightweight Node.js server that receives SMS via 46elks, queries Kimi K2.6 via Clarifai with web search, and replies via SMS.
How It Works
- User texts your 46elks virtual number
- 46elks POSTs to your
/elks/sms?token=SECRETwebhook - Server calls Kimi K2.6 via Clarifai with a
web_searchtool - Server sends the reply back via 46elks API
Prerequisites
- 46elks account with a virtual number
- Clarifai account with a Personal Access Token
- fly.io account (or any host with a public URL)
Setup
1. Configure Your 46elks Number
In the 46elks dashboard:
- Go to Numbers
- Set
sms_urlto:https://your-app.fly.dev/elks/sms?token=YOUR_SECRET_TOKEN
2. Set Secrets
If you have a .env file, import it directly:
fly secrets import < .env
Or set them individually:
fly secrets set \
ELKS_USERNAME="your_46elks_username" \
ELKS_PASSWORD="your_46elks_password" \
CLARIFAI_PAT="your_clarifai_pat" \
WEBHOOK_TOKEN="your_random_secret_token"
Note:
fly secrets importcan choke on quoted values (e.g.KEY="value"). Remove quotes from.envvalues if you hit issues.
3. Deploy
fly deploy
4. Test
Text your 46elks number. You should get an LLM-powered reply within a few seconds.
Local Development
cp .env.example .env
# Fill in your credentials
pnpm install
pnpm dev
Testing Without Sending SMS (Free)
You can test the LLM and web search logic without spending money on SMS messages. Only CLARIFAI_PAT and WEBHOOK_TOKEN are required.
# Start without 46elks credentials — SMS sending is automatically disabled
CLARIFAI_PAT="..." WEBHOOK_TOKEN="test" pnpm dev
curl -X POST "http://localhost:8080/elks/sms?token=test" \
-d "from=+4712345678" \
-d "to=+46766861001" \
-d "message=what is the weather in Oslo"
The reply is logged to console as [SMS DISABLED] Reply to +4712345678: ... — no actual SMS is sent.
Available Tools
The agent has access to these tools:
| Tool | Description | Default |
|---|---|---|
calculate |
Math expressions and unit conversions | — |
get_weather |
Current weather + forecast via Open-Meteo | 7 days (up to 16) |
convert_currency |
Live exchange rates via ECB/Frankfurter | — |
get_directions |
Turn-by-turn directions via OpenRouteService | foot-walking |
web_search |
DuckDuckGo/Brave web search | — |
web_fetch |
Fetch full text of a URL | — |
Weather understands natural language like "tomorrow", "next week", or "2 weeks" and requests the appropriate forecast length automatically.
Full Integration Test (With SMS)
Use ngrok to expose localhost:
ngrok http 8080
Then temporarily set your 46elks sms_url to the ngrok URL.
Architecture Notes
- One-shot: No conversation history (stateless). Each SMS is independent.
- Synchronous processing: The webhook handler waits for the LLM response before returning HTTP 200. This keeps the fly.io machine alive for the duration (~2-4 seconds).
- Auto-stop enabled: Machines stop after idle to save ~$5/mo. Cold start with Node.js is typically 1-2 seconds.
- Retry safety: 46elks retries webhooks for 6+ hours if non-200. If a cold start causes a timeout, the retry will likely succeed.
- Web search: Uses DuckDuckGo HTML interface directly (no API key, no browser). Inspired by open-websearch.
Costs (Estimated)
| Component | Monthly Cost |
|---|---|
| 46elks Swedish VMN | €3 |
| fly.io (shared-cpu-1x, 256MB, auto-stop) | ~$1-2 |
| Clarifai/Kimi API | usage-based (~$0.95/1M input tokens) |
| 46elks SMS (Norway) | €0.064 per message |
System Prompt
The agent is instructed to:
- Keep replies under 300 characters
- Avoid markdown, URLs, emojis
- Use plain text only
- Search the web when needed
- Be conversational
License
MIT