Post once. Sell everywhere. The operating system for selling on every marketplace you use.
CommerceTypeScript · JavaScriptGitHub @JMas100Availability needs another checkOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A reseller operating system that lets you create one listing and cross-post it to multiple marketplaces.
Needs a local Postgres. docker-compose.yml provides one (persistent, isolated from
production):
docker compose up -d
npm install
npx prisma migrate dev
npm run seed
npm run dev
Open http://localhost:3000 and sign in with [email protected] / demo123.
Copy .env.example to .env and set at least:
| Variable | Purpose |
|---|---|
DATABASE_URL / DIRECT_URL | Pooled and direct Postgres connection strings |
NEXTAUTH_URL, NEXTAUTH_SECRET | NextAuth base URL and signing secret |
MASTER_KEY | 64-char hex used only to encrypt marketplace tokens at rest |
INNGEST_DEV | Set to 1 for local dev, so the SDK talks to npx inngest-cli@latest dev instead of Inngest's cloud |
INNGEST_EVENT_KEY / INNGEST_SIGNING_KEY | Production only -- from an app at app.inngest.com |
BROWSER_WORKER_URL / BROWSER_WORKER_SECRET | Production only -- see worker/README.md. Unset locally: manual-adapter platforms run in-process instead |
STORAGE_PROVIDER | Storage adapter to use (default r2) |
R2_ACCOUNT_ID | Cloudflare account ID (used to build the R2 S3 endpoint) |
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY | R2 API token credentials with object read/write on the bucket |
R2_BUCKET | R2 bucket name that stores listing photos |
R2_PUBLIC_BASE_URL | Public CDN/custom-domain base URL images are served from (e.g. https://images.postmost.co) |
S3_ENDPOINT | Optional S3 endpoint override for another S3-compatible host (e.g. MinIO locally); defaults to R2 |
NEXT_IMAGE_HOSTS | Optional extra comma-separated hostnames allowed for next/image |
BG_REMOVER | Background-removal adapter: birefnet (default) or photoroom |
FAL_KEY | fal.ai key used by the BiRefNet adapter |
BIREFNET_MODEL / BIREFNET_RESOLUTION | Optional BiRefNet variant and operating resolution |
PHOTOROOM_API_KEY | PhotoRoom key for the premium fallback |
RESEND_API_KEY | Resend key used to send password-reset emails |
EMAIL_FROM | From address for outgoing email; must be a domain verified in Resend |
Outside production, a DATABASE_URL that is not a *.neon.tech host uses Prisma's standard TCP
client instead of the Neon serverless driver, so a local Postgres works without any code change.
Listing photos live in object storage, not in the database. lib/storage/ mirrors the marketplace
adapter pattern: lib/storage/types.ts defines StorageAdapter, lib/storage/adapters/r2.ts
implements it with the AWS S3 SDK pointed at Cloudflare R2, and getStorage() picks the adapter
from STORAGE_PROVIDER. Because it speaks the plain S3 API, swapping to AWS S3 or any other
S3-compatible host is a new adapter plus config — no caller changes.
Upload flow (browser never proxies bytes through the serverless function, so the 4.5 MB body limit doesn't apply):
/api/upload, which requires a session (401 otherwise)
and rejects non-image types or files over 10 MB.PUT URLs plus the public CDN URL for each key
(listings/<userId>/<uuid>.<ext>).https:// URL on the listing.AI "Enhance photo" results are uploaded the same way, so enhanced images are hosted URLs too.
Legacy base64 data: photos already in the database still render, still validate, and still
cross-post through the extension.
The browser PUTs uploads directly and the extension re-fetches images to attach them to marketplace forms, so the bucket (and the custom domain in front of it) needs CORS. In the Cloudflare dashboard under R2 > your bucket > Settings > CORS policy:
[
{
"AllowedOrigins": ["https://your-app-domain.com", "http://localhost:3000"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["content-type", "cache-control"],
"ExposeHeaders": ["etag"],
"MaxAgeSeconds": 3600
},
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 86400
}
]
Public reads require either R2 public access or a custom domain bound to the bucket; point
R2_PUBLIC_BASE_URL at it.
lib/ai/background/ is a provider registry in the same shape as storage and marketplaces:
getBackgroundRemover() resolves BG_REMOVER (default birefnet), and
getConfiguredBackgroundRemover() falls back to whichever provider actually has credentials so a
missing key degrades instead of throwing. Adapters return raw cut-out bytes; lib/ai/optimize.ts
encodes them as a data: URL that the listing form uploads to storage, so enhanced photos end up
hosted like any other.
| Adapter | Backend | Notes |
|---|---|---|
birefnet | fal-ai/birefnet/v2 | MIT-licensed weights, billed per GPU-second — the unlimited-removals default |
photoroom | sdk.photoroom.com/v1/segment | Premium fallback for hair, sheer fabric, white-on-white; sandbox keys are watermarked |
To compare providers on your own photos before choosing a production default:
npx tsx -r dotenv/config scripts/bake-off-bg-removal.ts --input ./samples --out ./bakeoff
This is the beginning of the README. Read the rest at the original. Read the full README ↗