/omega-new-project — Provision-and-scaffold pipeline
Turns "new project" into a fully wired project: external services created and connected, the stack scaffolded, then vision → PRD → plan. Idempotent and resumable. Honest about what can and cannot be auto-provisioned.
$ARGUMENTS = [stack] [category] [name] (positional, all optional). When
invoked from the TUI menu these are pre-filled; when typed, any missing value is
asked interactively. Never block on a value the menu already supplied.
STACKS (registry)
| id | Stack | Components |
|---|---|---|
nextstack (default) |
Next.js 16 + Convex + Clerk + Stripe + shadcn-chatbot-kit | App Router, Convex realtime, Clerk auth, Stripe billing, the full shadcn chatbot kit (every chat component), oklch brand system. Optional: React Flow. The smart-default for SaaS/web. |
custom |
Pick-your-own | Let the operator compose the stack — same question flow as the old VPS wizard. |
nextstackis the proposed default (identical to the old-VPS smart default). Choosingcustomopens a short stack chooser (AskUserQuestion), one question each, default pre-selected:
- Project type — SaaS web · Landing/marketing · Mobile (Expo) · Desktop (Tauri) · API backend.
- Backend/DB — Convex (default) · Supabase · Custom API (Hono) · none.
- Auth — Clerk (default) · Better Auth · Auth.js · none.
- Payments — Stripe (default) · LemonSqueezy · none.
- UI — shadcn/ui + oklch (default) · NativeWind (mobile) · Tamagui.
The chosen components drive provisioning (Phase 2) + the scaffold (Phase 3): provision only the picked services, scaffold only the picked libs. Add a new fixed stack as another row + a scaffold block;
customneeds no new row.
PHASE 0 — Resolve inputs (no redundant questions)
Import mode (existing GitHub repo). If an argument is a git URL (git@…,
https://….git, or https://github.com/<owner>/<repo>), this is an IMPORT, not
a fresh scaffold:
Resolve
CATEGORY(ask if missing:customer/side-business/tools) and<projects_dir>exactly as below — so the repo lands in the right place.git clone <url> "<projects_dir>/<category-path>/<repo-name>"(repo-name from the URL unlessNAMEoverrides it). For acustomer, also pick/create its credential group so deploys use that customer's own accounts.Set the category git identity (Phase 4 step 3), detect the stack from the cloned tree (package.json / Cargo.toml / pyproject.toml / go.mod), and register the project in
~/.omega/projects.jsonso it shows in the TUI.Run provisioning (Phase 2) ONLY for services the repo needs but is missing (absent
.envkeys); never scaffold over existing source. Then stop — import is done. The scaffold phases below are for fresh projects.Parse
$ARGUMENTS:STACK,CATEGORY,NAME(positional).Stack: if missing or unknown → AskUserQuestion listing the table above with
nextstackpre-selected as the default. If the user pickscustom, run the stack chooser (the bulleted questions above) and record the picked components — they drive which services get provisioned (Phase 2) and which libs get scaffolded (Phase 3). Any explicit stack arg is honored without asking.Category (the guiding branch) — if missing, AskUserQuestion:
customer→ client work →<projects_dir>/customers/<name>(falls back to an existingclients/if that's the user's layout)side-business→ your own products →<projects_dir>/side-business/<name>(falls back to an existingwork/)tools→ internal tooling / libraries →<projects_dir>/tools/<name>Resolve<projects_dir>from~/.omega/config.toml(keyprojects_dir), never a hardcoded~/VibeCoding. Life →<projects_dir>/1-life/. The category also picks the git identity later (see Phase 4).
Name: if missing → ask. Validate: lowercase,
[a-z0-9-], not already a dir under the resolved category path, not already in~/.omega/projects.json.React Flow option: AskUserQuestion "Inclure React Flow (systèmes de nœuds/diagrammes) ?" yes/no →
WANT_REACTFLOW.Echo the resolved plan in 3 lines and proceed (no confirmation gate when the menu supplied everything — Law L3).
Set PROJECT_DIR="<projects_dir>/<category-path>/<name>" using the resolved
projects_dir and the category→path mapping above (customers / side-business /
tools, falling back to an existing clients/ or work/).
PHASE 1 — Load provisioning credentials
PROV="$HOME/.omega/provisioning/services.env"
if [[ -f "$PROV" ]]; then set -a; source "$PROV"; set +a; else
echo "⚠ No ~/.omega/provisioning/services.env — provisioning will run in MANUAL/PAUSE mode."
fi
Guided token entry: the user fills these tokens without editing any file — in the OmegaOS TUI, Monitor tab → "Set up project provisioning keys" (P) runs a step-by-step wizard (Vercel → Convex → GitHub → Stripe) that writes them safely to
services.env(chmod 600, blanks skipped). If tokens are missing, point the user there before falling back to PAUSE mode.
Build a capability map (each ON only if its token is present):
CAN_VERCEL=[ -n "$VERCEL_TOKEN" ]CAN_CONVEX=[ -n "$CONVEX_TEAM_TOKEN" ]CAN_GITHUB=[ -n "$GITHUB_TOKEN" ]ORgh auth statussucceedsCAN_STRIPE=[ -n "$STRIPE_SECRET_KEY" ]- Clerk uses
CLERK_PROVISION_MODE(pool|pause), never a create-API.
Print the capability map so the user sees exactly what will be automated vs paused. A blank token is a PAUSE, never a silent skip (Law L4 / L5).
PHASE 2 — Provision external services
Run each provisioner; record results into $PROJECT_DIR/.omega-provision.json
(create the dir first). Each is idempotent — re-running detects existing
resources and reuses them. On any hard failure, write the partial state and
continue with the rest (never abort the whole pipeline over one service); list
blockers at the end (Law L4).
2a. GitHub repo
CAN_GITHUB:gh repo create <owner>/<name> --private --source="$PROJECT_DIR" --remote=origin(owner =$GITHUB_OWNERorgh api user -q .login). If$GITHUB_TOKENset, exportGH_TOKEN="$GITHUB_TOKEN"for the call.- Else PAUSE: tell the user to
gh auth login, then re-run (resumable).
2b. Vercel project (FULL auto)
CAN_VERCEL: create the project via API and capture its id.
Link the local dir:TEAM_Q=${VERCEL_TEAM_ID:+?teamId=$VERCEL_TEAM_ID} curl -s -X POST "https://api.vercel.com/v11/projects$TEAM_Q" \ -H "Authorization: Bearer $VERCEL_TOKEN" -H "Content-Type: application/json" \ -d "{\"name\":\"<name>\",\"framework\":\"nextjs\"}"vercel link --yes --project <name> --token "$VERCEL_TOKEN" ${VERCEL_TEAM_ID:+--scope $VERCEL_TEAM_ID}in$PROJECT_DIR. Env vars are pushed in Phase 3 once their values exist.- Else PAUSE.
2c. Convex deployment (FULL auto)
CAN_CONVEX: provision a dev deployment non-interactively. Run inside$PROJECT_DIRafternpm i convex:
UseCONVEX_AGENT_MODE=anonymous npx convex dev --once --configure=new \ --team "$CONVEX_TEAM_SLUG" --project "<name>" 2>&1 || true # capture CONVEX_DEPLOYMENT + NEXT_PUBLIC_CONVEX_URL from .env.local convex wrote$CONVEX_TEAM_TOKENviaCONVEX_DEPLOY_KEYenv if the non-interactive team flag is unavailable in the installed CLI version — verify the CLI's flags first (npx convex dev --help), never assume (Law L1).- Else PAUSE:
npx convex devinteractive.
2d. Clerk (NO create-API → pool or pause)
CLERK_PROVISION_MODE=pool: pop the first unused line of~/.omega/provisioning/clerk-pool.env(pk|sk|label), mark it# USED <name>, captureCLERK_PUBLISHABLE+CLERK_SECRET. If the pool is empty → fall through to pause.pause(default): openhttps://dashboard.clerk.com, instruct the user to create an app + copy the Publishable and Secret keys, then collect them with AskUserQuestion (free-text). Persist immediately so a re-run resumes.- State the reason plainly: "Clerk has no public app-creation API — this step is pool/pause by necessity, not by shortcut."
- Clerk↔Convex JWT template (MANDATORY when both Clerk + Convex are picked — FULL
auto). Convex auth is a JWT bridge:
ConvexProviderWithClerkcallsgetToken({ template: "convex" }), andconvex/auth.config.tsdeclares{ domain: <clerk-issuer>, applicationID: "convex" }. If the Clerk instance has no JWT template namedconvex, the token fetch 404s and EVERY authenticated Convex call throwsUnauthenticated— login renders fine, the app is just silently read/write-dead. This template has a public Backend API, so CREATE it (idempotent):
Verify a real token carries# skip if it already exists; aud MUST equal the auth.config applicationID ("convex") curl -s https://api.clerk.com/v1/jwt_templates -H "Authorization: Bearer $CLERK_SECRET" \ | grep -q '"name":"convex"' || \ curl -s -X POST https://api.clerk.com/v1/jwt_templates \ -H "Authorization: Bearer $CLERK_SECRET" -H "Content-Type: application/json" \ -d '{"name":"convex","claims":{"aud":"convex"},"lifetime":60,"allowed_clock_skew":5}'aud:"convex"+iss:<clerk-issuer>matchingauth.config.ts. This is provisioning, not optional polish — skipping it ships a dead authenticated backend that no build/typecheck can catch.
2e. Stripe
STRIPE_MODE=single+CAN_STRIPE: reuse the master account. Create a restricted key + a webhook endpoint pointing at the (future) Vercel URL + starter product/price via the API:
Capture the webhook signing secret (curl -s https://api.stripe.com/v1/products -u "$STRIPE_SECRET_KEY:" -d name="<name>" curl -s https://api.stripe.com/v1/webhook_endpoints -u "$STRIPE_SECRET_KEY:" \ -d url="https://<name>.vercel.app/api/stripe/webhook" -d "enabled_events[]=checkout.session.completed"whsec_…) and a restricted key for the app.STRIPE_MODE=connect+CAN_STRIPE:POST /v1/accounts(type=standard), capture the connectedacct_…, generate an onboarding link (/v1/account_links), and tell the user KYC onboarding is required before live charges — surface the link, don't pretend it's done.- Else PAUSE.
PHASE 3.0 — Claude Design import (optional — ask first)
Before scaffolding the UI from scratch, ask the operator (AskUserQuestion): *"Did you design this on Claude Design (claude.ai/design)? If so I'll import your work instead of generating UI from zero."*
- No → continue to the normal scaffold below.
- Yes → collect the work, two ways:
- Zip — operator uploads/points to the export
.zip; unzip into a temp dir. From Telegram, accept the uploaded document; from the TUI/CLI, ask for the path. - Command — give them the retrieval command to paste:
npx @anthropic-ai/claude-design pull <share-id-or-url> -o ./_design(or, if they have the share URL,curl -L <export-url> -o design.zip && unzip design.zip -d ./_design).
- Zip — operator uploads/points to the export
- MATCH THE FRONTEND LANGUAGE before importing — ask/detect and only import like-for-like:
- Claude Design output is HTML/CSS → import into an HTML/static or
doc/landing stack (don't paste raw HTML into a.tsxtree). - Output is React/Next (TypeScript) → import components into the Next.js
src/(.tsx), reconciling Tailwind/oklch tokens with the project'sglobals.css. - Mismatch (e.g. HTML design but Next.js project) → convert deliberately (port markup to components, lift styles into the token system) — never drop raw mismatched files in.
- Claude Design output is HTML/CSS → import into an HTML/static or
- Record the design source + language in
BRAND.md/CLAUDE.mdso later steps reuse it, then let/omg-brand-identity(PHASE 5) build on the imported tokens rather than re-inventing.
PHASE 3 — Scaffold the stack (nextstack)
Fetch latest versions first (don't hardcode): if Context7 MCP is available,
ToolSearch("select:mcp__context7__resolve-library-id") then query next,
convex, @clerk/nextjs, stripe, tailwindcss. Otherwise use @latest.
cd "$(dirname "$PROJECT_DIR")"
npx create-next-app@latest "<name>" --ts --app --tailwind --eslint --src-dir --import-alias "@/*" --use-npm --yes
cd "$PROJECT_DIR"
npx shadcn@latest init -d
# Default theme kit — apply the tweakcn theme so every project starts on the same
# polished design tokens (oklch). The cmpuchuiv theme first, then the Claude theme
# layered on top (the Claude tokens win for brand consistency). Both are tweakcn
# registry items; bunx if bun is present, else npx.
bunx shadcn@latest add https://tweakcn.com/r/themes/cmpuchuiv000104jmhbti6yb4 -y 2>/dev/null \
|| npx shadcn@latest add https://tweakcn.com/r/themes/cmpuchuiv000104jmhbti6yb4 -y || true
bunx shadcn@latest add https://tweakcn.com/r/themes/claude.json -y 2>/dev/null \
|| npx shadcn@latest add https://tweakcn.com/r/themes/claude.json -y || true
# Full shadcn chatbot kit — EVERY chat component (Law L5: all of it, not a subset)
npx shadcn@latest add "https://shadcn-chatbot-kit.vercel.app/r/chat.json" -y || \
npx shadcn@latest add chat message prompt-input markdown -y
# Design rule for this project: prefer the imported shadcn/ui components for ALL
# UI — do not hand-roll a button/input/dialog when the library ships one. Record
# this in the project's CLAUDE.md so every agent session honors it.
npm i convex @clerk/nextjs stripe @stripe/stripe-js
[ "$WANT_REACTFLOW" = yes ] && npm i @xyflow/react
Then create:
src/app/providers.tsx— ClerkProvider + ConvexProviderWithClerk wired together.src/proxy.ts— Clerk middleware. Next 16 renamedmiddleware.ts→proxy.ts; shippingsrc/middleware.tson Next 16 means the middleware never runs (auth.protect silently no-ops). Useproxy.tsand verify the build log listsƒ Proxy (Middleware).- Auth pages (NEVER skip — a missing one is a 404 on login): the Clerk catch-all
routes MUST exist whenever
NEXT_PUBLIC_CLERK_SIGN_IN_URL/SIGN_UP_URLpoint at/sign-in//sign-up(the default) — the env is a contract to a real page, and a non-catch-all page still 404s the/sign-in/sso-callbacksub-path. Exact files:
The double-bracket// src/app/sign-in/[[...sign-in]]/page.tsx (and the sign-up twin with <SignUp />) import { SignIn } from "@clerk/nextjs"; export default function SignInPage() { return ( <main className="flex min-h-screen items-center justify-center bg-background p-6"> <SignIn /> </main> ); }[[...sign-in]]is mandatory — it is the catch-all that serves/sign-inAND/sign-in/sso-callback,/sign-in/factor-one, etc. SetNEXT_PUBLIC_CLERK_SIGN_IN_URL/SIGN_UP_URL+NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL/AFTER_SIGN_UP_URL(e.g./chat) so the post-login redirect lands somewhere real, not a 404. convex/schema stub +convex/auth.config.tskeyed to the Clerk issuer.src/app/api/stripe/webhook/route.ts— signature-verified handler.- A
/chatroute mounting the chatbot-kit components end-to-end. - If
WANT_REACTFLOW: a/flowroute with a minimal React Flow canvas. - Brand system:
src/app/globals.cssoklch tokens (full light/dark scale, radii, shadows, typography) +BRAND.md. Pull a coherent palette; this is the "ultra complete" brand foundation the user asked for, not 3 stray variables.
PHASE 4 — Wire keys + register
- Write
$PROJECT_DIR/.env.localfrom the provisioned values:
Also writeNEXT_PUBLIC_CONVEX_URL=… CONVEX_DEPLOYMENT=… NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=… CLERK_SECRET_KEY=… STRIPE_SECRET_KEY=…(restricted) STRIPE_WEBHOOK_SECRET=whsec_… NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=….env.examplewith the same keys blanked (committed). - Push the non-public vars to Vercel (
CAN_VERCEL):vercel env add <KEY> productionvia API/CLI with--token. - Git identity by category (Law R-FICHE): set
git config user.email— customer → that customer's email; side-business / tools / AgentikOS →x@agentik-os.com. Commit the scaffold. Push to origin if the repo was created. - Register in
~/.omega/projects.json(append an entry: name, path, category, created date) so it shows up in the TUI Projects tab immediately. - Telegram setup — propose it, don't force it (optional). AskUserQuestion:
"Set up Telegram for this project?"
- Topic (recommended) — create its forum topic in the hub (
/sync, or the bot'screateForumTopic) so a message in that topic = a mission to its oracle, and publish its/<project>command. Default ON if a hub is configured. - + Dedicated bot (optional) — link a separate Telegram bot token
(BotFather) whitelisted to the operator (the bot's
proj:botlinkflow / thetg-linkpending). Talking to it = this project's oracle, scoped to it. - None — skip; the project still works from the TUI/CLI. Whatever is chosen, never block the pipeline on it.
- Topic (recommended) — create its forum topic in the hub (
- Verify (Law L1):
npm run buildmust pass before declaring scaffold done.
PHASE 5 — Chain into the product pipeline (engine-driven)
Do not re-implement vision/PRD/planning — delegate, in order, scoped to
$PROJECT_DIR:
/omg-vision— emotional positioning →VISION.md- Spawn the project's DEDICATED ORACLE to present the vision (asked for on
every new project). As soon as
VISION.mdexists, dispatch the project oracle to read it and explain the product back to the operator, in their language:omega dispatch "$NAME" "Read $PROJECT_DIR/VISION.md and present this product's VISION to me in one message — soul statement, internal compass, primary persona, the 3 design principles, and what we build next. Concrete and warm, no fluff."This opens a persistent oracle so the operator can immediately discuss + steer the vision. (From the Telegram bot,createProjectalready auto-launches it.) /omg-prd— full doc suite from the vision →docs/PRD.md- Brand — OPT-IN, NOT auto-run in the bootstrap (learned the hard way: the
full
/omg-brand-identityis a 15-agent run that builds a whole Next.js brand-book sub-app — ~1h / 1M+ tokens — and stalls a fresh bootstrap before it ever reaches the planner). So by default the bootstrap does only the lightweight brand foundation: the oklch tokens already laid down in PHASE 3 + theBRAND.mdsummary. That's enough to plan + build a coherent product. Then ASK the operator: "Run the full brand book now (/omg-brand-identity, long) or later?" — default later. Only run the heavy brand-book when they opt in (or--brand); never block the pipeline on it. (Keep--skip=brandto skip even the foundation for non-visual projects.) /omg-planner— generate the typed.planner/tracker.json(a DAG of single-worker-dispatch steps; audits as a terminalwave). Verify it loads:omega plan-status .must print the steps withready N | blocked M.omega plan-run .— the OmegaOS engine executes the plan (the "build"): ready-set → spawn worker → Guardian re-runs eachverify_command→ advance. Sequencing is enforced structurally (a step can NEVER be skipped) and no step is "done" without its verify proof. Watch progress withomega plan-status .. Ifomegais not on PATH, fall back tobun ~/.omega/skills/planner/fallback/plan.ts run ..- FUNCTIONAL ACCEPTANCE GATE — run
/omg-acceptance(the shipped autonomous test-and-heal loop). Mandatory last step, no shortcuts. It Playwright-sweeps every route + console + network + the authenticated golden path, then AUTONOMOUSLY fixes what it finds and re-runs until green (Law L3 — the build resolves its own bugs, it does not stop at "found one"). The gate it enforces: "It builds" is NOT "it works". Before declaring the project done, an agent MUST actually OPEN the running app in a real browser and exercise it end to end:npm run build→ serve the real build (next starton a port).- Run the sweep against a SECURE CONTEXT —
http://127.0.0.1:$PORTor HTTPS, NEVERhttp://<tailnet-or-LAN-IP>:port. Clerk (and any WebCrypto/crypto.subtleauth) only initialises on a secure context =localhost/127.0.0.1ORhttps://. On a rawhttp://100.x.x.xorigin it silently fails withCannot read properties of undefined (reading 'digest')andsecure-context:false— the page renders 200 but login never works. So acceptance-test on127.0.0.1, and the real shareable/prod URL must be HTTPS (Vercel, ortailscale serve --httpswhen the tailnet supports certs). A green sweep onhttp://IPis a false pass — it can't even reach the login form's crypto. - A Playwright sweep that NAVIGATES to every route (landing + each nav link +
each CTA target + every auth page:
/sign-in,/sign-up, the SSO/OAuth callback) and asserts each returns 200 and renders (no 404, no 500, no blank). - Walk the real golden path AS A LOGGED-IN USER, and prove the AUTHENTICATED DATA
ROUND-TRIP — not just "land in-app". Sign in with a real account (create a Clerk
test user via the Backend API;
…+clerk_test@example.comaccepts the dev code424242for deterministic e2e), then DO the core action that hits the backend (send a message) and assert the write actually PERSISTED (the row comes back on reload / a follow-up query returns it). A login that renders but whose first authenticated mutation throwsUnauthenticatedis the silent-dead-backend bug — and the #1 cause is a missing ClerkconvexJWT template (see Phase 2d): the token fetch 404s, Convex sees no identity. So this step MUST exercise a real authenticated call, because build/typecheck/render-200 ALL pass while it's broken. - FAIL on ANY console error OR failed network request during the flow (capture
page.on('console', …)+page.on('response', r => r.status() >= 400)). Thetokens/convex404 and the ConvexUnauthenticatedmutation both surface here — ignore only known third-party noise (walletevmAsk.js, Clerk dev-key warning). A green render with a red console is NOT shipped (rule R-PROD). The planner encodes this as the terminal step (rule 7); if its verify fails, the build is NOT done — fix the missing route/flow/wiring and re-run. This is what stops a project shipping a green build with a 404 login or a dead authenticated backend. (Provisioning/secrets missing → that step legitimately blocks here, surfaced honestly — never faked.)
Full pipeline order: vision → (oracle presents it) → prd → [brand foundation;
full brand book OPT-IN] → planner → build (plan-run). The heavy /omg-brand-identity
is never auto-run in the bootstrap (it stalls the run before the planner) — only the
lightweight oklch-tokens + BRAND.md foundation runs by default; the brand book is a
later on-demand step. Offer to stop after /omg-vision so the user can
review with the oracle, or — in a dispatched (non-interactive) context — proceed
automatically through to omega plan-run (Law L3). The engine, not the LLM, owns
the execution loop from plan-run on.
DONE CRITERIA (grade against this, not vibes — R-RUBRIC)
- Project dir created under the correct category path.
- Every service either provisioned (resource id captured) or explicitly paused with a recorded reason — none silently skipped.
-
.env.localwired;.env.examplecommitted; secrets only in$PROJECT_DIR/.env.local+~/.omega, never staged for git. -
npm run buildpasses. - Project registered in
~/.omega/projects.json(visible in the TUI). -
VISION.mdstarted (pipeline handed off). - A
--- Resume:one-line French recap (R-STYLE).
Report a checklist of provisioned vs paused services + the next command.