A Claude Code skill + CLI that orders a pizza for you over a real phone call, end-to-end via voice.
Trigger from Claude Code with a natural-language command. The skill:
- Calls you via voice agent and asks what pizza you want (in Italian).
- Searches the web for a pizzeria near your address that's open now.
- Calls the pizzeria via voice agent and places the order on your behalf.
- Reports back to Claude Code (transcripts + outcome).
No daemons, no scheduling. One command, one order.
[Paolo in terminal]
$ claude
> Ordina una pizza
[Claude Code]
invokes skill `pizzaiolo`
β spawns CLI: pizza order
[CLI step 1 β call Paolo]
Vapi outbound call β PAOLO_PHONE
Agent (IT): "Ciao Paolo, che pizza vuoi stasera?"
Paolo (voice): "Una diavola e una margherita, consegna a casa"
Agent confirms, hangs up
β structured order JSON returned via Vapi
[CLI step 2 β find pizzeria]
Google Places API: type=restaurant, keyword=pizzeria,
location=HOME_ADDRESS, radius=1500m, opennow=true
Rank by rating Γ review_count, filter has_phone_number
Pick top 1 (with 2 fallbacks)
[CLI step 3 β call pizzeria]
Vapi outbound call β pizzeria number
Agent (IT): scripted order, asks for total + ETA,
gives delivery address, confirms
β transcript + outcome returned
[CLI step 4 β report]
Print summary to terminal:
β Ordered from Pizzeria Spontini (via Padova 12)
β 1Γ Diavola, 1Γ Margherita
β Total: β¬18, ETA: 35 min
β Confirmed by: Marco (pizzeria)
Save full transcripts to ~/.pizzaiolo/orders/<timestamp>.json
If the pizzeria call fails (no answer, refuses, doesn't deliver to address), the CLI falls back to the next candidate. Max 3 attempts, then the order is reported as failed.
# 1. Install dependencies
npm install
# 2. Copy env template and fill in your keys
cp .env.example .env
$EDITOR .env
# 3. Build
npm run build
# 4. Place an order
npm start order
# Or run from source without building
npm run dev orderFor development, use --dry-run to test the full flow without real phone calls:
npm run dev order -- --dry-run| Flag | Description |
|---|---|
--dry-run |
Simulate the full ordering flow without making real Vapi calls. Google Places is still called for real. |
--skip-ask |
Skip calling Paolo and reuse the items from the last saved order. |
--candidate <n> |
Pick the Nth pizzeria from search results (1-based) instead of the top match. |
-v, --verbose |
Verbose debug output for troubleshooting. |
Examples:
pizza order # full live run
pizza order --dry-run # safe test, no calls
pizza order --candidate 2 # use 2nd-ranked pizzeria
pizza order --skip-ask # reuse last order
pizza order --verbose # debug modePure local CLI. No server, no cloud webhooks β the CLI polls Vapi for call completion.
βββββββββββββββββββββββββββββββββββββββββββββββ
β Claude Code (terminal) β
β user: "Ordina una pizza" β
ββββββββββββββββββββ¬βββββββββββββββββββββββββββ
β invokes skill
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β pizza CLI (Node.js + TypeScript) β
β ββββββββββββββββ ββββββββββββββββ β
β β OrderTaker β β Locator β β
β β (calls you) β β (Places API) β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β β β β
β ββββββββββ¬βββββββββ β
β βΌ β
β ββββββββββββββββ β
β β Placer β β
β β (calls shop) β β
β ββββββββ¬ββββββββ β
β βΌ β
β ββββββββββββββββ β
β β Reporter β β
β ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββ
β β
βΌ βΌ
Vapi API Google Places
(voice agent) API
OrderTaker β calls Paolo (src/ordertaker.ts)
- Vapi outbound call to
PAOLO_PHONE. - Voice agent prompt (Italian): friendly, brief, asks pizza type(s), size, delivery vs pickup, any notes.
- Returns structured output:
{ "items": [{ "name": "Diavola", "size": "media", "notes": "" }], "delivery": true, "user_confirmed": true } - If
user_confirmed=falseor Paolo cancels β exits cleanly.
Locator β finds pizzeria (src/locator.ts)
- Google Places API
nearbySearch. - Query params:
type=restaurant,keyword=pizzeria,location=HOME_ADDRESS,radius=1500,opennow=true. - Filters:
business_status=OPERATIONAL,rating >= 4.0, has phone number. - Ranks by
rating * log(user_ratings_total). - Returns top 3 candidates with
{name, phone, address, rating}.
Placer β calls pizzeria (src/placer.ts)
- Vapi outbound call to candidate's phone.
- Voice agent prompt (Italian): identifies as "ordine per Paolo", reads items, confirms delivery address, asks total + ETA, confirms.
- Returns structured output:
{ "accepted": true, "total_eur": 18.0, "eta_minutes": 35, "operator_name": "Marco", "rejection_reason": null } - If
accepted=falseβ tries next candidate (up to 3 total).
Reporter (src/reporter.ts)
- Pretty-prints result to stdout.
- Saves full payload β including both call transcripts β to
~/.pizzaiolo/orders/<ISO>.jsonfor debugging.
- Runtime: Node.js 20, TypeScript
- CLI framework:
commander - Voice: Vapi β outbound calls + structured extraction, Italian support.
- Pizzeria search: Google Places API (v1).
- Config:
.envat project root. - Persistence: flat JSON files under
~/.pizzaiolo/. No DB.
Copy .env.example to .env and fill in:
VAPI_API_KEY= # from https://vapi.ai/dashboard
VAPI_PHONE_NUMBER_ID= # Vapi-provisioned IT number for outbound
VAPI_ORDERTAKER_ASSISTANT_ID= # pre-built in Vapi dashboard
VAPI_PLACER_ASSISTANT_ID= # pre-built in Vapi dashboard
PAOLO_PHONE=+39... # E.164 format
HOME_ADDRESS=Via ..., Milano
GOOGLE_PLACES_API_KEY=
All variables are required β the CLI fails fast on startup if any are missing.
The repo ships a Claude Code skill at .claude/skills/pizzaiolo/SKILL.md. It triggers on phrases like:
ordina una pizza,ordiniamo la pizza,voglio una pizzaho fame,prendi una pizza,pizza timeorder a pizza,get me a pizza
Dictation β text β skill match β runs pizza order.
| Item | Cost |
|---|---|
| Vapi call to Paolo (~1 min) | ~β¬0.15 |
| Vapi call to pizzeria (~2 min, possibly 2 retries) | ~β¬0.30β0.90 |
| Google Places (search + details) | ~β¬0.03 |
| Total | ~β¬0.50β1.10 per order |
Plus, you know, the pizza.
- Italian pizzaiolo on the phone is the hard eval. Expect to iterate the Placer prompt heavily. First few orders: use
--dry-runand call yourself pretending to be the pizzeria to test the agent. - Phone number quality from Google Places is mixed β some entries have outdated numbers. Mitigation: cross-check with the pizzeria's website if listed.
- Payment: most Italian pizzerie are cash/card on delivery. The agent doesn't handle payment, just places the order.
- Legal: outbound automated calls in IT β calling a business to place a legitimate order is fine. Do not point this at consumers.
- Voice triggering: Claude Code's skill matching is text-based. Dictation β text β skill match works as long as the skill
descriptionincludes the Italian trigger phrases.
- Scheduling / cron pings
- Multiple recipients
- Tracking the delivery
- Anything other than Milan
- English-language pizzerias
- Payment handling
- Reordering UI
