Skip to content

Latest commit

 

History

History
860 lines (693 loc) · 33 KB

File metadata and controls

860 lines (693 loc) · 33 KB

AppFactory — AI-Driven App Factory

What This Is

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.

Project Structure

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

Credentials

  • 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

RevenueCat Key Limitations

  • 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

The Stack

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

Environment Variables

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_xxxxx

The template runs without any env vars (graceful fallback). Auth features activate when Supabase keys are provided.

Creating a New App — Step by Step

1. Find an Opportunity

# 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

2. Create the App (one command)

# 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.sql

This automatically:

  1. Copies template/apps/MyAppName/
  2. Updates app.json with the app name and slug
  3. Creates a new Supabase project via Management API
  4. Waits for it to be ready (~30-60 seconds)
  5. Gets the project URL + anon key + service key
  6. Sets up RevenueCat project (uses existing project with sk_ key, or creates new with atk_ token)
  7. Writes apps/MyAppName/.env with all credentials
  8. Runs template schemas (analytics, feedback, push-tokens) — every app gets these
  9. Runs app-specific SQL schema if provided (e.g. --schema schemas/cleanhome.sql)
  10. Installs npm dependencies

3. Build the Screens

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 auth
  • app/login.tsx — Auth screen already built
  • Add new tab: create app/(tabs)/newscreen.tsx and add to app/(tabs)/_layout.tsx
  • Add modal: create app/modal.tsx and add to app/_layout.tsx

4. Set Up the Backend

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);

5. Test Visually

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

  1. Create a confirmed test user via Supabase Admin API (service_role key)
  2. Run node src/test-auth.js which logs in, adds data, and verifies all screens
  3. Clean up test data after if needed

6. Ship to Stores

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

Coding Conventions

File Structure Per 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>
  );
}

Styling Rules

  • Use NativeWind (Tailwind) className for ALL styling
  • Never use StyleSheet.create() — always className strings
  • Use the Container component for safe area + padding
  • Use components from @/components/ui/ — these are shadcn/ui-style components (Card, Button, Dialog, Input, etc.)
  • Use cn() from @/lib/utils to 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

Available UI Components (@/components/ui/)

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";

Emotional Design Rules (MANDATORY)

Apps that feel alive beat apps that just work. Every app MUST follow these:

  1. Every tap feels responsive — Wrap tappable cards/items in <ScalePress> (shrinks to 0.97 on press)
  2. Content fades in — Wrap screen content in <FadeIn>. Never let content just appear.
  3. Lists stagger — Use <StaggerItem index={i}> for list/grid items. Each item animates in 50ms after the previous.
  4. Celebrate wins — Use <Celebrate> for checkmarks, completions, achievements. Pop-in with spring physics.
  5. Numbers count up — Use <CountUp to={42} /> for stats, scores, streaks. Never show a static number that just appeared.
  6. Loading = skeletons — Use <Skeleton> and <SkeletonCard>, NEVER spinners or "Loading..." text.
  7. Empty states are friendly — Use <EmptyState> with an icon, warm copy, and a CTA button. Never show a blank screen.
  8. Errors feel human — Empathetic copy ("That didn't work — let's try again"), not error codes. Use <Shake> on invalid inputs.
  9. 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>
  );
}

Animations with Moti

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>

Supabase Patterns

// 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' },
});

Auth Pattern

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>;
}

Supabase Management API

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 ..."}'

Visual Testing

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

Always run visual tests after making UI changes. Read the screenshot files to verify the result.

Opportunity Scanner

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

Built-in Features (Every App Gets These)

Every new app created with create-app.js automatically gets:

Analytics (Supabase-powered)

  • 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.js or query analytics_daily via Management API
  • Aggregation: Call SELECT aggregate_analytics_daily() daily (via agent or pg_cron)

Feedback Board (in-app)

  • 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 feedback table for top-voted items, update status to planned/in-progress/done

Push Notifications (Expo Push, free)

  • 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

Dashboard

  • 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

Key Principles

  1. KISS — Keep it simple. Minimal files, minimal abstractions, no over-engineering.
  2. Every app is the same skeleton — Copy template, fill in screens, wire up Supabase tables. Same bones, different skin.
  3. Test visually — Always run Playwright after UI changes. Read the screenshots. Fix. Repeat.
  4. Ship fast — Start on Google Play (cheaper, faster review). Port winners to iOS.
  5. Revenue from day one — RevenueCat for subscriptions, shared module across all apps, only .env keys differ.
  6. AI writes everything — Claude Code discovers, builds, tests, and ships. The human provides accounts/keys and final approval.
  7. Track everything — Every idea in ideas/, every decision in app NOTES.md, every session logged.
  8. Any agent can pick up — A new Claude session should be able to run /status and immediately know what to do next.

Agent Skills (Slash Commands)

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

The Build-Test Loop

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

Tracking & Continuity

Ideas (ideas/ directory)

Every 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)

Per-App Notes (apps/<name>/NOTES.md)

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)

Why This Matters

When a new Claude session starts in this project:

  1. It reads this CLAUDE.md — knows the full system
  2. It runs /status — sees all ideas, apps, progress
  3. It reads ideas/ — knows which opportunities exist
  4. It reads apps/<name>/NOTES.md — knows exactly where things left off
  5. It picks up and continues without any context loss

Monetization

RevenueCat (In-App Purchases / Subscriptions)

RevenueCat handles both App Store and Google Play purchases with one SDK.

Setup (per app):

  1. Create a RevenueCat account at https://app.revenuecat.com (free to start)
  2. Create a project → get API keys (one per platform)
  3. Add to app .env:
    EXPO_PUBLIC_REVENUECAT_IOS_KEY=appl_xxxxx
    EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=goog_xxxxx
    
  4. 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)

Google AdMob (Ads)

For apps where ads make sense (free apps with no premium tier, utility apps, games).

Package: react-native-google-mobile-ads Setup:

  1. Create AdMob account at https://admob.google.com (free)
  2. Create ad units → get ad unit IDs
  3. Install: npx expo install react-native-google-mobile-ads
  4. 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.

Which to use when?

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

What Supabase Can Handle

Not just simple CRUD — it runs real backend logic:

  • Database functions: CREATE FUNCTION for 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();

Docker System (Autonomous Mode)

AppFactory can run autonomously in Docker — discovering niches, building apps, testing, and shipping without human input.

Architecture

┌─────────────────────────────────────────────────────┐
│  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/                                          │
└─────────────────────────────────────────────────────┘

Quick Start

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

Orchestrator (src/orchestrator.js)

Runs forever in a loop: observe → prioritize → act → learn → heartbeat

Each cycle:

  1. Reads config, chat inbox, app statuses, ideas, memory
  2. Picks the highest-priority action (user chat > bugs > feedback > ship > build > scan)
  3. Executes via claude -p with the cheapest capable model
  4. Logs results, updates memory
  5. 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.js

Model Routing

The 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.jsonmodelRouting section.

Agent Wrapper (src/agent.js)

# The orchestrator calls Claude Code headlessly:
claude -p "<task>" --model <model> --allowedTools "Read,Edit,Write,Bash(...),Glob,Grep" --output-format json

Memory System (src/memory.js)

Persistent 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

Dashboard Pages

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

Heartbeat (src/heartbeat.js)

  • Writes status.json — dashboard polls this
  • Appends to logs/activity.jsonl — dashboard streams this via WebSocket

Shipping Pipeline (src/ship.js)

# 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

Privacy Policy (src/privacy-policy.js)

# Generate privacy policy HTML
node src/privacy-policy.js AppName
# → apps/AppName/privacy-policy.html

AI Integration (for AI-powered apps)

Apps 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
  };
}

Config (config.json)

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)