Michiu / marketing reference

michiu-marketing

Brand operations + AI marketing system for Restaurant Michiu — Cantonese-Japanese, Amsterdam-Zuid.

This repo is the internal command center: review management, social publishing, DM inbox, loyalty program ops, and the Marketing OS that orchestrates all of it. It owns the Supabase schema for the entire Michiu platform.


Who this is for

PersonRole
CasperAgency lead (Quarb Studio) — built and operates this system
Michelle ZhangOwner-chef of Restaurant Michiu — the client. Approves brand decisions, signs off on copy.

This is an internal agency tool, not a public product. There is no self-serve signup. Access is by invitation.


What Claude Code is

This project is built and operated using Claude Code — Anthropic's AI coding assistant that runs as a CLI in the terminal. When you open a terminal in this repo and run claude, Claude Code loads CLAUDE.md automatically as its operating contract. Every rule in that file governs every session.

Claude Code is not an IDE plugin and not a web interface. It's a terminal-native agent that reads files, writes code, runs commands, and commits — all from a conversation in your shell.

The most important rule Claude Code enforces here: nothing ever auto-posts. Every generated reply, caption, DM, or outreach draft requires human approval before it goes anywhere near a public surface. This is hardcoded into the system architecture and the operating rules.


Three-repo architecture

RepoPurposeDeploy
michiu-websitePublic-facing siteCloudflare Pages
michiu-marketing (this repo)Brand ops + AI toolingVercel (marketing.michiu.quarb.com)
michiu-operations (future)Kitchen + service opsTBD

All three repos share one Supabase project. Schema migrations live here only — other repos consume the DB via the Supabase JS client.


What's shipped

The six lanes

Work is organized into six domain lanes. Each is an independent working context with its own operating rules, handoff doc, and AI skill.

LaneWhat it doesStatus
reviewsBrand-voice reply generation for Google, TheFork, TripAdvisor reviews. Multi-platform inbox, bulk paste, guest photos, Circle member linkage.Production
socialCaption generation (with vision), asset library, cross-platform scheduler, Meta + LinkedIn publishers.Code-complete — gated on Meta/LinkedIn app approvals
dm-inboxMessenger/Instagram DM handling with brand-voice replies, Circle auto-link, bulk-paste tester.Code-complete — gated on Meta pages_messaging approval
guest-intelCustomer + reservation store (Zenchef integration). Foundation shipped; Zenchef B2B client pending API docs.Foundation (OS-31)
adsGoogle Ads GCLID capture, conversion upload, Customer Match.Scaffolded — gated on Google Ads dev token
marketing-osOrchestration umbrella over all five lanes above. See below.Epoch 2 complete

The Marketing OS

The Marketing OS is an AI operating system built on top of the six lanes. It consists of:

  • 12 helpers — named AI personas, each owning a domain:
    • Sun (Strategist), Zuck (Social), Siren (Replier), Anna (Concierge/DM), Penn (Editor/Press), Carlos (Local/SEO), Roy (Ads), Clara (Lifecycle/CRM), Insight (Analyst), Julius (Brand Guardian), Dion, Pablo
  • Brand Memory — structured tables (brand_identity, brand_voices, brand_banned_phrases, brand_audiences, etc.) that ground every helper output in Michiu's brand voice
  • Power-Ups — task-specific tools inside each helper (e.g. Sun's "RAI opportunity scan", Penn's "press pitch generator", Clara's "post-visit follow-up")
  • Universal approval queue — every helper output lands here before anything reaches a public surface
  • Inngest event bus — async orchestration between helpers (no direct helper-to-helper calls)
  • Vault sync pipeline — Obsidian brand vault → GitHub → webhook → Inngest → pgvector (Brand Memory RAG)

The Circle of Michiu

Michiu's loyalty program — an invite-only membership that recognizes returning guests. It is felt, never announced: members are recognized when they walk in, remembered between visits, invited to private evenings. It never appears publicly by name. In the codebase it surfaces as circle_members table + circle_tier field on reviews.


Hard rules (a newcomer must know these)

These live in CLAUDE.md in full. The four that will cause the most damage if broken:

  1. Never modify an applied migration. Migrations are append-only once shipped. Add a new file; never edit an existing one. Breaking this corrupts the schema history for all three repos.
  2. Nothing auto-posts. Human approval is required before any generated content reaches a public surface. No exceptions until an eval harness is built and passes.
  3. Every new database table needs account_id text not null default 'michiu'. This is the SaaS seed — without it, future multi-tenancy requires painful backfills.
  4. Every LLM-generated public-facing string runs through checkBannedPhrases(). This is layer 2 of brand voice protection. Never remove it.

Dev setup

Two locations, one codebase

Mac (primary)                           iPad / iPhone (mobile)
─────────────────────────────────       ─────────────────────────────────────
Repo: ~/APP-STUDIO/MICHIU_Code/         Blink app → Mosh → Hetzner devbox
      michiu-marketing                  ubuntu-4gb-nbg1-1 · 46.225.238.8
Claude Code: runs locally               Claude Code: runs on devbox
                                        Repo on devbox: ~/projects/michiu/
                                              michiu-marketing

iPad and iPhone both SSH into the same devbox — they share one filesystem and one Claude Code memory store. There are two Claude project slugs, not three:

LocationClaude slug
Mac-Users-CasperMacbook-APP-STUDIO-MICHIU-Code-michiu-marketing
Devbox (iPad + iPhone)-home-casper-projects-michiu-michiu-marketing

What is Tailscale?

Tailscale is a VPN that connects the Mac and the Hetzner devbox on a private network. The devbox has no public SSH port open — all access goes through Tailscale. If Tailscale is not running, or if WARP (Cloudflare) is active on the Mac (they conflict), the devbox is unreachable. Use the direct IP 100.112.135.68 if the .ts.net hostname fails to resolve.

Session handoff protocol

  1. git pull before starting any session
  2. git push before switching devices
  3. Paste a brief "where I left off" between terminal windows when switching
  4. After a devbox session, sync Claude Code memory to Mac:
# Run on Mac — pulls devbox memory across Tailscale
bash scripts/devbox/sync-from-server.sh 100.112.135.68

Connecting to the devbox

# Via Tailscale hostname (preferred)
ssh casper@ubuntu-4gb-nbg1-1.ts.net
# or direct Tailscale IP if hostname doesn't resolve:
ssh casper@100.112.135.68

# Mosh (better for mobile — survives network switches and sleep)
mosh casper@100.112.135.68

tmux on the devbox

The devbox runs tmux to keep Claude Code sessions alive across disconnects. Named sessions are pre-created per project:

tmux attach -t michiu     # re-attach to the michiu session (Claude Code running here)
tmux ls                   # list all active sessions

From Blink on iPad/iPhone: connect via Mosh, then tmux attach -t michiu. If Claude Code was mid-task when you disconnected, it resumes exactly where it left off.

Mac local does not need tmux. There's no SSH connection to drop and the process survives sleep natively. Just run claude directly in any terminal window (iTerm2, Warp, Terminal.app).


Tech stack

LayerChoiceWhy
LanguageTypeScript (strict mode)End-to-end type safety across DB → API → UI
FrameworkNext.js 15 (App Router) + React 19 + Tailwind 4Admin console + reference site in one app
DatabaseSupabase (Postgres + RLS + Auth)Managed Postgres with row-level security per account
LLMAnthropic SDK — claude-sonnet-4-6, prompt-cachedBrand-voice generation; prompt caching cuts cost on repeated system prompts
Event busInngestAsync helper orchestration; retries, DLQ, step checkpointing built in
EmailResendTransactional alerts + Clara lifecycle outreach
ValidationZodRuntime schema validation on all API inputs
Lint + formatBiomeSingle tool replaces ESLint + Prettier
Package managerpnpmFaster installs, strict lockfile
Node22 (.nvmrc)
DeployVercel (admin + API) + Cloudflare Pages (reference static export)

Local setup

git clone git@github.com:quarbstudio/michiu-marketing.git
cd michiu-marketing
pnpm install
cp .env.example .env.local
# Fill in the values — see .env.example for all required vars
pnpm typecheck
pnpm dev

Admin runs at http://localhost:3000/admin. The full env var list is in .env.example — the minimum to get started is NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and ANTHROPIC_API_KEY. Without Supabase vars, the review system falls back to a file-backed mock store (.data/reviews.json); everything else will error.

Key scripts

pnpm dev                  # local dev server
pnpm typecheck            # tsc --noEmit
pnpm lint                 # Biome check
pnpm regress              # review prompt regression suite (7 worked examples)
pnpm regress:dm           # DM prompt regression suite
pnpm seed                 # seed mock review store
pnpm seed:guests          # seed guest graph (customers + reservations)
pnpm import:google        # import Google reviews via API
pnpm import:csv           # import reviews from CSV
pnpm import:zenchef       # import Zenchef Manager UI CSV export
pnpm cron:tick            # manually fire due scheduled posts (Hobby-tier workaround)
pnpm vault:push           # push Obsidian vault → GitHub mirror (Mac only)
pnpm cf:build             # static export for Cloudflare Pages reference site

Apply schema migrations

supabase login
supabase link --project-ref YOUR_PROJECT_REF
supabase db push

108 migrations applied (0001 → 0108). Full annotated list in HANDOFF.md → "Database schema". Never edit an applied migration file — add a new one.

CI/CD

GitHub Actions runs on every PR: pnpm typecheck + pnpm lint. Both must pass before merging. Config at .github/workflows/ci.yml.


Repo structure

michiu-marketing/
├── CLAUDE.md              ← operating contract — Claude Code auto-loads this at session start
├── HANDOFF.md             ← current state + next steps — read this second
│
├── src/
│   ├── app/
│   │   ├── admin/         ← admin console (local + Vercel) — /admin/reviews, /admin/posts, etc.
│   │   └── reference/     ← marketing reference site (Cloudflare Pages static export)
│   ├── brand/             ← review-prompt.ts + caption-prompt.ts + dm-prompt.ts (core brand-voice IP)
│   ├── marketing-os/      ← 12 helpers, Brand Memory, Power-Ups, Campaign, Inngest functions
│   └── lib/
│       ├── reviews/       ← review pipeline + store
│       ├── guest-graph/   ← customer-store + reservation-store
│       └── integrations/  ← google/, zenchef/, csv/
│
├── docs/                  ← knowledge base (brand, strategy, competition, channels, RAI, circle)
├── supabase/migrations/   ← 108 migrations — append-only after applied
├── scripts/               ← seed, import, regress, cron, devbox sync utilities
├── brand-assets/          ← canonical visual asset library (logos, photos, video, templates)
├── platforms/             ← per-platform config + sized image exports (GBP, Instagram, LinkedIn, etc.)
├── editorial/             ← press kit + editorial opportunity tracker
│
└── .claude/
    ├── lanes/             ← per-lane CLAUDE.md (operating rules) + HANDOFF.md (current state)
    └── skills/            ← auto-triggering domain skills loaded by Claude Code

Lane operating rules

Each lane has two files a new developer must read before touching that lane:

LaneOperating rulesCurrent state
reviews.claude/lanes/reviews.CLAUDE.md.claude/lanes/handoffs/reviews.HANDOFF.md
social.claude/lanes/social.CLAUDE.md.claude/lanes/handoffs/social.HANDOFF.md
dm-inbox.claude/lanes/dm-inbox.CLAUDE.md.claude/lanes/handoffs/dm-inbox.HANDOFF.md
guest-intel.claude/lanes/guest-intel.CLAUDE.md.claude/lanes/handoffs/guest-intel.HANDOFF.md
ads.claude/lanes/ads.CLAUDE.md.claude/lanes/handoffs/ads.HANDOFF.md
marketing-os.claude/lanes/marketing-os.CLAUDE.md.claude/lanes/handoffs/marketing-os.HANDOFF.md

Session start sequence: CLAUDE.mdHANDOFF.md → lane CLAUDE.md → lane HANDOFF.md → start work.


Knowledge architecture

Code lives here. Strategy lives in three Obsidian vaults:

VaultLocationWhat
MICHIUiCloud → Documents/MICHIU/Brand, marketing, ops, guests — canonical source of truth for WHY decisions were made
QUARBiCloud → Documents/QUARB/Quarb Studio agency operations
UGC_AGENCYiCloud → Documents/UGC_AGENCY/Separate AI UGC venture

The MICHIU vault syncs to GitHub (quarbstudio/michiu-brand-vault, private) every 15 minutes via a Mac LaunchAgent (scripts/vault-sync.sh). A GitHub webhook triggers an Inngest pipeline that chunks and embeds the vault docs into memory_documents (pgvector) — this is the RAG layer the Marketing OS helpers use to ground their outputs in Michiu's brand strategy.

The vault is the source of truth for why. This repo is the source of truth for what runs.


Approval workflow

Every AI-generated output — review replies, social captions, DM replies, press pitches, lifecycle emails — lands in the universal approval queue at /admin/marketing-os/approvals before anything leaves the system. The flow:

Helper generates draft
  → Julius (Brand Guardian) runs a pre-publish rubric check
  → Draft lands in pending_approvals with warnings (advisory only — never blocking)
  → Human reviews → approves / rejects / regenerates
  → On approval: content routes to its destination (reply posted, post scheduled, email queued)

There is no bypass. No draft auto-advances to published. This is enforced in code, not just policy.


License

Proprietary — Restaurant Michiu, Amsterdam. Quarb Studio is the agency operating the marketing systems.

Source: michiu-marketing/README.md. To edit, change the file in the repo and redeploy.