Amy
Recipes

Build a mobile app with Claude Code

For non-developers. This recipe assumes you have no coding experience and are using Claude Code (the terminal AI agent) to drive the work. You don't need to understand any of the code Claude generate…

For non-developers. This recipe assumes you have no coding experience and are using Claude Code (the terminal AI agent) to drive the work. You don't need to understand any of the code Claude generates, but you do need to understand what each step does and what "done" looks like. This page tells you exactly that.

What you'll build: a React Native mobile app for iOS and Android that lets users sign in, connect their wearable, upload labs, and chat with Amy, same agent as the CLI, just a different surface.

Time: ~2 hours, mostly waiting on installs and builds.

Cost: $0 (Expo's free tier covers everything you need to get on your phone).


Before you start

You need three things installed once. If you've done any of them before, skip ahead.

ToolWhat it isHow to install
BunA fast JavaScript runtime (what the Amy CLI runs on)curl -fsSL https://bun.sh/install | bash
Claude CodeThe AI agent that will write your codenpm install -g @anthropic-ai/claude-code then claude login
Expo Go on your phoneThe app that runs your in-progress mobile app, so you can see it without building anythingDownload from the App Store / Play Store

You also need a running Amy backend. If you don't have one yet, follow Deploying to Cloudflare first. You should end that guide with:

  • A URL like https://api.amy.health (or https://amy-api.YOUR.workers.dev).
  • An API key you can use to authenticate as yourself.

If those two things exist, you're ready.


Step 0, Make a working directory

Open a new terminal and run:

mkdir ~/amy-mobile && cd ~/amy-mobile
claude

You're now inside Claude Code, in an empty folder. Everything from here on is a prompt you paste into Claude Code's chat. Each one is a self-contained instruction, Claude will ask permission before it does anything destructive.


Step 1, Scaffold the app

Paste this into Claude Code:

Scaffold a new Expo app in this directory called amy-mobile, using TypeScript, the Expo Router template (file-based routing, tabs), and Bun as the package manager. After scaffolding, do not start the dev server, just confirm it built and show me the directory tree.

What "done" looks like:

  • A new folder named amy-mobile/ exists inside ~/amy-mobile/.
  • It contains an app/ directory with files like _layout.tsx and (tabs)/index.tsx.
  • Claude reports "scaffold complete" or similar.

If it goes wrong: tell Claude what error you see. Common one: "bun not found", that means Bun isn't installed (re-do the prerequisites).


Step 2, Install the Amy SDK

Paste this:

Move into the amy-mobile directory. Install @amy/sdk (the TypeScript SDK for the Amy backend) from npm with bun. Also install react-native-event-source for SSE streaming and expo-secure-store for storing the API key safely on the device. Verify all three were installed by checking package.json.

What "done" looks like:

  • package.json shows @amy/sdk, react-native-event-source, and expo-secure-store under dependencies.

Step 3, Set up the API client

Paste this: (replace <YOUR_API_URL> with your backend URL from the prerequisites)

Create a file at lib/amy.ts that:

  • Reads the API base URL from EXPO_PUBLIC_AMY_API_URL, defaulting to <YOUR_API_URL>.
  • Reads the API key from expo-secure-store under the key amy.api_key.
  • Exports a function getAmy() that returns an initialized Amy client (new Amy({ apiKey, baseUrl })).
  • Exports a function signIn(apiKey: string) that stores the key in secure storage.
  • Exports a function signOut() that deletes the key.
  • Also create a .env file with EXPO_PUBLIC_AMY_API_URL=<YOUR_API_URL>.

What "done" looks like:

  • lib/amy.ts exists and exports those four things.
  • .env exists in the project root.
  • Claude added .env to .gitignore (if not, ask it to).

Step 4, Build the sign-in screen

Paste this:

Create a sign-in screen at app/sign-in.tsx that:

  • Has a single text input for "API Key" and a "Sign in" button.
  • On submit, calls signIn(apiKey) from lib/amy.ts, then navigates to (tabs)/index.
  • Shows a loading spinner while submitting.
  • Shows an error message if the key is invalid (test by calling amy.me.get() after sign-in; if it throws, show the error).
  • Use only React Native primitives (View, Text, TextInput, TouchableOpacity, ActivityIndicator), no UI library.
  • Style it simply: white background, centered content, 16px padding.

Also update app/_layout.tsx so unauthenticated users are redirected to /sign-in (check expo-secure-store for the key on mount).

What "done" looks like:

  • app/sign-in.tsx exists.
  • app/_layout.tsx redirects when no key is present.

Try it now. Run bun start inside amy-mobile/. Scan the QR code with the Expo Go app on your phone. You should see the sign-in screen. Type your API key, tap Sign in, and the app should navigate away (even if the next screen is empty for now).


Step 5, Build the chat screen (the heart of the app)

Paste this:

Replace app/(tabs)/index.tsx with a chat screen that:

Layout:

  • A scrollable message list filling the screen.
  • A text input pinned to the bottom with a "Send" button.
  • User messages right-aligned, blue background, white text.
  • Amy messages left-aligned, gray background, dark text.
  • While Amy is thinking, show the streaming text in real time (italic, slightly dimmer until complete).

Behavior:

  • On send: append the user message to local state, clear the input, call amy.turns.create({ messages }) to start a turn, then subscribe to amy.turns.stream(turn.id).
  • Handle these event types from the stream (see Amy's streaming docs):
    • turn.started → add an empty Amy bubble.
    • agent.thought with delta → append the delta to the current Amy bubble.
    • turn.completed → mark the bubble done; show the final result.answer text.
    • turn.failed → show the error message in the bubble in red.
  • Disable the input while a turn is running.
  • Keep all messages in component state for now, no persistence yet.

Use react-native-event-source for SSE; the SDK exposes the stream URL via amy.turns.streamUrl(id). The SDK's auth header must be passed manually since EventSource doesn't accept headers natively, use the SDK helper amy.turns.eventSourceInit(id) which returns { url, headers } and pass them to the EventSource constructor.

What "done" looks like:

  • You can type a question, tap Send, and watch Amy's answer stream in word by word.

Try it now. Reload Expo Go (shake the phone, tap "Reload"). Ask "what's my average resting heart rate?", if your backend has data, you should see Amy's answer appear progressively over ~30-90 seconds.


Step 6, Add the Connect Wearable button

Paste this:

Create a screen at app/(tabs)/sources.tsx that:

  • Lists currently connected sources (call amy.sources.list()).
  • Has a "Connect a wearable" button.
  • On tap, calls amy.sources.terra.connect() to get a widget_url, then opens it in expo-web-browser's openAuthSessionAsync.
  • After the user completes the OAuth and the browser closes, refreshes the source list.
  • Add a tab icon for "Sources" in app/(tabs)/_layout.tsx.

Install expo-web-browser if it isn't already.

What "done" looks like:

  • Tapping "Connect a wearable" opens the Terra widget in a browser.
  • After connecting (try with Whoop), the source appears in the list.

Step 7, Add the Upload Labs button

Paste this:

Create a screen at app/(tabs)/labs.tsx that:

  • Lists previously uploaded labs (call amy.labs.list()), showing filename, upload date, and status (processing / parsed / failed).
  • Has an "Upload labs" button.
  • On tap, uses expo-document-picker to let the user pick a PDF or image (jpg/png), then calls amy.labs.upload({ file }). The SDK handles the multipart form correctly.
  • Shows a progress indicator during upload.
  • Refreshes the list after upload completes.
  • Polls every 5s for status changes on items that are still "processing".
  • Add a tab icon for "Labs" in app/(tabs)/_layout.tsx.

Install expo-document-picker if it isn't already.

What "done" looks like:

  • You can upload a lab PDF, see it appear with "processing", and after ~30s see it flip to "parsed".

Step 8, Add the Memory screen

Paste this:

Create a screen at app/(tabs)/memory.tsx that:

  • Lists all facts Amy remembers (call amy.memory.list()), grouped by category (goal, insight, preference, history).
  • Each row shows the fact text and a small trash icon to delete (calls amy.memory.delete(id)).
  • Pull-to-refresh.
  • Has a "+" button in the header that opens a modal to add a new fact (amy.memory.create({ text, category })).
  • Add a tab icon for "Memory" in app/(tabs)/_layout.tsx.

What "done" looks like:

  • Memory screen shows your saved facts from the backend.

Step 9, Polish and test

Paste this:

Do a polish pass:

  • Add an Amy logo placeholder in the header (just text "Amy" for now).
  • Ensure all screens handle the loading state (spinner) and the error state (a small red banner with the message).
  • Test the full flow on your phone end-to-end: sign in → connect wearable → upload a lab → ask a question that uses both data sources → see the answer stream.
  • Report any bugs you hit.

What "done" looks like:

  • The four-tab app (Chat, Sources, Labs, Memory) works end-to-end on your phone via Expo Go.

Step 10, Ship it (optional)

You now have a working app running on your phone via Expo Go. To put it in someone else's hands without making them install Expo Go, you have two options:

OptionWhat it gives youCostEffort
EAS UpdatePush updates to anyone who has your app installedFree for small teams5 min
EAS BuildGenerate .ipa (iOS TestFlight) and .apk (Android sideload) filesFree tier; paid past 30 builds/mo30 min

Either is a one-shot Claude Code prompt. When you're ready, ask:

Set up EAS Build for this project so I can distribute the iOS and Android builds to a few testers. Walk me through every CLI prompt.


Common problems and fixes

"Network request failed" on every API call

Your phone (running Expo Go) can't reach localhost. You need a publicly reachable backend URL. Either:

  • Use your deployed Cloudflare backend (recommended), update EXPO_PUBLIC_AMY_API_URL in .env.
  • Tunnel localhost via bunx cloudflared tunnel --url http://localhost:8787 if you're running the backend locally.

Stream stops after ~30 seconds

Some networks idle out SSE connections. The SDK auto-reconnects with Last-Event-Id, but if you see this, mention it to Claude, they can add explicit reconnect logging to confirm it's recovering.

"Invalid API key" on sign-in

The API key you're using is wrong, expired, or for a different backend. Get a fresh one with amy whoami --print-key from the CLI.


What you've built

┌────────────────────────────────────┐
│   Your phone (iOS or Android)      │
│   Expo Go app running amy-mobile   │
└──────────────────┬─────────────────┘
                   │  HTTPS

┌────────────────────────────────────┐
│   api.amy.health  (Cloudflare)     │
│   Same backend the CLI uses.       │
└────────────────────────────────────┘

The mobile app is a thin client over the Amy backend. The agent doesn't run on the phone, it runs on Cloudflare. The phone just shows the results.

This is the whole point of the design: swap the surface, keep the brain. Tomorrow you can build a web app or a Slack bot the same way.


Where to next

On this page