Files
zeekayandHanzo Dev 69c6e860d7 transplant onto tamagui v2.6.0, brand as a re-runnable transform
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.

This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.

174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.

Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
  - useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
    returns undefined there, so every Tamagui Dialog kills a prerender — the
    exact crash hanzo.app spent a day on.
  - data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.

Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.

Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.

Co-Authored-By: Hanzo Dev <[email protected]>
2026-07-30 18:25:06 -07:00
..

Takeout Subscription Management Scripts

Overview

These scripts help manage the sync between Stripe subscriptions and GitHub team access for Gui's takeout products (Gui Pro, Takeout Stack, Team Seats).

How It Works

  1. Customers subscribe to Gui Pro or Takeout Stack in Stripe
  2. Webhook creates claim in database with team_slug: 'early-access'
  3. User claims access via the website, which adds them to GitHub early-access team
  4. Subscription cancels → webhook marks claim as unclaimed and removes from GitHub team

Products That Grant Early Access

  • Gui Pro (prod_RlRd2DVrG0frHe) - includes takeout access
  • Takeout Stack (prod_NzLEazaqBgoKnC) - standalone takeout
  • Gui Pro Team Seats (prod_Rxu0x7jR0nWJSv) - team subscriptions

Available Scripts

1. check-discord-sync.ts

Purpose: Audit Discord Takeout role vs active Stripe subscriptions

npx tsx scripts/takeout/check-discord-sync.ts

Shows:

  • Discord users with Takeout role
  • Active Stripe subscriptions
  • Users who should be removed (no active subscription)
  • Users without mapping (old accounts)

Creates: tmp/discord-users-to-remove-*.json with users to remove

Use when: You want to audit Discord access or find stale Discord users


2. remove-discord-users.ts

Purpose: Remove Discord Takeout role from users based on analysis file

# Dry run first
npx tsx scripts/takeout/remove-discord-users.ts tmp/discord-users-to-remove-*.json --dry-run

# Then actually remove
npx tsx scripts/takeout/remove-discord-users.ts tmp/discord-users-to-remove-*.json

Use when: After reviewing the discord-users-to-remove file and confirming it's safe


3. check-subscription-sync.ts

Purpose: Audit sync between Stripe subscriptions and GitHub team membership

npx tsx scripts/takeout/check-subscription-sync.ts

Shows:

  • Active Stripe subscriptions count
  • GitHub team members (active + pending)
  • Database claims count
  • Discrepancies between systems

Use when: You suspect sync issues or want to verify everything is working


2. check-webhook-events.ts

Purpose: View recent Stripe webhook events to debug issues

npx tsx scripts/takeout/check-webhook-events.ts [--limit 100]

Shows:

  • Recent webhook events by type
  • Recent subscription events (created/updated/deleted)
  • Webhook endpoint configuration

Use when: Debugging webhook issues or verifying webhooks are being received


3. analyze-stale-claims.ts

Purpose: Find claims for cancelled subscriptions that need cleanup

npx tsx scripts/takeout/analyze-stale-claims.ts

Creates files in /tmp/:

  • active-subscriptions-*.json - All active Stripe subscription IDs
  • valid-claims-*.json - Claims that should be kept
  • stale-claims-*.json - Claims to clean up (subscriptions cancelled)
  • cleanup-summary-*.txt - Summary report

Use when: You want to identify and review stale claims before cleanup


4. cleanup-stale-claims.ts

Purpose: Clean up stale claims (mark as unclaimed)

# Dry run first
npx tsx scripts/takeout/cleanup-stale-claims.ts tmp/stale-claims-*.json --dry-run

# Then actually clean up
npx tsx scripts/takeout/cleanup-stale-claims.ts tmp/stale-claims-*.json

Use when: After reviewing the stale claims file and confirming it's safe to clean up


Bug History

Critical Bug Fixed (Nov 2024)

Location: apps/gui.hanzo.ai/features/api/unclaimProduct.ts

Problem: When subscriptions were cancelled, the webhook tried to update claims but was missing the WHERE clause:

// BEFORE (BROKEN)
await supabaseAdmin.from('claims').update({
  unclaimed_at: Number(new Date()).toString(),
})
// This tried to update ALL claims in the database!

Fix:

// AFTER (FIXED)
await supabaseAdmin
  .from('claims')
  .update({
    unclaimed_at: new Date().toISOString(),
  })
  .eq('id', claim.id) // ← Added WHERE clause

Additional fixes:

  • Added await before unclaimRepoAccess() call
  • Added fallback to 'early-access' team for old claims without team_slug
  • Fixed date format to use ISO string
  • Added Discord role removal - now removes Takeout role from Discord when subscriptions cancel

Migration History (Nov 2024)

Migrated from direct repo collaborator access to GitHub team-based access:

Before:

  • Users added as collaborators to individual repos
  • Claims had repository_name: 'takeout' or 'unistack'

After:

  • Users added to early-access GitHub team
  • Team has access to all relevant repos
  • Claims have team_slug: 'early-access'

Migration Stats:

  • Invited 197 active subscribers to GitHub team
  • Removed 40 users from Discord Takeout role (cancelled subscriptions)
  • Cleaned up 590 stale claims from cancelled subscriptions
  • Fixed webhook bugs preventing future stale claims and Discord cleanup

Current State (as of Nov 2024)

  • Active Stripe Subscriptions: ~370
  • GitHub Team Members: ~350 (active + pending)
  • Discord Takeout Role: ~27 users (active subscriptions only)
  • Webhooks: Working correctly
  • Claim Creation: Properly sets team_slug
  • Claim Cleanup: Properly marks as unclaimed on cancellation
  • Discord Cleanup: Removes Takeout role and deletes discord_invites on cancellation

Environment Variables Required

All scripts need these environment variables (automatically loaded from apps/gui.hanzo.ai/.env):

  • NEXT_PUBLIC_SUPABASE_URL - Supabase project URL
  • SUPABASE_SERVICE_ROLE_KEY - Supabase service key
  • STRIPE_SECRET_KEY - Stripe secret key
  • GITHUB_ADMIN_TOKEN - GitHub PAT with org/team admin access
  • DISCORD_BOT_TOKEN - Discord bot token (for Discord sync scripts)

Common Tasks

Check if sync is healthy

npx tsx scripts/takeout/check-subscription-sync.ts
npx tsx scripts/takeout/check-discord-sync.ts

Debug webhook issues

npx tsx scripts/takeout/check-webhook-events.ts

Monthly cleanup of stale claims

# 1. Analyze
npx tsx scripts/takeout/analyze-stale-claims.ts

# 2. Review the files in /tmp/

# 3. Clean up
npx tsx scripts/takeout/cleanup-stale-claims.ts tmp/stale-claims-*.json

Monthly Discord cleanup

# 1. Check Discord sync
npx tsx scripts/takeout/check-discord-sync.ts

# 2. Review tmp/discord-users-to-remove-*.json

# 3. Remove users with cancelled subscriptions
npx tsx scripts/takeout/remove-discord-users.ts tmp/discord-users-to-remove-*.json

Troubleshooting

"Subscriptions without claims"

Some subscriptions may not have claims if:

  • User hasn't clicked "Claim Access" on the website yet
  • Webhook failed when subscription was created
  • User subscribed before claim system existed

Solution: Users can claim access by visiting their account page on hanzogui.dev

"Users not in GitHub team"

If active subscribers aren't in the team:

  • Check if they have claimed access on the website
  • Run check-subscription-sync.ts to identify missing users
  • Verify webhooks are working with check-webhook-events.ts

"Stale claims piling up"

If claims aren't being marked as unclaimed:

  • Verify the bug fix in unclaimProduct.ts is deployed
  • Check webhook events are being received
  • Look for errors in webhook logs

  • apps/gui.hanzo.ai/app/api/stripe/webhook+api.ts - Stripe webhook handler
  • apps/gui.hanzo.ai/features/api/unclaimProduct.ts - Unclaim logic (GitHub + Discord removal)
  • apps/gui.hanzo.ai/features/user/claim-product.ts - Claim creation logic
  • apps/gui.hanzo.ai/features/github/helpers.ts - GitHub team management
  • apps/gui.hanzo.ai/features/discord/helpers.ts - Discord client and constants
  • apps/gui.hanzo.ai/app/api/discord/channel+api.ts - Discord channel management
  • Database tables: claims, discord_invites, subscriptions