EvoX Genesis
EvoX Genesis는 고수준 목표를 재귀적 소프트웨어 세계로 전환해, 유한 에이전트가 개발하고 승인된 결과만 유지됩니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗This is a Next.js project bootstrapped with create-next-app.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
Open http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
To learn more about Next.js, take a look at the following resources:
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.
Commercial billing catalog records are platform-owned and are not created by Billing page requests. Deploy them explicitly after migrations and before the application:
npx prisma migrate deploy
npm run billing:seed-catalog
npm run billing:verify-catalog
# deploy application
The catalog seed is idempotent and is configured in prisma.config.ts for explicit Prisma seed operations. Do not add catalog seeding to Vercel builds.
Set these server-side variables in Vercel/Next.js runtime:
OTP_PROVIDER (optional): twilio, msg91, or mockTwilio config (required when using Twilio):
TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENTWILIO_VERIFY_SERVICE_SIDMSG91 config (required when using MSG91):
MSG91_AUTH_KEYMSG91_TEMPLATE_IDProvider selection behavior:
OTP_PROVIDER is explicitly set and configured, that provider is used.OTP_PROVIDER is explicitly set but missing required config, the API logs a warning and falls back.OTP_PROVIDER is not set, fallback order is: twilio -> msg91 -> mock.Never expose OTP provider secrets as NEXT_PUBLIC_*.
Which number sends what (services/whatsapp/sender.ts):
| Message | Shop with its own number | Shop without one |
|---|---|---|
| Login code (OTP) | the shop's number | the LoopD2C platform number |
| Abandoned-checkout reminders | the shop's number | not sent |
| Exchange, cancellation, issue and refund updates | the shop's number | not sent |
The platform number only ever sends authentication (OTP) messages, so no store's marketing or order updates go out under LoopD2C's name or another store's. There is no global fallback: every send names its sender.
Merchant Settings → WhatsApp (stored in MerchantWhatsAppAccount, token
encrypted with TOKEN_ENCRYPTION_KEY): phone number ID, WhatsApp Business
Account ID, permanent system-user token, template language, and toggles for
OTP, abandoned-checkout reminders and customer updates. Save & check
connection makes a read-only Graph call and shows the number's name and
quality rating. Own-number OTP is metered as MERCHANT_WHATSAPP (not billed).
WHATSAPP_OTP_ENABLED — must be trueWHATSAPP_OTP_ACCESS_TOKEN — system-user token with whatsapp_business_messagingWHATSAPP_OTP_PHONE_NUMBER_IDWHATSAPP_OTP_TEMPLATE_NAME (optional; defaults to loopd2c_login_otp)WHATSAPP_OTP_TEMPLATE_LANGUAGE (optional; defaults to en)WHATSAPP_OTP_COUNTRY_PREFIXES (optional; comma-separated, defaults to +91; applies to every sender)WHATSAPP_OTP_GRAPH_VERSION (optional; defaults to v20.0)Metered as PLATFORM_WHATSAPP.
WhatsApp first (authentication template, no DLT), Twilio SMS fallback when the send fails; shoppers can tap "Get code by SMS" after 30s. The code is generated, hashed and verified by us (4 digits, 5-minute expiry, 5 attempts, 25s resend cooldown, 5 sends per 15 minutes per number). Template: category Authentication, code delivery Copy code button.
Automatic SMS backup (services/whatsapp/otp-sms-backup.ts): Meta accepts a
WhatsApp OTP and reports a failed delivery later on the status webhook (number not
on WhatsApp, or the WhatsApp account out of funds: 130429 / 131042). When that
happens to a pending, unexpired WhatsApp challenge, the code is sent again by
platform Twilio SMS (when the shop's OTP settings allow SMS): the WhatsApp
challenge is superseded and the SMS one is verified instead. At most 3 automatic
backups per number per 15 minutes; the outcome is kept in the challenge's
metadata.smsBackup.
Template health (services/whatsapp/template-health.ts): when 5 or more
template messages (OTPs, order updates, nudges) from a merchant's number fail
within 6 hours with an account-level error (130429 and 131042 usually mean
an empty prepaid balance or a payment problem; also 131048, 131031), the
shop's admin recipients get one email (at most every 12 hours) and the WhatsApp
inbox and WhatsApp settings show a warning with the fix. It clears on the next
delivered template. Fields: MerchantWhatsAppAccount.templateFailingSince,
templateFailureError, templateAlertEmailedAt (migration
20261105120000_whatsapp_template_health).
https://<app-host>/api/webhooks/whatsapp, subscribed to messages.
WHATSAPP_META_WEBHOOK_VERIFY_TOKEN — answers Meta's verification challengeWHATSAPP_META_APP_SECRET — Meta app secret(s), comma-separated when numbers
live in different apps; verifies X-Hub-Signature-256. Without it every POST
is rejected.WHATSAPP_META_GRAPH_VERSION (optional; defaults to v20.0)STOP / "Stop promotions" record an opt-out for the business number that
received it (AuditEvent whatsapp.opt_out, <phone_number_id>:<customer>), so
a STOP to one store does not stop another; START opts back in.
services/checkout-recovery/whatsapp-checkout-recovery.ts, run by the 15-minute
checkout-recovery cron for shops whose own number has reminders switched on.
Reads Shopify abandoned checkouts whose phone was verified by the OTP gate
(megaska_verified_phone) and sends at most two messages per checkout: the
first once it has been idle 15 minutes (15–30 minutes after the shopper left;
skipped if it cannot go out within 6 hours) and a reminder 24 hours after the
first if they still have not ordered (dropped after 30 hours). A phone gets at
most two reminders from a shop in any 7 days. Opted-out numbers are skipped.
Needs CHECKOUT_RECOVERY_SIGNING_SECRET (32+ chars, shared with the COD email).
Both templates need one URL button
https://<store-domain>/apps/loopd2c/checkout/bag?t={{1}}; only the token is
sent. The link rebuilds the bag and opens the drawer with Pay online and COD.
WHATSAPP_INTENT_RECOVERY_ENABLED — legacy recovery for LoopD2C express-checkout
intents (in-drawer checkout modal); off unless true, own number only.
services/orders/whatsapp-cod-confirmation.ts, switched on per shop
(Merchant Settings → WhatsApp → COD order confirmation). The 15-minute
checkout-recovery cron asks each new cash-on-delivery order (3 minutes to 6
hours old, unshipped, not cancelled) once, from the shop's own number, with a
Utility template (default cod_order_confirmation): body {{1}} first
name, {{2}} order number, {{3}} total; two Quick reply buttons, Confirm
order then Cancel order (payloads codc:confirm|cancel:<order id>). The
webhook handles the taps (only from a phone on that order): Confirm → tag
cod-confirmed + thank-you; Cancel → tag cod-cancel-requested, chat flagged
for the team, email; nothing is cancelled automatically. No reply in 12 hours →
tag cod-no-response (no second message). Events: AuditEvent
WHATSAPP_COD_CONFIRMATION_SENT / _RESPONSE on the order. Admin:
/admin/whatsapp/cod.
A Confirm tap on an order that has already shipped gets "already on its way"
with the tracking link. On Shipments, cod-cancel-requested blocks booking
(remove the tag if the customer changes their mind), and while COD confirmation
is on, COD orders without cod-confirmed show "Not confirmed yet" / "No answer
on WhatsApp" and are not preselected.
services/orders/whatsapp-shipping-updates.ts, switched on per shop
(Merchant Settings → WhatsApp → Shipping updates), run by the 15-minute
checkout-recovery cron, 8 am–9 pm IST only. Source: the courier tracking events
on the Shopify fulfillment (CONFIRMED, IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED).
Utility templates, fixed names: order_shipped ({{1}} name, {{2}} order,
{{3}} courier, {{4}} tracking link) on the first IN_TRANSIT;
order_out_for_delivery ({{1}}, {{2}}, {{3}} amount to pay, e.g. "₹1,195
(Cash on Delivery)" or "nothing, it's already paid"); order_delivery_attempt_failed
({{1}}, {{2}}) when OUT_FOR_DELIVERY is followed by IN_TRANSIT again (at most two
per order); order_delivered ({{1}}, {{2}}). One message per order per run, the
most advanced step wins; events older than 24 h (12 h for out for delivery) are
not announced. AuditEvent WHATSAPP_SHIPPING_UPDATE_SENT (step + key) per order.
services/whatsapp/back-in-stock.ts, switched on per shop (Merchant Settings →
WhatsApp → Back-in-stock alerts). The AI assistant sees each product's sold-out
sizes/colours; when a customer asks to be told (or agrees to the offer) it returns
restock_request, which is matched to the product/variant it was shown and saved
as a BackInStockRequest (unique per shop + phone + variant, or product|size|colour).
Unmatched requests become a SOFT handoff. The 15-minute cron checks waiting
requests with one nodes(ids) query per shop and sends the template
back_in_stock ({{1}} first name, {{2}} product and size/colour, {{3}} product
link opening the variant, with utm_source=whatsapp) once, 9 am–9 pm IST; opted-out
numbers are cancelled, requests expire after 60 days. Admin: WhatsApp → Back in stock
(who is waiting for what).
services/reviews/review-whatsapp.ts, hooked into the automatic review-request
pipeline (review-request-processor.ts, hourly review-requests cron). Needs
Reviews → automatic requests on, plus Merchant Settings → WhatsApp → Review
requests on WhatsApp. A due, still-eligible request is sent as the template
review_request ({{1}} first name, {{2}} product (size), {{3}} review link with
the request token) to the customer's verified phone (or the order's phone), once
per order; other lines of that order, customers without a phone and opted-out
numbers fall back to email. Each send is a WHATSAPP ProductReviewRequestDeliveryAttempt,
so reviews from it are sourced REVIEW_REQUEST_WHATSAPP. The WhatsApp path has its
own toggle and is not gated by REVIEW_REQUEST_DELIVERY_ENABLED (that env var
still gates email).
services/orders/whatsapp-second-order.ts, switched on per shop with a delay
(7–90 days after delivery, default 21) and the merchant's own offer line (required;
sent as written). 15-minute cron, 11 am–7 pm IST. Shopify orders delivered
delay…delay+7 days ago whose customer has exactly one order, not cancelled,
refunded or returned; skipped when the customer's chat is waiting on the team or
they opted out. Marketing template second_order_nudge ({{1}} first name, {{2}}
product bought, {{3}} store link with utm_medium=second_order, {{4}} offer line).
Once per customer: AuditEvent WHATSAPP_SECOND_ORDER_NUDGE_SENT on the Shopify customer.
services/whatsapp/shop-in-chat.ts, switched on per shop (Merchant Settings →
WhatsApp → Shop in chat); needs the Meta catalog connected to the WhatsApp
number with cart on. A cart sent with "Place order" arrives as an order
message (webhook routes it here, not to the AI). Catalog retailer ids are mapped
to Shopify variants (shopify_<CC>_<product>_<variant>, plain variant ids,
variant gids, else SKU lookup), checked for stock, and the customer gets one
free-form reply with the signed bag link (/apps/loopd2c/checkout/bag, token
from createCodRecoveryToken, checkout id wa:<message id>, needs
CHECKOUT_RECOVERY_SIGNING_SECRET). The bag opens with exactly those items and is
tagged with the cart attribute loopd2c_source=whatsapp_cart. Sold-out items are
named; unmatched carts become a SOFT handoff. AuditEvent WHATSAPP_CART_LINK_SENT.
Catalogs built from the website name items by product page (/products/<handle>#product, one item per product, no sizes): such cart items are matched by handle and the customer is asked "Which size?" with a WhatsApp list (in-stock sizes; reply ids wsz:<cart message id>:<variant id>, state in AuditEvent WHATSAPP_CART_SIZE_SELECTION) before the bag link is sent. Product cards try that naming first, then the Shopify channel's variant naming. The AI assistant names products to show (show_products, up to 3, plus cards_title): they are sent after its short reply as a product_list (one section per product, its in-stock variants as items, retailer ids shopify_IN_<product>_<variant> as the Shopify channel syncs them, catalog id from settings or GET /{waba}/product_catalogs, cached). If cards cannot be sent it falls back to a catalog_message; show_catalog opens the whole catalog. A run whose message was followed by another before the reply is ready is skipped (the newer message's run answers both).
The webhook's referral on a message (first message after an ad tap) is stored
on the conversation (adSourceId, adHeadline, adBody, adCtwaClid,
adReferredAt; latest ad wins). The AI assistant is told about an ad clicked in
the last 3 days. services/whatsapp/ad-attribution.ts, called from the
orders/create webhook (after()): an order whose phone matches a chat with an ad
click in the 7 days before is recorded as AuditEvent WHATSAPP_AD_ORDER (shown
under WhatsApp → Ads, per ad: chats, orders, revenue). With Merchant Settings →
WhatsApp → Report WhatsApp ad sales to Meta, it is also sent as a Purchase to the
WABA's Meta dataset (GET/POST /{waba}/dataset, cached in capiDatasetId;
POST /{dataset}/events, action_source business_messaging, messaging_channel
whatsapp, user_data whatsapp_business_account_id + ctwa_clid). The token needs
whatsapp_business_manage_events.
LoopD2C → WhatsApp Inbox → AI assistant (/admin/whatsapp/assistant).
Modes per shop (MerchantWhatsAppAccount.aiMode): OFF, DRAFT (suggested
reply in the chat, a person sends it) and AUTO (answers on its own). It runs
after the webhook responds (next/server after), waits 4s so a burst of
messages gets one answer, shows WhatsApp's typing indicator, then asks OpenAI
(OPENAI_API_KEY; model OPENAI_WHATSAPP_MODEL → OPENAI_MODEL →
gpt-4o-mini) with facts read live from Shopify: matching products (price,
sizes in stock, link), the customer's last 3 orders by phone (status,
tracking), shop policies when read_legal_policies is granted, and the
merchant's store notes (aiKnowledge). Rules: only quote those facts, no
invented prices/offers/urgency, never ask for OTP/card/UPI details.
Complaints, refunds, exchanges, photos/voice notes, low confidence (< 0.6) or
AI failure: one holding message (AUTO), the chat is flagged "Needs your team"
and the team is emailed. Limits: 6 AI replies per chat per hour, 300 per shop
per day. A team reply clears the flag and pauses the assistant in that chat for
12 hours. In AUTO the "new chat" email is replaced by the handoff email.
Logic and prompt: services/whatsapp/assistant/ (policy.ts is pure and
unit-tested).
Request status (customer-requests.ts): the assistant also gets this
customer's exchange, cancellation, issue and refund requests from LoopD2C's own
records (OrderActionRequest of the last 120 days, up to 5, matched on the
WhatsApp number's last 10 digits against the request's phone or the customer
profile's): the step in plain words, items (and sizes for exchanges), reverse
pickup and replacement tracking, and the linked refund's amount and status. A
question about one of them ("where is my exchange?", "is my refund done?") is
answered from those facts with intent request_status and no handoff; a new
request, a change to one, or a complaint still goes to the team
(return_exchange stays a hard handoff). A failed lookup only hides requests.
Templates exchange_approved, exchange_payment_received,
exchange_pickup_scheduled, exchange_pickup_completed, exchange_item_received,
exchange_replacement_processing, exchange_replacement_shipped,
exchange_completed, exchange_rejected (override names with
WHATSAPP_TEMPLATE_EXCHANGE_<STATUS>), sent from the shop's own number when
exchange updates are switched on.
Set up update templates (Merchant Settings → WhatsApp → "Save & set up
update templates", services/whatsapp/exchange-templates.ts): submits the
exchange templates and the request templates below as Utility in the account's template language from the shop's
WhatsApp Business Account (POST /{waba}/message_templates, with examples),
skipping any already on the account in that language, and reports what was
submitted, already approved or pending, and Meta's reason for any that failed.
The bodies' {{1}} name, {{2}} order number and {{3}} AWB (pickup scheduled,
replacement shipped) match what each step sends; names are shared with the
sender (EXCHANGE_TEMPLATE_NAMES, REQUEST_TEMPLATE_NAMES). The message reports each group separately; tick
Exchange updates and Cancellation, issue and refund updates each once its
templates are approved.
services/notifications/request-updates.ts, from the shop's own number with its own
switch (requestUpdatesEnabled, "Cancellation, issue and refund updates"), each step sent once (AuditEvent marker request.update_notified.<KEY>):
| Step | Template | Variables |
|---|---|---|
| Cancellation approved / rejected | cancellation_approved, cancellation_rejected | name, order |
| Order cancelled (incl. auto-cancel) | order_cancelled | name, order |
| Issue received / approved / rejected / resolved | issue_received, issue_approved, issue_rejected, issue_resolved | name, order |
| Refund paid (amount > ₹0.01) | refund_paid | name, amount, order |
Override names with WHATSAPP_TEMPLATE_<KEY> (e.g. WHATSAPP_TEMPLATE_REFUND_PAID).
Sends never block the status change; with updates off nothing is sent or marked.
Never expose WhatsApp secrets as NEXT_PUBLIC_*.
Admin → Free parcels (/admin/collabs, services/collab/) sends free
products at the store's cost (influencer, brand collab, PR, samples to vendors and
manufacturers, photoshoot / internal, gift, other) instead of booking them in the
Delhivery panel. Content tracking (posted, link) applies to influencer, collab and
PR parcels only; an optional company goes on the Shopify address and the CSV:
LoopD2C Collab, Free parcel: <purpose>
and Campaign: <name>, plus a note and loopd2c_collab_* attributes. Shopify
takes the stock off, and the order shows in Shopify's own reports as a
discount. Stock is checked first; a variant that can't be oversold stops the parcel.bookOrderShipments),
with the retail value declared to the courier (declaredValuePaise). The order is
marked shipped in Shopify with the AWB (emails the recipient only if ticked).
If booking fails, the order stays and Book on Delhivery retries it.CollabShipment): retail value, product cost (the variant's Shopify
cost price at the time), shipping cost (entered from the Delhivery bill),
campaign, handle, and whether the content was posted, with its link.
Get cost from Shopify fills in items that had no cost price when sent (cost
added in Shopify later); costs already recorded are never changed.delhivery-tracking cron refreshes open parcels (15 per run);
Refresh does one now.Collab orders skip GST auto-invoicing (a ₹0 tax invoice would use up a number). How free samples are treated for GST and input tax credit is for the store's accountant to decide; the CSV and the tagged Shopify orders are there for that.