An end-to-end pipeline where Claude Code discovers app opportunities, builds the apps, tests them visually, and ships them to both app stores. No external AI app builders — Claude Code IS the builder.
AppFactory/
├── CLAUDE.md ← You are here
├── .env ← SUPABASE_ACCESS_TOKEN (gitignored, never commit)
├── .gitignore ← Protects .env files and node_modules
├── Dockerfile ← Agent container (Playwright + Node + Claude CLI + EAS)
├── docker-compose.yml ← Agent + Dashboard containers, shared /data volume
├── .dockerignore ← Excludes node_modules, .env, etc.
├── src/ ← Tools (Node.js, ES modules)
│ ├── orchestrator.js ← THE BRAIN: observe → prioritize → act → learn loop
│ ├── agent.js ← Claude -p wrapper with model routing
│ ├── memory.js ← Persistent knowledge base (memory/*.json)
│ ├── heartbeat.js ← Status + activity logging
│ ├── ship.js ← Full ship pipeline: screenshots → ASO → EAS
│ ├── privacy-policy.js ← Generate privacy policy HTML
│ ├── create-app.js ← Creates app + Supabase project in one command
│ ├── pipeline.js ← Opportunity scanner (discover → score → analyze → rank)
│ ├── stores.js ← Unified Google Play + App Store scraper
│ ├── discover.js ← Phase 1: keyword discovery via autocomplete
│ ├── score.js ← Phase 2: difficulty + traffic scoring
│ ├── analyze.js ← Phase 3: competitor weakness + review pain points
│ ├── niches.js ← Pre-built seed lists (10 categories, 80+ seeds)
│ ├── report.js ← Output formatter (AI markdown / CSV / JSON)
│ ├── test-app.js ← Playwright visual testing (headless, screenshots)
│ ├── dashboard.js ← Cross-app dashboard (analytics, feedback, revenue)
│ └── notify.js ← Send push notifications via Expo Push API
├── config/ ← Default configuration
│ └── default.json ← Template for config.json (copied on first run)
├── dashboard/ ← Web dashboard (Express + WebSocket)
│ ├── Dockerfile ← Dashboard container
│ ├── package.json ← Express + ws
│ ├── server.js ← API routes + WebSocket log streaming
│ └── public/ ← HTML pages (Tailwind CDN, vanilla JS)
│ ├── index.html ← Home: app cards + agent status
│ ├── apps.html ← Single app detail + screenshots
│ ├── log.html ← Real-time agent activity stream
│ ├── settings.html ← API keys + proxy config
│ └── chat.html ← Chat with the agent
├── memory/ ← Agent knowledge base (JSON files)
├── logs/ ← Activity log (activity.jsonl)
├── chat/ ← Agent chat (inbox.json, outbox.json)
├── schemas/ ← Reusable SQL schemas per app type
├── supabase-functions/ ← Edge Function templates
│ └── ai-proxy/index.ts ← Secure AI API proxy for apps
├── output/ ← Scan results + screenshots (gitignored)
├── template/ ← Reusable Expo app skeleton
│ ├── app/ ← Expo Router pages (file-based routing)
│ ├── lib/ ← Supabase client, auth, analytics, feedback, push, AI hooks
│ └── components/ ← Shared UI components
└── apps/ ← Generated apps live here (one folder per app)
└── <app-name>/ ← Each app is a self-contained Expo project
├── .env ← App-specific Supabase keys (auto-generated)
├── app/ ← Screens
├── lib/ ← Supabase client
└── components/ ← UI
- Supabase access token: Stored in
AppFactory/.env(gitignored) - Supabase org ID: Stored in
config.json(set after cloning) - RevenueCat secret key: Stored in
AppFactory/.env(gitignored,sk_prefix, project-scoped) - RevenueCat project: Set in RevenueCat dashboard
- Per-app Supabase keys: Auto-generated in each
apps/<name>/.env - Per-app RevenueCat keys: Added at ship time (requires bundle_id / package_name from EAS)
- NEVER commit .env files — the .gitignore is set up to protect them
sk_keys are project-scoped: can manage apps/products within a project, but cannot create new projects- To create new projects per app, you need an OAuth token (
atk_prefix) from RevenueCat dashboard → Settings → OAuth - For now, all apps share the existing project
proj4e78da7b - RevenueCat app creation requires store credentials (iOS bundle_id + App Store Connect key, Android package_name) — done at EAS ship time, not at create-app time
| Layer | Technology | Why |
|---|---|---|
| Framework | Expo (React Native, Managed Workflow) | Best AI code gen quality, one codebase → both stores |
| Language | TypeScript (strict) | Most training data, fewest AI mistakes |
| Routing | Expo Router v4 (file-based) | Simple, file = route |
| Styling | NativeWind v4 (Tailwind for RN) | AI writes Tailwind perfectly, className-based |
| UI Components | React Native Reusables (shadcn for RN) | Same patterns as shadcn/ui, Claude knows them |
| Icons | Lucide React Native (1,500+ SVG icons) | The shadcn standard, tree-shakable |
| Animations | Moti (Framer Motion-like API) | Claude writes Framer Motion API perfectly |
| Backend | Supabase (Postgres + Auth + Storage + Edge Functions) | Full SQL, AI writes it best, free tier generous |
| Auth | Supabase Auth | Built into Supabase, no extra dependency |
| Payments | RevenueCat (react-native-purchases) | Handles both App Store + Play Store IAP, one SDK |
| Build | EAS Build (cloud) | No local Xcode/Android Studio needed |
| Submit | EAS Submit | Automated store submission |
| OTA Updates | EAS Update | Push JS changes without store review |
| Visual Test | Playwright (headless Chromium) | Screenshot apps, verify UI, catch errors |
Each app needs a .env file. Copy from .env.example:
# Required — Supabase project credentials
EXPO_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=eyJhbG...
# Later — RevenueCat (for payments)
EXPO_PUBLIC_REVENUECAT_IOS_KEY=appl_xxxxx
EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=goog_xxxxxThe template runs without any env vars (graceful fallback). Auth features activate when Supabase keys are provided.
# Quick scan a niche
node src/pipeline.js -s "cleaning schedule,plant care"
# Deep scan with more keywords
node src/pipeline.js -s "habit tracker,mood journal" --expand --score-top 50 --deep 10
# Browse pre-built seed lists
node src/niches.js
# Get AI-readable report from latest scan
node src/report.js ai# Creates everything: copies template, creates Supabase + RevenueCat projects, gets keys, writes .env, installs deps
node src/create-app.js MyAppName
# With a pre-built database schema
node src/create-app.js CleanHome --schema schemas/example-cleaning-app.sqlThis automatically:
- Copies
template/→apps/MyAppName/ - Updates
app.jsonwith the app name and slug - Creates a new Supabase project via Management API
- Waits for it to be ready (~30-60 seconds)
- Gets the project URL + anon key + service key
- Sets up RevenueCat project (uses existing project with
sk_key, or creates new withatk_token) - Writes
apps/MyAppName/.envwith all credentials - Runs template schemas (analytics, feedback, push-tokens) — every app gets these
- Runs app-specific SQL schema if provided (e.g.
--schema schemas/cleanhome.sql) - Installs npm dependencies
Modify files inside app/ using Expo Router conventions:
app/(tabs)/index.tsx— Main home screen (replace placeholder)app/(tabs)/settings.tsx— Settings already wired with authapp/login.tsx— Auth screen already built- Add new tab: create
app/(tabs)/newscreen.tsxand add toapp/(tabs)/_layout.tsx - Add modal: create
app/modal.tsxand add toapp/_layout.tsx
For each app, create a Supabase project and set up tables:
-- Example: cleaning schedule app
create table tasks (
id uuid default gen_random_uuid() primary key,
user_id uuid references auth.users(id) on delete cascade,
title text not null,
room text,
frequency text default 'weekly',
last_done timestamp with time zone,
next_due timestamp with time zone,
created_at timestamp with time zone default now()
);
-- Row Level Security: users only see their own data
alter table tasks enable row level security;
create policy "Users see own tasks" on tasks
for all using (auth.uid() = user_id);# Start the dev server
npx expo start --web --port 8099
# In another terminal — screenshot test (unauthenticated)
cd ../.. # back to AppFactory root
node src/dev.js apps/MyApp
# ALWAYS run auth flow test too (authenticated: login → add data → verify)
node src/test-auth.js apps/MyApp --email test@myapp.app --password TestPass1234
# Screenshots saved to apps/MyApp/screenshots/ and apps/MyApp/screenshots/auth-flow/Auth testing is MANDATORY for every app. Before the test:
- Create a confirmed test user via Supabase Admin API (service_role key)
- Run
node src/test-auth.jswhich logs in, adds data, and verifies all screens - Clean up test data after if needed
# One-time setup
npm install -g eas-cli
eas login
eas init
eas build:configure
# Build for both platforms
eas build --platform all
# Submit to stores
eas submit --platform ios
eas submit --platform android
# Push JS-only updates (no store review)
eas update --branch production --message "fix: updated home screen"// app/(tabs)/home.tsx
import { View, Text } from "react-native";
import { Container } from "@/components/Container";
export default function HomeScreen() {
return (
<Container>
<View className="flex-1 justify-center items-center gap-3">
<Text className="text-3xl font-bold">Title</Text>
<Text className="text-gray-500 text-center">Subtitle</Text>
</View>
</Container>
);
}- Use NativeWind (Tailwind)
classNamefor ALL styling - Never use
StyleSheet.create()— always className strings - Use the
Containercomponent for safe area + padding - Use components from
@/components/ui/— these are shadcn/ui-style components (Card, Button, Dialog, Input, etc.) - Use
cn()from@/lib/utilsto merge Tailwind classes conditionally - Use Lucide icons:
import { Check, Home, Settings } from "lucide-react-native" - Use Moti for animations:
import { MotiView } from "moti"— same API as Framer Motion
These follow shadcn/ui composition patterns — Claude knows these deeply:
// Cards
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";
// Forms
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Checkbox } from "@/components/ui/checkbox";
import { Switch } from "@/components/ui/switch";
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@/components/ui/select";
// Display
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { Progress } from "@/components/ui/progress";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { Text } from "@/components/ui/text";
// Feedback
import { Button } from "@/components/ui/button";
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogAction, AlertDialogCancel } from "@/components/ui/alert-dialog";
// Icons (1,500+ available)
import { Check, Home, Settings, Plus, Trash2, Edit, Search } from "lucide-react-native";Apps that feel alive beat apps that just work. Every app MUST follow these:
- Every tap feels responsive — Wrap tappable cards/items in
<ScalePress>(shrinks to 0.97 on press) - Content fades in — Wrap screen content in
<FadeIn>. Never let content just appear. - Lists stagger — Use
<StaggerItem index={i}>for list/grid items. Each item animates in 50ms after the previous. - Celebrate wins — Use
<Celebrate>for checkmarks, completions, achievements. Pop-in with spring physics. - Numbers count up — Use
<CountUp to={42} />for stats, scores, streaks. Never show a static number that just appeared. - Loading = skeletons — Use
<Skeleton>and<SkeletonCard>, NEVER spinners or "Loading..." text. - Empty states are friendly — Use
<EmptyState>with an icon, warm copy, and a CTA button. Never show a blank screen. - Errors feel human — Empathetic copy ("That didn't work — let's try again"), not error codes. Use
<Shake>on invalid inputs. - Onboarding is smooth — First-run screens use
<FadeIn>with staggered delays for each element.
// Available animation components (auto-included in template)
import { FadeIn, StaggerItem, ScalePress, Celebrate, SlideIn, Shake, AnimatePresence } from "@/components/Animated";
import { CountUp } from "@/components/CountUp";
import { Skeleton, SkeletonCard } from "@/components/Skeleton";
import { EmptyState } from "@/components/EmptyState";Example — A list screen done right:
export default function TasksScreen() {
const { tasks, loading } = useTasks();
if (loading) return (
<Container>
<FadeIn className="gap-3 pt-4">
<SkeletonCard /><SkeletonCard /><SkeletonCard />
</FadeIn>
</Container>
);
if (tasks.length === 0) return (
<Container>
<EmptyState
icon={<ClipboardList size={48} color="#9ca3af" />}
title="No tasks yet"
description="Add your first task to get started"
actionLabel="Add Task"
onAction={() => router.push('/add')}
/>
</Container>
);
return (
<Container>
<FadeIn>
<CountUp to={tasks.length} suffix=" tasks" className="text-gray-500 text-sm mb-3" />
</FadeIn>
{tasks.map((task, i) => (
<StaggerItem index={i} key={task.id}>
<ScalePress onPress={() => router.push(`/task/${task.id}`)}>
<Card>...</Card>
</ScalePress>
</StaggerItem>
))}
</Container>
);
}import { MotiView } from "moti";
// Fade in on mount
<MotiView from={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ type: "timing", duration: 300 }}>
<Text>Hello</Text>
</MotiView>
// Slide up
<MotiView from={{ opacity: 0, translateY: 20 }} animate={{ opacity: 1, translateY: 0 }}>
<Card>...</Card>
</MotiView>// Read data
const { data, error } = await supabase
.from('tasks')
.select('*')
.order('next_due', { ascending: true });
// Insert data
const { error } = await supabase
.from('tasks')
.insert({ title: 'Clean kitchen', room: 'Kitchen', user_id: session.user.id });
// Realtime subscription
useEffect(() => {
const channel = supabase
.channel('tasks')
.on('postgres_changes', { event: '*', schema: 'public', table: 'tasks' },
(payload) => { /* update local state */ }
)
.subscribe();
return () => { supabase.removeChannel(channel); };
}, []);
// Edge Function (server-side logic)
const { data } = await supabase.functions.invoke('my-function', {
body: { key: 'value' },
});import { useAuth } from "@/lib/useAuth";
export default function Screen() {
const { session, loading, signOut } = useAuth();
if (loading) return null;
if (!session) return <Redirect href="/login" />;
return <Text>{session.user.email}</Text>;
}When the user provides a Supabase access token, create projects programmatically:
# Create a new Supabase project
curl -X POST https://api.supabase.com/v1/projects \
-H "Authorization: Bearer SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app-name",
"organization_id": "org-id",
"plan": "free",
"region": "us-east-1",
"db_pass": "GENERATE_STRONG_PASSWORD"
}'
# Run SQL on a project
curl -X POST https://api.supabase.com/v1/projects/PROJECT_REF/database/query \
-H "Authorization: Bearer SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "CREATE TABLE ..."}'The test runner at src/test-app.js uses Playwright headless Chromium:
# Test at default localhost:8099
node src/test-app.js
# Test at custom URL
node src/test-app.js http://localhost:3000
# What it does:
# 1. Opens headless browser (iPhone 14 viewport: 390x844)
# 2. Screenshots home screen → output/screenshots/01-home.png
# 3. Clicks Settings tab → output/screenshots/02-settings.png
# 4. Attempts login screen → output/screenshots/03-login.png
# 5. Reports all console errors
# 6. ExitsAlways run visual tests after making UI changes. Read the screenshot files to verify the result.
# Pipeline flags
node src/pipeline.js \
-s "keywords,comma,separated" \ # seed keywords (required)
-c us \ # country code (default: us)
-d 5 \ # how many to deep-analyze (default: 5)
-e \ # enable alphabet expansion (thorough)
-m 100 \ # max keywords to discover
--score-top 30 # how many to score
# Output formats
node src/report.js ai # Markdown (for Claude to read)
node src/report.js csv # CSV (for spreadsheets)
node src/report.js json # Raw JSONEvery new app created with create-app.js automatically gets:
- Schema:
schemas/analytics.sql— raw events + daily aggregates - Hook:
useAnalytics()from@/lib/useAnalytics - Usage:
const { track, trackScreen } = useAnalytics(); trackScreen("home"); // auto-tracks screen views track("task_completed", { taskId: "..." }); // custom events
- Agent access: Run
node src/dashboard.jsor queryanalytics_dailyvia Management API - Aggregation: Call
SELECT aggregate_analytics_daily()daily (via agent or pg_cron)
- Schema:
schemas/feedback.sql— feedback items + votes +toggle_vote()RPC - Hook:
useFeedback()from@/lib/useFeedback - Screens: Feedback tab (list), submit-feedback modal, feedback-detail modal
- Usage: Already wired into the template tab bar — users can submit feature requests, vote, see status
- Agent access: Query
feedbacktable for top-voted items, updatestatusto planned/in-progress/done
- Schema:
schemas/push-tokens.sql— device token storage - Hook:
usePushNotifications()from@/lib/usePushNotifications - Sending:
node src/notify.js <AppName> --title "Title" --body "Message"(agents send via Expo Push API) - Setup: Just call
usePushNotifications()in the root layout — tokens are auto-registered - No account needed: Expo Push API is free and requires no signup
- Script:
node src/dashboard.js— queries ALL apps, outputs markdown report - Data: DAU, retention, top screens, feedback rankings, RevenueCat revenue
- Per-app:
node src/dashboard.js --app CleanHome
- KISS — Keep it simple. Minimal files, minimal abstractions, no over-engineering.
- Every app is the same skeleton — Copy template, fill in screens, wire up Supabase tables. Same bones, different skin.
- Test visually — Always run Playwright after UI changes. Read the screenshots. Fix. Repeat.
- Ship fast — Start on Google Play (cheaper, faster review). Port winners to iOS.
- Revenue from day one — RevenueCat for subscriptions, shared module across all apps, only .env keys differ.
- AI writes everything — Claude Code discovers, builds, tests, and ships. The human provides accounts/keys and final approval.
- Track everything — Every idea in
ideas/, every decision in appNOTES.md, every session logged. - Any agent can pick up — A new Claude session should be able to run
/statusand immediately know what to do next.
These are available in any Claude Code session in this project:
| Command | What it does |
|---|---|
/scan |
Run the opportunity scanner, find niches, save ideas to ideas/ |
/new-app |
Create a new app: design schema → create project → build screens → test |
/dev |
Run the recursive build-test loop: edit → test → screenshot → fix → repeat |
/continue |
Pick up work on an existing app — reads notes, status, runs tests |
/status |
Full overview: all ideas, all apps, recent scans, recommended next action |
/dashboard |
Cross-app report: DAU, retention, feedback, revenue, reviews |
/aso |
Generate optimized App Store / Google Play listing for an app |
/ship |
Ship an app to stores: screenshots → ASO → privacy policy → EAS build → submit |
This is the core workflow. It's recursive — keep going until the app is right.
┌──────────────┐
│ EDIT CODE │ ← Write/modify screens, components, queries
└──────┬───────┘
│
▼
┌──────────────┐
│ RUN TEST │ ← node src/dev.js apps/MyApp
└──────┬───────┘
│
▼
┌──────────────┐
│ READ │ ← Read screenshot PNGs + results.json
│ SCREENSHOTS │
└──────┬───────┘
│
├── Looks wrong? ──→ Go back to EDIT CODE
│
▼
┌──────────────┐
│ DONE │ ← All screens look good, no errors
└──────────────┘
Commands:
# Start the server (once)
cd apps/MyApp && npx expo start --web --port 8099 &
# Test loop (run after each edit)
node src/dev.js apps/MyApp
# Read results
# → apps/MyApp/screenshots/01-current.png (Read tool)
# → apps/MyApp/screenshots/results.jsonEvery scanned opportunity gets an idea file with:
- Opportunity score, difficulty, traffic
- Competitor weaknesses and user pain points
- Proposed features and monetization
- Status checklist (idea → created → built → tested → shipped)
Every app tracks:
- What it does (one sentence)
- Key decisions and trade-offs
- Database tables and their purpose
- Screen descriptions
- Current status
- Session log (date + what was done + what's next)
When a new Claude session starts in this project:
- It reads this CLAUDE.md — knows the full system
- It runs
/status— sees all ideas, apps, progress - It reads
ideas/— knows which opportunities exist - It reads
apps/<name>/NOTES.md— knows exactly where things left off - It picks up and continues without any context loss
RevenueCat handles both App Store and Google Play purchases with one SDK.
Setup (per app):
- Create a RevenueCat account at https://app.revenuecat.com (free to start)
- Create a project → get API keys (one per platform)
- Add to app
.env:EXPO_PUBLIC_REVENUECAT_IOS_KEY=appl_xxxxx EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=goog_xxxxx - Install:
npx expo install react-native-purchases
RevenueCat has a REST API for managing products, customers, and subscriptions programmatically:
GET /v1/subscribers/{app_user_id}— check subscription status- API key from RevenueCat dashboard → can be used from Supabase Edge Functions
Revenue models we support:
- One-time purchase (e.g. CleanHome premium $4.99)
- Monthly/yearly subscription
- Consumables (e.g. credits)
For apps where ads make sense (free apps with no premium tier, utility apps, games).
Package: react-native-google-mobile-ads
Setup:
- Create AdMob account at https://admob.google.com (free)
- Create ad units → get ad unit IDs
- Install:
npx expo install react-native-google-mobile-ads - Add to
app.json:{ "react-native-google-mobile-ads": { "android_app_id": "ca-app-pub-xxxxx~xxxxx", "ios_app_id": "ca-app-pub-xxxxx~xxxxx" } }
Ad types: Banner (bottom of screen), Interstitial (full screen between actions), Rewarded (user watches ad for reward)
Important: AdMob requires a Google AdMob account (free) and a Google Play Developer account ($25). Review approval can take days.
| App Type | Strategy | Tool |
|---|---|---|
| Premium utility (CleanHome) | Free core + one-time IAP | RevenueCat |
| Content/service app | Subscription (monthly/yearly) | RevenueCat |
| Free casual app | Ad-supported | AdMob |
| Hybrid | Free with ads, pay to remove ads | AdMob + RevenueCat |
Not just simple CRUD — it runs real backend logic:
- Database functions:
CREATE FUNCTIONfor complex server-side logic in SQL or PL/pgSQL - Triggers: Automatically run logic on insert/update/delete
- Edge Functions: Full TypeScript serverless functions (call external APIs, process data, send emails)
- Realtime: Subscribe to any table change via WebSocket
- Storage: Upload/serve images, files, videos with built-in CDN
- pg_cron: Scheduled background jobs (daily cleanup, reminders, reports)
- pgvector: Store and query AI embeddings for semantic search
- Full-text search: Built-in Postgres text search, no external service needed
- RPC: Call database functions directly from the client as typed endpoints
- Webhooks: POST to external URLs when data changes
- Auth hooks: Custom logic on signup, login, token refresh
Example — send a push notification when a task is due:
-- Trigger function
create function notify_due_task() returns trigger as $$
begin
if NEW.next_due <= now() then
perform net.http_post(
'https://your-project.supabase.co/functions/v1/send-push',
jsonb_build_object('user_id', NEW.user_id, 'task', NEW.title)
);
end if;
return NEW;
end;
$$ language plpgsql;
-- Attach to table
create trigger on_task_due after update on tasks
for each row execute function notify_due_task();AppFactory can run autonomously in Docker — discovering niches, building apps, testing, and shipping without human input.
┌─────────────────────────────────────────────────────┐
│ Docker │
│ │
│ appfactory-agent appfactory-dashboard │
│ (Playwright + Node 20 (Express + WebSocket) │
│ + Claude Code CLI localhost:3000 │
│ + eas-cli) │
│ │
│ Shared Volume: /data/ │
│ ├── apps/ ├── logs/activity.jsonl │
│ ├── ideas/ ├── chat/ (inbox + outbox) │
│ ├── memory/ ├── config.json │
│ ├── template/ └── status.json │
│ └── src/ │
└─────────────────────────────────────────────────────┘
# Set your proxy URL and API key
export ANTHROPIC_BASE_URL=http://host.docker.internal:9212
export ANTHROPIC_API_KEY=your-key-here
# Start everything
docker-compose up -d
# Dashboard at http://localhost:3000
# Configure settings via the dashboardRuns forever in a loop: observe → prioritize → act → learn → heartbeat
Each cycle:
- Reads config, chat inbox, app statuses, ideas, memory
- Picks the highest-priority action (user chat > bugs > feedback > ship > build > scan)
- Executes via
claude -pwith the cheapest capable model - Logs results, updates memory
- Sleeps for configurable interval (default 5 min)
# Dry run (single cycle, no actions)
node src/orchestrator.js --dry-run
# Normal run (loops forever)
node src/orchestrator.jsThe orchestrator picks the cheapest model per task:
| Task | Model | Why |
|---|---|---|
| Status checks, scanning | Haiku | Fast, cheap |
| Building screens, fixing bugs | Sonnet | Code gen sweet spot |
| Architecture, complex debugging | Opus | Deep reasoning |
Configured in config.json → modelRouting section.
# The orchestrator calls Claude Code headlessly:
claude -p "<task>" --model <model> --allowedTools "Read,Edit,Write,Bash(...),Glob,Grep" --output-format jsonPersistent knowledge base the agent reads every cycle:
memory/
├── patterns.json # Design patterns that work
├── failures.json # What didn't work
├── revenue.json # Revenue insights per category
├── store_reviews.json # Common complaints + solutions
└── meta.json # Agent self-assessment
| Page | URL | Description |
|---|---|---|
| Home | / |
App cards, agent status, quick stats |
| Apps | /apps.html?name=X |
Single app detail + screenshots |
| Agent Log | /log.html |
Real-time activity stream (WebSocket) |
| Settings | /settings.html |
Proxy URL, API keys, orchestrator config |
| Chat | /chat.html |
Send messages to the agent |
- Writes
status.json— dashboard polls this - Appends to
logs/activity.jsonl— dashboard streams this via WebSocket
# Ship an app to stores
node src/ship.js AppName --platform android
# Full pipeline:
# 1. Store-quality screenshots (multiple device sizes)
# 2. ASO metadata (title, description, keywords)
# 3. Privacy policy generation
# 4. EAS build
# 5. EAS submit# Generate privacy policy HTML
node src/privacy-policy.js AppName
# → apps/AppName/privacy-policy.htmlApps that use AI features get:
- Schema:
schemas/ai-credits.sql— token/credit tracking per user - Hook:
template/lib/useAI.ts— React hook calling Edge Functions - Edge Function:
supabase-functions/ai-proxy/index.ts— secure AI API proxy
import { useAI } from "@/lib/useAI";
function MyScreen() {
const { generate, loading, error } = useAI({ feature: 'summarize' });
const handleGenerate = async () => {
const result = await generate("Summarize this text...");
// result.result, result.tokensUsed
};
}Created automatically from config/default.json on first run. Managed via dashboard Settings page. Contains:
- Proxy URL + API key
- Model preferences (which model for which task type)
- Supabase + RevenueCat credentials
- EAS credentials
- Orchestrator settings (cycle interval, auto-ship, max apps, dry run)