+
+
+ {offerData?.offer?.name === "linkedin-launch-week"
+ ? "Free CodeGuard Audit"
+ : "Free CodeGuard Audit"}
+
+
+ {offerData?.offer?.description || "Drop your repo, and I'll run an Audit Mode pass on it personally."}
+
+
+
+ {(offerData?.pendingCount ?? 0) > 50 && (
+
+
+
+ High volume: Due to the number of requests, new submissions may not be reviewed before the offer ends.
+
+
+ )}
+
+
+
+ Submit your repository
+
+ No credit card required. I review every submission personally to ensure we can provide value.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/FREE_OFFER_MECHANISM.md b/docs/FREE_OFFER_MECHANISM.md
new file mode 100644
index 0000000..c780e35
--- /dev/null
+++ b/docs/FREE_OFFER_MECHANISM.md
@@ -0,0 +1,45 @@
+# CodeGuard Free Offer Mechanism
+
+This document describes the architectural implementation of the public "Free Audit" offer, a temporary promotion that provides a full CodeGuard Audit Mode pass (typically gated behind a $1,500+ paywall) for free.
+
+## Why Admin-Mediated?
+
+The public offer was designed intentionally as an **admin-mediated** process rather than a fully self-serve free tier. This fulfills three goals:
+1. **Quality Control & Engagement:** A core promise of the offer is that the founder personally reviews the request. It's a high-touch sales and onboarding motion disguised as a free trial.
+2. **Abuse Prevention:** A self-serve $1,500 coupon would be immediately scraped by bots, costing thousands of dollars in API usage before it could be shut down.
+3. **No Auth Friction:** Prospective users do not need to create an account, install a GitHub app, or add a credit card to request the audit. The admin handles the orchestration.
+
+## How the Flow Works
+
+1. **Intake (`client/src/pages/free-audit-request.tsx`)**
+ - The public visits the intake page. If the current `promo_offers` row for this campaign has an `ends_at` date in the future, the form is shown.
+ - Submissions are recorded in the `free_audit_requests` table with a `pending` status.
+
+2. **Admin Queue (`client/src/pages/admin/free-audit-queue.tsx`)**
+ - An admin logs in and navigates to the "Free Audit Queue".
+ - The admin reviews the repository URL and the submitter's motivation text.
+ - The admin clicks "Approve" (or "Reject").
+
+3. **Orchestration (`server/routes/admin.ts`)**
+ - Upon "Approve", the backend performs several actions:
+ - Updates the `free_audit_requests` status to `approved`.
+ - Creates a standard `audits` record attributed to the Admin's `userId`.
+ - Creates an `audit_orders` record with `status: "comped"`. This ensures the finance and billing layer ignores this audit when reconciling payments, bypassing the paywall.
+ - Kicks off the standard `runAuditAsync` function just as a paid order would.
+ - Logs the action in `adminActionLog`.
+
+## Cost Ceilings and Safety
+
+Because CodeGuard's backend utilizes LLMs that incur real-world costs per token, a sudden viral spike could theoretically bankrupt the system if an admin rapidly approved requests.
+
+To mitigate this, two safety limits are enforced:
+1. **Public Volume Warning:** If there are more than 50 `pending` requests in the queue, the public intake form automatically displays a "High volume" warning, setting expectations that new requests might not be fulfilled.
+2. **Hard Cost Ceiling:** The admin dashboard calculates the total API spend for the current day across the entire platform (by summing `cost_usd` in `api_usage_log`). If this sum exceeds **$100**, the "Approve" button is disabled and the backend strictly rejects further approvals. This guarantees the platform cannot lose more than $100 per day on free audits.
+
+## Managing the Offer Window
+
+The offer window is controlled purely via the database `promo_offers` table.
+- **To End Early:** Set the `ends_at` timestamp to a past date. The frontend will immediately fall back to the standard pricing page link.
+- **To Restart/Extend:** Update the `ends_at` timestamp to a future date.
+
+*Note: No code changes are required to turn the offer on or off.*
diff --git a/scripts/seed-promo.ts b/scripts/seed-promo.ts
new file mode 100644
index 0000000..1c26028
--- /dev/null
+++ b/scripts/seed-promo.ts
@@ -0,0 +1,26 @@
+import { db } from "../server/db.js";
+import { promoOffers } from "../shared/schema.js";
+
+async function seed() {
+ console.log("Seeding initial promo offer...");
+
+ const now = new Date();
+ const endsAt = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 days from now
+
+ await db.insert(promoOffers).values({
+ name: "linkedin-launch-week",
+ description: "Drop your repo, and I'll run an Audit Mode pass on it personally, free of charge this week.",
+ startsAt: now,
+ endsAt: endsAt,
+ status: "active",
+ grantsUsed: 0
+ });
+
+ console.log("Successfully seeded linkedin-launch-week promo offer.");
+ process.exit(0);
+}
+
+seed().catch((err) => {
+ console.error("Failed to seed promo offer:", err);
+ process.exit(1);
+});
diff --git a/server/routes.ts b/server/routes.ts
index 23fc72d..c95953a 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -146,6 +146,58 @@ export async function registerRoutes(
app.use("/api/admin", adminRouter);
app.use("/api/ai", aiStatusRouter);
+ // ============= PUBLIC PROMO OFFERS =============
+ app.get("/api/public/promo-offer", async (req, res) => {
+ try {
+ const offer = await storage.getActivePromoOffer();
+ if (!offer) {
+ return res.json({ active: false });
+ }
+ const pendingRequests = await storage.getPendingFreeAuditRequests();
+ res.json({
+ active: true,
+ pendingCount: pendingRequests.length,
+ offer: {
+ id: offer.id,
+ name: offer.name,
+ description: offer.description,
+ startsAt: offer.startsAt,
+ endsAt: offer.endsAt,
+ }
+ });
+ } catch (error: any) {
+ console.error("[API] Error fetching promo offer:", error);
+ res.status(500).json({ error: "Failed to fetch promo offer" });
+ }
+ });
+
+ app.post("/api/public/free-audit-request", async (req, res) => {
+ try {
+ const offer = await storage.getActivePromoOffer();
+ if (!offer) {
+ return res.status(400).json({ error: "No active free audit offer at this time." });
+ }
+
+ const { repoUrl, contactName, contactEmail, motivationText } = req.body;
+ if (!repoUrl || !contactName || !contactEmail || !motivationText) {
+ return res.status(400).json({ error: "Missing required fields." });
+ }
+
+ const newRequest = await storage.createFreeAuditRequest({
+ promoOfferId: offer.id,
+ repoUrl,
+ contactName,
+ contactEmail,
+ motivationText
+ });
+
+ res.status(201).json(newRequest);
+ } catch (error: any) {
+ console.error("[API] Error submitting free audit request:", error);
+ res.status(500).json({ error: "Failed to submit request" });
+ }
+ });
+
// ============= REPOSITORIES =============
// Update user preferences
diff --git a/server/routes/admin.ts b/server/routes/admin.ts
index 4039999..7be3a2e 100644
--- a/server/routes/admin.ts
+++ b/server/routes/admin.ts
@@ -2,6 +2,8 @@ import { Router } from "express";
import { db } from "../db.js";
import { users, audits, requestLogs, apiUsageLog, adminActionLog, auditOrders } from "../../shared/schema.js";
import { desc, sql, eq } from "drizzle-orm";
+import { storage } from "../storage.js";
+import { runAuditAsync } from "./audits.js";
const router = Router();
@@ -27,6 +29,95 @@ async function logAdminAction(adminUserId: string, actionType: string, targetId:
});
}
+router.get("/free-audits", async (req, res) => {
+ try {
+ const requests = await storage.getPendingFreeAuditRequests();
+
+ // Calculate today's API cost
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const [{ totalCost }] = await db
+ .select({ totalCost: sql