Skip to content

Latest commit

Β 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

claude-pizza

A Claude Code skill + CLI that orders a pizza for you over a real phone call, end-to-end via voice.

claude-pizza workflow

Trigger from Claude Code with a natural-language command. The skill:

  1. Calls you via voice agent and asks what pizza you want (in Italian).
  2. Searches the web for a pizzeria near your address that's open now.
  3. Calls the pizzeria via voice agent and places the order on your behalf.
  4. Reports back to Claude Code (transcripts + outcome).

No daemons, no scheduling. One command, one order.


User flow

[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.


Quick start

# 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 order

For development, use --dry-run to test the full flow without real phone calls:

npm run dev order -- --dry-run

CLI options

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 mode

Architecture

Pure 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

Components

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=false or 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>.json for debugging.

Tech stack

  • Runtime: Node.js 20, TypeScript
  • CLI framework: commander
  • Voice: Vapi β€” outbound calls + structured extraction, Italian support.
  • Pizzeria search: Google Places API (v1).
  • Config: .env at project root.
  • Persistence: flat JSON files under ~/.pizzaiolo/. No DB.

Configuration

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.


Claude Code skill

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 pizza
  • ho fame, prendi una pizza, pizza time
  • order a pizza, get me a pizza

Dictation β†’ text β†’ skill match β†’ runs pizza order.


Cost estimate per 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.


Risks & open questions

  1. Italian pizzaiolo on the phone is the hard eval. Expect to iterate the Placer prompt heavily. First few orders: use --dry-run and call yourself pretending to be the pizzeria to test the agent.
  2. Phone number quality from Google Places is mixed β€” some entries have outdated numbers. Mitigation: cross-check with the pizzeria's website if listed.
  3. Payment: most Italian pizzerie are cash/card on delivery. The agent doesn't handle payment, just places the order.
  4. Legal: outbound automated calls in IT β€” calling a business to place a legitimate order is fine. Do not point this at consumers.
  5. Voice triggering: Claude Code's skill matching is text-based. Dictation β†’ text β†’ skill match works as long as the skill description includes the Italian trigger phrases.

Out of scope (v1)

  • Scheduling / cron pings
  • Multiple recipients
  • Tracking the delivery
  • Anything other than Milan
  • English-language pizzerias
  • Payment handling
  • Reordering UI

About

Even Claudio needs good pizza 🀌

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages