中文版 | English
VibeSKU Clips turns product material into a localised 15-second UGC video. An operator submits a product link or images with a brief; the platform reads the product, writes a market-specific script, optionally draws a storyboard, generates the clip, checks it against a quality gate, and hands back the asset with a delivery manifest.
The platform produces material and the manifest. Account login, publishing, storefront links, product tagging, and performance observation stay with the operations team.
- Product understanding. Reads a product link, uploaded images, and a brief into recorded product facts. Anything the material does not support is flagged for a top-up rather than invented.
- Localised scripts. Output language and target market are separate settings, so Spanish for Mexico and Spanish for Spain are different scripts, each with its own voiceover, subtitles, and publish caption.
- Three delivery formats. Presenter, everyday scene, and how-to-use, each a production recipe with its own beat structure and rotating opening angles.
- Talent consistency. Upload a licensed photo or describe a fictional adult performer. The generated opening frame anchors the look for the whole clip.
- One clip at a time. Product, talent, script, optional storyboard, and video form one guided path, with a human confirmation before each expensive step.
- A quality gate before review. Duration, voiceover length, caption safe area, product accuracy, performer consistency, and local expression are checked on every clip.
- Review and regeneration. Each finished clip can be selected, rejected, or regenerated while preserving its product, script, and talent lineage.
- Exports with a manifest. Every asset includes its reference number, product, language, market, talent, licence note, and disclosure line.
- Asset and consumption records. Products, talent, scripts, and approved clips keep their source, licence, and version lineage; analysis, scripting, rendering, retries, and regenerations are metered separately.
Delivery specification: 15 seconds, selectable 9:16 or 16:9 framing, and a provider-compatible resolution, with cover, subtitles, publish caption, and a synthetic-content disclosure.
| Stage | Job | What it does |
|---|---|---|
| Intake | ugc.product.ingest |
Imports the product page through Firecrawl, reads facts, and waits for review or missing material |
| Script | ugc.work.script |
Writes one script from the confirmed product, talent, language, and market |
| Storyboard | ugc.work.storyboard |
Draws one key frame per beat and stops for confirmation |
| Video | ugc.work.video |
Generates one clip from the script and selected references, archives it, writes subtitles, and runs quality checks |
Jobs run on pg-boss through the repository's task-run outbox, so each step
survives a restart and can be retried on its own. Images go through Prism;
video uses the provider selected by VIDEO_GENERATION_PROVIDER, product-page
imports use Firecrawl, and scripting uses any OpenAI-compatible endpoint. Prism video is fixed to H3; lk666 also
offers Seedance 2.0 and 2.5, with each model exposing only its supported output
resolutions.
Business logic lives in src/lib/ugc, the job handlers in src/lib/jobs/ugc,
and the operator surfaces under src/app/dashboard.
| Category | Technology |
|---|---|
| Framework | Next.js 16 |
| Language | TypeScript |
| UI | React, shadcn/ui, Tailwind v4, Lucide React (icons) |
| Auth | Better-Auth |
| Database | PostgreSQL |
| ORM | Drizzle ORM |
| Payments | Stripe |
| Media | Prism for images; selectable Prism or lk666 video generation |
| AI | Vercel AI SDK v7, any OpenAI-compatible LLM endpoint |
| Resend, React Email | |
| Forms | React Hook Form, Zod |
| Deployment | Zeabur or Docker |
| Package Manager | pnpm |
Ensure you have the following software installed in your development environment:
# Clone the repository
git clone https://github.com/UllrAI/VibeSKU-Clips.git
# Enter project directory
cd vibesku-clips
# Install dependencies with pnpm
pnpm installThe project is configured through environment variables. First, copy the example file:
cp .env.example .envThen edit .env and fill in the core values plus the credentials for the
features enabled in src/lib/config/site.js.
SITE_CONFIG is the client-safe source of truth for brand, contact, links,
assets, and the static emailAuth, billing, uploads, and ai feature
switches. All features default to enabled. Disable an unused feature there before
removing its environment variables. Disabled features are removed from
navigation and guarded at their pages, APIs, plugins, and server actions.
If emailAuth is disabled, configure at least one complete OAuth provider so
the web sign-in flow remains usable. Credentials remain server-only and must
never be added to SITE_CONFIG.
| Variable Name | Description | Example |
|---|---|---|
DATABASE_URL |
Required. PostgreSQL connection string. | postgresql://user:password@localhost:5432/db_name |
JOB_DATABASE_URL |
Optional pg-boss database; defaults to DATABASE_URL. |
postgresql://user:password@localhost:5432/db_name |
JOB_DB_POOL_SIZE |
Optional pg-boss pool size per process; defaults to 3. |
3 |
WORKER_GRACEFUL_TIMEOUT_MS |
Optional Worker SIGTERM drain deadline; defaults to 30 seconds. | 30000 |
RATE_LIMIT_IP_HEADER |
Optional trusted client-IP header; defaults to Zeabur. | x-forwarded-for |
NEXT_PUBLIC_APP_URL |
Required. Public URL of your deployed app. | http://localhost:3000 or https://yourdomain.com |
BING_SITE_VERIFICATION |
Optional Bing Webmaster msvalidate.01 verification token. |
Value issued for your deployed hostname |
NEXT_PUBLIC_UMAMI_SCRIPT_URL |
Optional Umami tracker URL; set all three Umami variables. | https://analytics.example.com/script.js |
NEXT_PUBLIC_UMAMI_WEBSITE_ID |
Optional deployment-specific Umami website UUID. | 00000000-0000-4000-8000-000000000000 |
NEXT_PUBLIC_UMAMI_DOMAINS |
Optional comma-separated host allowlist for this deployment. | yourdomain.com |
BETTER_AUTH_SECRET |
Required. Random session secret, at least 32 characters. | Generate with openssl rand -base64 32 |
RESEND_API_KEY |
Required when emailAuth is enabled. Resend API key. |
re_xxxxxxxxxxxxxxxx |
RESEND_EMAIL_FROM |
Required when emailAuth is enabled. Verified sender. |
noreply@your-verified-domain.com |
LLM_API_KEY |
Required when ai is enabled. Key for your LLM endpoint. |
sk-... |
LLM_BASE_URL |
Optional OpenAI-compatible endpoint; defaults to OpenRouter. | https://openrouter.ai/api/v1 |
AI_DEFAULT_MODEL |
Optional model id; defaults to openai/gpt-5.6-luna. |
openai/gpt-5.6-luna |
FIRECRAWL_API_BASE_URL |
Optional Firecrawl-compatible API root. | https://api.firecrawl.dev/v2 |
FIRECRAWL_API_KEY |
Required for product URL imports. Worker-only API key. | fc-... |
VIDEO_GENERATION_PROVIDER |
Video backend: prism or lk666; defaults to Prism. |
lk666 |
PRISM_API_BASE_URL |
Prism root; staging outside production, production in prod. | https://staging-prism.ullrai.com/api/v1 |
PRISM_API_KEY |
Required for rendering. Prism API key for the chosen host. | pk_... |
PRISM_API_SECRET |
Required for rendering. Prism secret for the chosen host. | sk_... |
LK666_API_BASE_URL |
Optional lk666-compatible API root. | https://api.lk888.ai |
LK666_API_KEY |
Required when the selected video backend is lk666. |
sk-... |
STRIPE_SECRET_KEY |
Required for billing. Prefer a least-privilege restricted key. | rk_test_... or rk_live_... |
STRIPE_ENVIRONMENT |
Stripe mode; defaults to test_mode. |
test_mode or live_mode |
STRIPE_WEBHOOK_SECRET |
Required when billing is enabled. Endpoint signing secret. |
whsec_your_webhook_secret |
R2_ENDPOINT |
Required when uploads is enabled. R2 API endpoint. |
https://<ACCOUNT_ID>.r2.cloudflarestorage.com |
R2_ACCESS_KEY_ID |
Required when uploads is enabled. R2 access key ID. |
your_r2_access_key_id |
R2_SECRET_ACCESS_KEY |
Required when uploads is enabled. R2 secret key. |
your_r2_secret_access_key |
R2_BUCKET_NAME |
Required when uploads is enabled. R2 bucket name. |
your_r2_bucket_name |
GITHUB_CLIENT_ID |
Optional. GitHub OAuth Client ID. | your_github_client_id |
GITHUB_CLIENT_SECRET |
Optional. GitHub OAuth Client Secret. | your_github_client_secret |
GOOGLE_CLIENT_ID |
Optional. Google OAuth Client ID. | your_google_client_id |
GOOGLE_CLIENT_SECRET |
Optional. Google OAuth Client Secret. | your_google_client_secret |
LINKEDIN_CLIENT_ID |
Optional. LinkedIn OAuth Client ID. | your_linkedin_client_id |
LINKEDIN_CLIENT_SECRET |
Optional. LinkedIn OAuth Client Secret. | your_linkedin_client_secret |
Tip: You can generate a secure key using the following command:
openssl rand -base64 32Optional local CLI auth: for scripts, local agents, or quick terminal access, you can export
VIBESKU_CLIPS_CLI_API_KEY=ssk_...instead of storing credentials in the CLI config.
Umami tracking is optional and disabled unless all three public Umami variables
are set. Each deployment must create its own website in Umami and use its own
website ID; never copy the maintainer deployment's ID into a fork. The tracker
honors Do Not Track, excludes URL search parameters, and only records the hosts
listed in NEXT_PUBLIC_UMAMI_DOMAINS.
The stable event vocabulary is cta_click, signup_click, signup_submit,
signup_link_sent, login_submit, login_link_sent, signup_success,
pricing_view, payment_start, and payment_success.
Tracking is best-effort and never controls authentication or billing behavior.
Umami's accounting remains separate from provider webhooks and database records.
This configuration is cookieless, but operators remain responsible for their privacy notice and any consent flow required by the data they add or the jurisdictions they serve. Verification and SEO measurement procedures are in SEO growth operations.
Production notes:
Test and live Stripe catalog IDs are intentionally separate in
src/lib/billing/stripe/prices.ts. Set STRIPE_ENVIRONMENT and its matching
STRIPE_SECRET_KEY, then run pnpm stripe:sync-products. Each tier owns one
stable Stripe Product; a price change creates a new Price under that Product,
deactivates the old Price for new checkouts, and leaves existing subscriptions
on their original amount. Webhooks resolve the tier from the Product ID, so no
finite or unbounded retired-Price list is needed. Review and commit the
selected-environment configuration change before deploying. Checkout fails
closed when the active environment has no configured Price ID.
Webhook retry, idempotency, and manual replay behavior are documented in Billing webhook operations.
This project uses a single Drizzle config file, src/database/config.ts, and a single committed migration history in src/database/migrations/. The target database is selected only by DATABASE_URL.
For fast local iteration against your own database:
pnpm db:pushIf the schema change should be reviewed, committed, or shared with other environments, create and apply a real migration instead:
pnpm db:generate
pnpm db:migrateShared environments should use committed SQL migrations only:
# 1. Generate and commit the migration from your schema change
pnpm db:generate
# 2. Run the committed migration once against the target DATABASE_URL
pnpm db:migrate
# 3. Deploy the application after the migration succeedsRecommended release practice
- Never use
pnpm db:pushin staging or production.- Keep one migration history for all environments. Do not split migrations into dev/prod trees.
- Run
pnpm db:migrateas a dedicated one-shot release step in CI/CD or your deploy platform.- Do not run migrations on every application process startup.
- Make schema changes backward-compatible when possible, so app rollout and migration timing stay safe.
The project uses Content Collections plus plain Markdown files for blog content. Posts live in locale-scoped paths such as content/blog/en/*.md and content/blog/zh-Hans/*.md, authors live in content/authors/*.json, and build-time generation produces typed collections for the blog pages and sitemap.
- Authoring workflow: Add or edit posts directly in the repository with frontmatter and Markdown content.
- Generated content data: Run
pnpm content:buildto refresh the generated collections manually. The Next.js plugin handles development and production builds; test and type-check scripts invoke the generator explicitly. - Production behavior: There is no CMS admin route or runtime content API. All blog content is built from the repository content files.
This starter distinguishes clearly between human auth and machine auth:
- Browser users: Better Auth session cookies for the web app
- Server-to-server and agent access: user-managed API keys
- Local developer tools: browser-approved device login via
vibesku-clips-cli
What ships today:
- versioned machine endpoints under
/api/v1/* - API key creation and revocation in Dashboard Settings
- CLI session review and revocation in Dashboard Settings
- a terminal workflow for signing in from local tools without reusing browser session tokens
Quick examples:
# Sign in a local CLI through the browser
pnpm vibesku-clips-cli -- auth login --base-url http://localhost:3000
# Check current CLI auth state
pnpm vibesku-clips-cli -- auth status --base-url http://localhost:3000
# Use an API key for scripts or coding agents
VIBESKU_CLIPS_CLI_API_KEY=ssk_your_key_here pnpm vibesku-clips-cli -- auth status --base-url http://localhost:3000The web app exposes management surfaces at /dashboard/developer for both API keys and authorized CLI sessions.
pnpm devNow your application should be running at http://localhost:3000!
For security reasons, the first registered user is not promoted automatically. Use the admin script after the user has signed up normally:
pnpm set:admin --email=your-email@example.comThe command loads .env if it exists and otherwise uses the current process environment, so the same command works locally and on a server.
After successful execution, the user receives super_admin privileges and can access /dashboard/admin.
Security tips
- Grant this role only to trusted users.
- Run the command in a secure environment with the correct
DATABASE_URL.
| Script | Description |
|---|---|
pnpm dev |
Start development server. |
pnpm build |
Build application for production. |
pnpm start |
Start production server. |
pnpm vibesku-clips-cli |
Run the first-party CLI for device login and API verification. |
pnpm lint |
Check code for linting errors. |
pnpm dead-code:check |
Detect unused files, exports, and dependencies. |
pnpm type-check |
Run TypeScript type checking. |
pnpm test |
Run the Jest test suite. |
pnpm test:coverage |
Run Jest and generate a coverage report. |
pnpm test:e2e |
Build and run Playwright E2E smoke tests. |
pnpm prettier:format |
Format all code using Prettier. |
pnpm set:admin |
Promote specified email user to super admin. |
This repository includes a Playwright smoke test suite in e2e/ for the most important browser-level flows:
- unauthenticated dashboard redirect
- authenticated dashboard access
- admin permission gating
- locale canonicalization for marketing routes
- API key creation and machine-auth verification
- browser-approved device auth for CLI sign-in
- Use
ShellContainerfor the marketing header, footer, and other truly wide layouts. - Use
SectionContainerfor standard marketing sections and non-dashboard page bodies. - Use
ReadingContainerfor blog articles, legal pages, and other long-form reading surfaces. - Use
CompactContainerfor auth flows. - Use
FocusContainerfor payment status and other centered cards that need more space. - Keep full-bleed backgrounds separate from content width. Backgrounds can span the viewport while content stays inside one semantic container.
Run the suite with:
createdb vibesku_e2e
# Add E2E_DATABASE_URL=postgresql://.../vibesku_e2e to .env
pnpm test:e2eE2E_DATABASE_URL is mandatory and must point to a dedicated local PostgreSQL database whose name contains a standalone e2e or test segment. Outside CI, the runner rejects the regular DATABASE_URL to prevent test users, API keys, CLI tokens, device codes, and rate-limit state from reaching a development or shared database. It applies migrations, builds the app, starts the production server, and cleans E2E fixtures before and after the suite.
The test-only session route is enabled only while Playwright is running against the declared E2E database. It also requires an explicit E2E_TEST_SECRET of at least 32 characters and is disabled for non-local production deployments. Playwright generates a per-run secret when CI does not provide one.
| Script | Description |
|---|---|
pnpm analyze |
Build application and generate bundle analysis report. |
pnpm analyze:dev |
Enable bundle analysis in development mode. |
| Script | Description |
|---|---|
pnpm db:generate |
Generate SQL migration files from schema changes. |
pnpm db:migrate |
Apply committed migrations to the database selected by DATABASE_URL. |
pnpm db:push |
Local development only. Sync schema directly without creating migration |
This project integrates a secure file upload system based on Cloudflare R2. Direct and server-side uploads share the same short-lived upload intent, per-user byte quotas, one-time completion, and orphan cleanup flow.
- Create R2 Bucket: Log into Cloudflare Dashboard, navigate to R2 and create a new bucket.
- Get API Token: In the R2 overview page, click "Manage R2 API Tokens", create a token with "Object Read & Write" permissions. Note down the
Access Key IDandSecret Access Key. - Set Environment Variables: Fill your R2 credentials and information into the
.envfile. - Configure CORS Policy: To allow browsers to upload files directly, you need to configure CORS policy in your R2 bucket's "Settings". Add the following configuration, replacing the URLs in
AllowedOriginswith your own:
[
{
"AllowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["Content-Type", "If-None-Match"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]Direct uploads use protocol version 2. The presign response declares the
required Content-Type and If-None-Match: * headers, so an issued object key
can only be written once. The signed URL lasts 15 minutes and its database
reservation lasts one hour. Cancelling releases quota immediately without
shortening that original expiry. After expiry, cleanup deletes the object but
retains a tombstone for 24 hours and deletes the object again before removing
the intent. This second check catches a late PUT that began while the signed URL
was still valid. Clients built against the older unsigned-header protocol must
be refreshed when this version is deployed.
Every completion requires a database-backed intent; there is no unauthenticated
or key-only completion path. Per-user storage quotas are 1 GiB per rolling 24
hours and 5 GiB in total, defined as DAILY_QUOTA_BYTES and TOTAL_QUOTA_BYTES
in src/lib/config/upload.ts.
The Worker drains expired upload intents itself: every few seconds it recovers
stale cleanup claims, deletes abandoned R2 objects, and removes tombstoned
files (maintain() in scripts/worker.ts). There is nothing to schedule and
no secret to hold — as long as the Worker runs, cleanup runs. R2 lifecycle
rules may additionally abort incomplete multipart uploads after one day, but
they do not replace this database-aware cleanup.
We provide a powerful FileUploader component that supports drag-and-drop, progress display, image compression, and error handling.
import { FileUploader } from "@/components/ui/file-uploader";
function MyComponent() {
const handleUploadComplete = (files) => {
console.log("Upload complete:", files);
// Handle uploaded file information here
};
return (
<FileUploader
acceptedFileTypes={["image/png", "image/jpeg", "application/pdf"]}
maxFileSize={5 * 1024 * 1024} // 5MB
maxFiles={3}
onUploadComplete={handleUploadComplete}
/>
);
}Note: This project uses a
srcdirectory structure. All components and library files are located in thesrc/directory and can be accessed through the@/path mapping which resolves tosrc/.
The component includes built-in client-side image compression functionality that can reduce image file size before upload, saving bandwidth and storage space.
<FileUploader
acceptedFileTypes={["image/png", "image/jpeg", "image/webp"]}
enableImageCompression={true}
imageCompressionQuality={0.7} // Compression quality (0.1-1.0)
imageCompressionMaxWidth={1200} // Maximum width after compression
/>This project integrates @next/bundle-analyzer to help you analyze and optimize your application's bundle size.
# Analyze production build
pnpm analyze
# Analyze in development mode
pnpm analyze:devAfter execution, bundle size analysis reports for both client and server will automatically open in your browser.
- Dynamic Imports: Use
next/dynamicfor code splitting of large components or libraries that aren't needed on first screen. - Dependency Optimization:
- Tree Shaking: Ensure you only import what you need from libraries, e.g.,
import { debounce } from 'lodash-es';instead ofimport _ from 'lodash';. - Lightweight Alternatives: Consider using lighter libraries, e.g., replace
moment.jswithdate-fns.
- Tree Shaking: Ensure you only import what you need from libraries, e.g.,
- Image Optimization: Prioritize using Next.js
<Image>component and enable WebP format.
The production reference deployment uses Zeabur. The repository also includes a standalone multi-stage Docker build.
Save 10% on a Zeabur server: Purchase a server at Zeabur and enter referral code
visoarat checkout.
Configure the production Zeabur service to deploy the prod branch, not
the default development branch (main in this repository). Pushing a
release/vX.Y.Z tag matching the version in package.json runs
promote-release-to-prod.yml,
which verifies that the tagged commit belongs to the repository's default
branch (main at present) before moving prod to that commit. Zeabur deploys
only after the promotion succeeds. Fork maintainers can reuse the same setup;
see the Zeabur deployment guide.
-
Merge the reviewed commit into the default branch and wait for the Quality workflow to pass.
-
Configure every required variable from
.env.example. SetNEXT_PUBLIC_APP_URLto the final HTTPS origin before building because canonical URLs and client configuration are compiled from it. Keep the user upload bucket private; see architecture notes. -
Set
PRODUCTION_DATABASE_URLin the GitHubproductionenvironment, plusPRODUCTION_JOB_DATABASE_URLfor a separate queue database. The release workflow checks the exact SHA's Quality result and runs migrations before promotion. -
Update the version in
package.json, then tag that commit with an annotatedrelease/vX.Y.Ztag using the same version and push it:git tag -a release/v1.2.3 -m "Release v1.2.3" git push origin release/v1.2.3 -
Wait for the promotion workflow and the subsequent Zeabur deployment to succeed. Use
/api/healthfor liveness and/api/readyfor database-backed readiness. -
Verify the public origin, both locale URL variants, authentication redirects, Dashboard access,
robots.txt,sitemap.xml, and application logs.
Docker Compose follows the same order with a one-shot migrate service. See
docker/README.md for local and self-hosted instructions.
The optional GitHub maintenance schedule is best-effort and runs only from the
default branch. Enable scheduled Actions explicitly in a fork, monitor its
latest successful run, and use a platform scheduler when timing is an SLA.
This project is licensed under the MIT license.
