Load your collection
Upload a CSV export of the cards you own. Every row is matched against Scryfall — type, color identity, legality, price and EDHREC rank — so the deck that follows is grounded in real data.
Drop your CSV here
Or pick a file from your computer. One row per card is enough; quantities are optional.
Name, Card Name, Karte) and an optional quantity column (Quantity, Count, Anzahl). Everything else is ignored, so exports from Manabox, Moxfield, Archidekt, Deckbox and Delver Lens work as-is. Comma and semicolon delimiters are both handled.
Your collection
Every card matched from your CSV. Organise cards into binders, filter by color identity or search by name.
Choose your commander
Legendary creatures from your own collection, or the most-played commanders that fit your colors.
Decks
Every deck you've built, saved locally. Open one to edit it card by card, or start a new one from a commander.
Scan cards
Point the camera at a card so its name sits inside the dashed band, then capture. The name is read on-device and matched against Scryfall — you confirm every card before it enters your collection.
Match
Search
Search every card ever printed. Filters combine into a single Scryfall query — cards you already own are marked automatically.
Settings
Optional configuration. Everything here is stored locally in your browser.
https://abcdefgh.supabase.co.sb_secret_… key here — it bypasses row level security.
create table if not exists public.command_forge (
user_id uuid primary key references auth.users(id) on delete cascade,
payload jsonb not null default '{}'::jsonb,
updated_at timestamptz not null default now()
);
alter table public.command_forge enable row level security;
create policy "read own row" on public.command_forge
for select using (auth.uid() = user_id);
create policy "insert own row" on public.command_forge
for insert with check (auth.uid() = user_id);
create policy "update own row" on public.command_forge
for update using (auth.uid() = user_id) with check (auth.uid() = user_id);
export default {
async fetch(request) {
const url = new URL(request.url);
const path = url.pathname.replace(/^\/+/, "");
if (!path) return new Response("ok");
if (request.method === "OPTIONS") return new Response(null, { headers: cors() });
const upstream = await fetch("https://json.edhrec.com/" + path, {
headers: { Accept: "application/json" },
cf: { cacheTtl: 86400, cacheEverything: true }
});
return new Response(upstream.body, {
status: upstream.status,
headers: { ...cors(), "Content-Type": "application/json" }
});
}
};
function cors() {
return {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,OPTIONS",
"Cache-Control": "public, max-age=86400"
};
}