git-paid 0.0.0-stage → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { scanTargetUrl } from "../lib/engine.js";
6
+ import { authenticateUser } from "../lib/auth.js";
7
+ import { applyLocalSeoFixes } from "../lib/fixer.js";
8
+ import { sendTestVerificationEmail } from "../lib/inbox.js";
9
+ import {
10
+ startPostbuildMcpServer,
11
+ syncPostbuildToVault,
12
+ installAgentSkillFiles,
13
+ autoVerifyInboxLinks,
14
+ runStatefulMcpSimulation
15
+ } from "../lib/mcp.js";
16
+ import { runHeadlessDirectorySubmit } from "../lib/submitter.js";
17
+
18
+ const args = process.argv.slice(2);
19
+ const subcmd = (args[0] || "").toLowerCase();
20
+
21
+ // Stdio MCP Server mode: DO NOT print ASCII banners to stdout (stdout is JSON-RPC 2.0)
22
+ if (subcmd === "mcp" || args.includes("--mcp")) {
23
+ startPostbuildMcpServer();
24
+ } else {
25
+ await runCli();
26
+ }
27
+
28
+ async function runCli() {
29
+ console.log("\n========================================================");
30
+ console.log(" postbuild (postbuild.dev) — `git paid` CLI & MCP");
31
+ console.log(" git push ships your code. git paid sells it.");
32
+ console.log("========================================================\n");
33
+
34
+ if (subcmd === "--help" || subcmd === "-h" || subcmd === "help") {
35
+ console.log(" Usage:");
36
+ console.log(" npx git-paid <url> Run 8-point SEO/paywall audit & generate Launch Kit");
37
+ console.log(" git paid submit [url] Headless Chromium/Edge directory form submitter + OTP verifier");
38
+ console.log(" git paid fix [url] Auto-write robots.txt, sitemap.xml, llms.txt & pSEO into public/");
39
+ console.log(" git paid inbox [bot-email] Check & auto-verify <slug>-bot@postbuild.dev links & OTPs");
40
+ console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.0)");
41
+ console.log(" git paid init-agent Scaffold .agent/skills/postbuild/SKILL.md & MCP config");
42
+ console.log(" git paid vault [path] Sync live telemetry & Bento cockpit to Obsidian VaultOS");
43
+ console.log(" git paid login <email|key> Authenticate with your Stripe Launch Pass or pb_live_ key\n");
44
+ process.exit(0);
45
+ }
46
+
47
+ if (subcmd === "submit") {
48
+ const targetUrl = args.find((a, i) => i > 0 && !a.startsWith("--")) || "https://postbuild.dev";
49
+ console.log(` [*] Running Headless Directory Form Submitter for: ${targetUrl} ...`);
50
+ const submitRes = await runHeadlessDirectorySubmit({
51
+ targetUrl,
52
+ limit: 6,
53
+ useHeadless: !args.includes("--no-headless"),
54
+ cwd: process.cwd()
55
+ });
56
+ console.log(` [+] Headless Browser Engine: ${submitRes.headlessBrowser}`);
57
+ console.log(` [+] Provisioned Bot Inbox: ${submitRes.botEmail}`);
58
+ console.log(` [+] Total Directories Pinged:${submitRes.totalDirectoriesDispatched}\n`);
59
+ console.log(" --- Headless Form Inspection & Submission Receipts ---");
60
+ for (const r of submitRes.formReceipts) {
61
+ console.log(
62
+ ` [SUBMIT] ${r.name.padEnd(24)} (DR ${r.dr}) via ${r.engineUsed} -> Fields: [${r.mappedFields.slice(0, 5).join(", ")}]`
63
+ );
64
+ }
65
+ console.log("\n --- @postbuild.dev Inbox Auto-Verification ---");
66
+ console.log(
67
+ ` [INBOX] ${submitRes.inboxVerification.inboxAddress} -> ${submitRes.inboxVerification.autoVerifiedCount} confirmation links auto-verified`
68
+ );
69
+ if (submitRes.inboxVerification.extractedOtps.length > 0) {
70
+ for (const o of submitRes.inboxVerification.extractedOtps) {
71
+ console.log(` [OTP] Extracted code ${o.otp} from ${o.from}`);
72
+ }
73
+ }
74
+ if (submitRes.receiptFile) {
75
+ console.log(` [WRITE] Saved launch receipts to ${submitRes.receiptFile}\n`);
76
+ }
77
+ process.exit(0);
78
+ }
79
+
80
+ if (subcmd === "init-agent" || subcmd === "agent") {
81
+ const res = installAgentSkillFiles({ cwd: process.cwd() });
82
+ console.log(` [+] Scaffolded Agent Skill: ${res.skillMdPath}`);
83
+ console.log(` [+] Powered by: postbuild-mcp@2.1.0 (Stateful Delta Compaction)\n`);
84
+ console.log(" Add this to your Claude Code / Cursor / Antigravity MCP config:\n");
85
+ console.log(JSON.stringify(res.mcpConfigSnippet, null, 2));
86
+ console.log("");
87
+ process.exit(0);
88
+ }
89
+
90
+ if (subcmd === "vault" || subcmd === "vaultos") {
91
+ const vaultArg =
92
+ args.find((a, i) => i > 0 && !a.startsWith("--") && !/^https?:\/\//i.test(a)) ||
93
+ "C:\\Users\\Bennett\\Bennett Labs";
94
+ const urlArgs = args.filter((a, i) => i > 0 && /^https?:\/\//i.test(a));
95
+ const targetUrls = urlArgs.length ? urlArgs : ["https://postbuild.dev", "https://payarity.com"];
96
+
97
+ console.log(` [*] Syncing Postbuild MCP Telemetry to Obsidian VaultOS...`);
98
+ console.log(` [*] Target Vault: ${vaultArg}`);
99
+ console.log(` [*] Domains: ${targetUrls.join(", ")}\n`);
100
+
101
+ const syncRes = await syncPostbuildToVault({
102
+ vaultPath: vaultArg,
103
+ urls: targetUrls
104
+ });
105
+
106
+ for (const p of syncRes.telemetry.projects) {
107
+ console.log(
108
+ ` [OK] ${p.domain.padEnd(20)} Score: ${p.readinessScore}/100 | pSEO: ${p.pseoCount} | Dirs: ${p.directoryCount} | MCP: ${p.mcpReductionPct}`
109
+ );
110
+ }
111
+ console.log("\n --- VaultOS Files Updated ---");
112
+ for (const f of syncRes.writtenVaultFiles) {
113
+ console.log(` [WRITE] ${f}`);
114
+ }
115
+ console.log("");
116
+ process.exit(0);
117
+ }
118
+
119
+ if (subcmd === "benchmark") {
120
+ const targetUrl = args[1] || "https://postbuild.dev";
121
+ const sim = await runStatefulMcpSimulation({ url: targetUrl });
122
+ console.log(JSON.stringify(sim, null, 2));
123
+ process.exit(0);
124
+ }
125
+
126
+ if (subcmd === "login") {
127
+ const input = args[1] || "";
128
+ if (!input) {
129
+ console.log(" Usage: git paid login <email-or-pb_live_key>\n");
130
+ process.exit(1);
131
+ }
132
+ const auth = await authenticateUser(
133
+ input.startsWith("pb_live_") ? { token: input } : { email: input }
134
+ );
135
+ if (!auth.ok) {
136
+ console.log(` [!] Login failed: ${auth.error}\n`);
137
+ process.exit(1);
138
+ }
139
+ const globalDir = path.join(os.homedir(), ".postbuild");
140
+ fs.mkdirSync(globalDir, { recursive: true });
141
+ fs.writeFileSync(
142
+ path.join(globalDir, "credentials.json"),
143
+ JSON.stringify(
144
+ { email: auth.email, plan: auth.plan, token: auth.token, updatedAt: new Date().toISOString() },
145
+ null,
146
+ 2
147
+ ),
148
+ "utf8"
149
+ );
150
+ console.log(` [+] Authenticated as: ${auth.email}`);
151
+ console.log(` [+] Active Plan: ${auth.plan}`);
152
+ console.log(` [+] License Key: ${auth.token.slice(0, 28)}...`);
153
+ console.log(` [ok] Saved credentials to ~/.postbuild/credentials.json\n`);
154
+ process.exit(0);
155
+ }
156
+
157
+ if (subcmd === "inbox") {
158
+ const inboxAddr = args.find((a, i) => i > 0 && !a.startsWith("--")) || "postbuild-bot@postbuild.dev";
159
+ if (args.includes("--test")) {
160
+ await sendTestVerificationEmail(inboxAddr, "postbuild.dev");
161
+ }
162
+ const res = await autoVerifyInboxLinks(inboxAddr);
163
+ console.log(
164
+ ` [*] Verification Inbox: ${res.inboxAddress} (${res.totalMessages} messages, ${res.verifiedCount} auto-verified)\n`
165
+ );
166
+ if (res.messages.length === 0) {
167
+ console.log(" No verification emails yet. Run `git paid inbox --test` to test OTP & link extraction.\n");
168
+ } else {
169
+ for (const m of res.messages.slice(0, 8)) {
170
+ console.log(` From: ${m.from}`);
171
+ console.log(` Subject: ${m.subject}`);
172
+ if (m.otpCode) console.log(` OTP: ${m.otpCode}`);
173
+ if (m.primaryLink) console.log(` Verify: ${m.primaryLink} [${m.autoVerified ? "AUTO-VERIFIED" : "PENDING"}]`);
174
+ console.log(" --------------------------------------------------------");
175
+ }
176
+ }
177
+ process.exit(0);
178
+ }
179
+
180
+ const isFixMode = subcmd === "fix" || subcmd === "init" || args.includes("--fix");
181
+ const targetArg =
182
+ args.find((a) => !a.startsWith("--") && !["scan", "dispatch", "fix", "init"].includes(a.toLowerCase())) ||
183
+ "https://postbuild.dev";
184
+
185
+ console.log(` [*] Scanning Target: ${targetArg} ...`);
186
+
187
+ const report = await scanTargetUrl(targetArg);
188
+
189
+ console.log(` [+] Provisioned Bot Inbox: ${report.botEmail}`);
190
+ console.log(` [+] Readiness Score: ${report.readinessScore} / 100`);
191
+ console.log(` [+] Auto-SEO Pages Ready: ${report.pseoPages.length} high-intent routes`);
192
+ console.log(` [+] Directory Launch Queue: ${report.directoryCount} AI, Dev & Startup Directories\n`);
193
+
194
+ console.log(" --- Audit Signals ---");
195
+ for (const c of report.checks) {
196
+ const icon = c.passed ? "[PASS]" : "[WARN]";
197
+ console.log(` ${icon} ${c.label.padEnd(40)} -> ${c.detail}`);
198
+ }
199
+
200
+ const fixResult = applyLocalSeoFixes(report, {
201
+ cwd: process.cwd(),
202
+ overwritePublic: isFixMode || args.includes("--force")
203
+ });
204
+
205
+ console.log(`\n --- Local Repository Auto-Fix (${fixResult.framework}) ---`);
206
+ for (const f of fixResult.writtenFiles) {
207
+ console.log(` [WRITE] ${f.path.padEnd(36)} (${f.role})`);
208
+ }
209
+
210
+ console.log("\n -> Live Workspace & MCP Studio: https://postbuild.dev\n");
211
+ }
@@ -0,0 +1,54 @@
1
+ # `postbuild` (`git paid`) — Official Brand Kit
2
+
3
+ > **`git push` ships your code. `git paid` sells it.**
4
+
5
+ ## 1. Brand Assets
6
+
7
+ All assets are available in both `/brand/` (repository root) and `/public/brand/` (served live at `https://postbuild.dev/brand/...`):
8
+
9
+ | Asset | File | Dimensions | Format | Usage |
10
+ | :--- | :--- | :--- | :--- | :--- |
11
+ | **Brand Icon (PNG)** | [`postbuild-icon.png`](./postbuild-icon.png) | `512 × 512 px` | PNG | Stripe Icon, App Store / Directory listings, OAuth apps |
12
+ | **Horizontal Logo (PNG)** | [`postbuild-logo.png`](./postbuild-logo.png) | `1000 × 260 px` | PNG | Stripe Checkout Header Logo, Invoices, Receipts, Press Kits |
13
+ | **Brand Icon (SVG)** | [`postbuild-icon.svg`](./postbuild-icon.svg) | Vector (`64 × 64`) | SVG | Web UI, Favicons, Scalable vector contexts |
14
+ | **Horizontal Logo (SVG)** | [`postbuild-logo.svg`](./postbuild-logo.svg) | Vector (`1000 × 260`) | SVG | High-DPI headers, documentation, print |
15
+
16
+ ---
17
+
18
+ ## 2. Visual Symbolism (`2 × 2` Grid)
19
+
20
+ The `postbuild` mark represents the two-stage developer lifecycle inside a clean `2 × 2` architectural grid:
21
+ 1. **Top Row (`git push` — Ship the build)**: Deep Slate build module (`#0F172A`) on the left pushing outward into the Sky Blue deployment arrow (`#0284C7`) on the right.
22
+ 2. **Bottom Row (`git paid` — Monetize & distribute)**: Muted Slate post-build artifact (`#64748B`) on the left converting into the Emerald Green revenue badge (`$`, `#10B981`) on the right.
23
+
24
+ ---
25
+
26
+ ## 3. Color Palette
27
+
28
+ | Role | Color Name | Hex Code | RGB | Usage |
29
+ | :--- | :--- | :--- | :--- | :--- |
30
+ | **Primary / Brand** | Deep Slate | `#0F172A` | `rgb(15, 23, 42)` | Wordmark, primary CTAs, Stripe **Brand Color** |
31
+ | **Accent (`git paid`)** | Emerald Green | `#10B981` | `rgb(16, 185, 129)` | `$` revenue badge, `git paid` pill, Stripe **Accent Color** |
32
+ | **Secondary (`git push`)** | Sky Blue | `#0284C7` | `rgb(2, 132, 199)` | Deployment arrow (`→`), technical indicators |
33
+ | **Structural Muted** | Slate Gray | `#64748B` | `rgb(100, 116, 139)` | Secondary build block, supporting labels |
34
+ | **Surface / Canvas** | Crisp Alabaster | `#F8FAFC` | `rgb(248, 250, 252)` | App background, card surfaces |
35
+ | **Pure White** | White | `#FFFFFF` | `rgb(255, 255, 255)` | Icon & Logo container surface |
36
+
37
+ ---
38
+
39
+ ## 4. Typography
40
+
41
+ - **Wordmark (`postbuild`)**: Always lowercase (`postbuild`), set in **Plus Jakarta Sans / Inter ExtraBold (`800`)** with `-0.045em` letter-spacing.
42
+ - **CLI Companion (`git paid`)**: Always lowercase (`git paid`), set in **JetBrains Mono Bold (`700`)** inside an emerald tint pill (`#ECFDF5` fill, `#A7F3D0` border, `#047857` text).
43
+
44
+ ---
45
+
46
+ ## 5. Official Stripe & Directory Copy
47
+
48
+ - **Business Name**: `postbuild`
49
+ - **Website**: `https://postbuild.dev`
50
+ - **Statement Descriptor**: `POSTBUILD.DEV` (Shortened: `POSTBUILD`)
51
+ - **One-Line Tagline**:
52
+ > Automated post-build technical SEO, programmatic metadata, and directory launch kits for software developers.
53
+ - **Full Business Description**:
54
+ > `postbuild` (`https://postbuild.dev`) is a developer SaaS platform and CLI tool (`git paid` / `npx postbuild`) that automates post-build technical SEO audits, structured metadata (JSON-LD, OpenGraph, robots.txt, sitemaps), programmatic SEO route packs, and curated SaaS directory launch kits for software developers and product teams.
Binary file
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 64 64" fill="none">
2
+ <rect width="64" height="64" rx="14" fill="#FFFFFF"/>
3
+
4
+ <!-- Top-left build block (git push source) -->
5
+ <rect x="7" y="7" width="20" height="20" rx="5.5" stroke="#0F172A" stroke-width="5" fill="#F8FAFC"/>
6
+ <!-- Bottom-left build block (git paid source) -->
7
+ <rect x="7" y="37" width="20" height="20" rx="5.5" stroke="#64748B" stroke-width="5" fill="#F8FAFC"/>
8
+ <!-- Top-right arrow (git push) -->
9
+ <path d="M36 17H56M56 17L47.5 8.5M56 17L47.5 25.5" stroke="#0284C7" stroke-width="5.2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
10
+ <!-- Bottom-right emerald badge + crisp typographic $ (git paid) -->
11
+ <rect x="35" y="35" width="22" height="22" rx="6.5" fill="#10B981"/>
12
+ <text x="46" y="52.5" text-anchor="middle" fill="#FFFFFF" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif" font-size="18.5" font-weight="800">$</text>
13
+
14
+ </svg>
Binary file
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1000" height="260" viewBox="0 0 1000 260" fill="none">
2
+ <rect width="1000" height="260" fill="#FFFFFF"/>
3
+ <g transform="translate(36, 26) scale(3.25)">
4
+
5
+ <!-- Top-left build block (git push source) -->
6
+ <rect x="7" y="7" width="20" height="20" rx="5.5" stroke="#0F172A" stroke-width="5" fill="#F8FAFC"/>
7
+ <!-- Bottom-left build block (git paid source) -->
8
+ <rect x="7" y="37" width="20" height="20" rx="5.5" stroke="#64748B" stroke-width="5" fill="#F8FAFC"/>
9
+ <!-- Top-right arrow (git push) -->
10
+ <path d="M36 17H56M56 17L47.5 8.5M56 17L47.5 25.5" stroke="#0284C7" stroke-width="5.2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
11
+ <!-- Bottom-right emerald badge + crisp typographic $ (git paid) -->
12
+ <rect x="35" y="35" width="22" height="22" rx="6.5" fill="#10B981"/>
13
+ <text x="46" y="52.5" text-anchor="middle" fill="#FFFFFF" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif" font-size="18.5" font-weight="800">$</text>
14
+
15
+ </g>
16
+ <text x="276" y="176" fill="#0F172A" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif" font-size="144" font-weight="800" letter-spacing="-5.5">postbuild</text>
17
+ </svg>
package/lib/auth.js ADDED
@@ -0,0 +1,208 @@
1
+ // Zero-DB Auth, Email OTP via Resend & Cryptographic License Verification for postbuild (postbuild.dev)
2
+
3
+ import crypto from "node:crypto";
4
+ import { getStripeClient } from "./stripe.js";
5
+
6
+ const SECRET = process.env.POSTBUILD_AUTH_SECRET || process.env.STRIPE_WEBHOOK_SECRET || "postbuild-dev-secret-key-2026";
7
+
8
+ export function signLicenseToken({ email, plan = "launch_pass", domain = "*" }) {
9
+ const payload = {
10
+ email: (email || "").toLowerCase().trim(),
11
+ plan,
12
+ domain,
13
+ iat: Math.floor(Date.now() / 1000)
14
+ };
15
+ const base64Payload = Buffer.from(JSON.stringify(payload)).toString("base64url");
16
+ const sig = crypto.createHmac("sha256", SECRET).update(base64Payload).digest("base64url").slice(0, 24);
17
+ return `pb_live_${base64Payload}.${sig}`;
18
+ }
19
+
20
+ export function verifyLicenseToken(token) {
21
+ if (!token || typeof token !== "string" || !token.startsWith("pb_live_")) {
22
+ return { valid: false };
23
+ }
24
+ const raw = token.slice("pb_live_".length);
25
+ const [base64Payload, sig] = raw.split(".");
26
+ if (!base64Payload || !sig) return { valid: false };
27
+
28
+ const expectedSig = crypto.createHmac("sha256", SECRET).update(base64Payload).digest("base64url").slice(0, 24);
29
+ if (sig !== expectedSig) return { valid: false };
30
+
31
+ try {
32
+ const payload = JSON.parse(Buffer.from(base64Payload, "base64url").toString("utf8"));
33
+ return { valid: true, ...payload };
34
+ } catch {
35
+ return { valid: false };
36
+ }
37
+ }
38
+
39
+ function createSignedOtpChallenge(email, otpCode, plan = "launch_pass") {
40
+ const exp = Math.floor(Date.now() / 1000) + 600; // 10 minutes
41
+ const data = `${email.toLowerCase().trim()}:${otpCode}:${plan}:${exp}`;
42
+ const sig = crypto.createHmac("sha256", SECRET).update(data).digest("base64url").slice(0, 24);
43
+ const encoded = Buffer.from(JSON.stringify({ email: email.toLowerCase().trim(), plan, exp })).toString("base64url");
44
+ return `${encoded}.${sig}`;
45
+ }
46
+
47
+ function verifySignedOtpChallenge(challengeToken, otpCode) {
48
+ if (!challengeToken || !otpCode) return { valid: false };
49
+ const [encoded, sig] = String(challengeToken).split(".");
50
+ if (!encoded || !sig) return { valid: false };
51
+ try {
52
+ const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
53
+ if (!parsed.exp || Math.floor(Date.now() / 1000) > parsed.exp) {
54
+ return { valid: false, error: "Verification code expired. Please request a new code." };
55
+ }
56
+ const data = `${parsed.email}:${String(otpCode).trim()}:${parsed.plan}:${parsed.exp}`;
57
+ const expectedSig = crypto.createHmac("sha256", SECRET).update(data).digest("base64url").slice(0, 24);
58
+ if (sig !== expectedSig) {
59
+ return { valid: false, error: "Invalid 6-digit verification code." };
60
+ }
61
+ return { valid: true, email: parsed.email, plan: parsed.plan };
62
+ } catch {
63
+ return { valid: false, error: "Invalid verification challenge." };
64
+ }
65
+ }
66
+
67
+ async function sendLoginOtpViaResend(email, otpCode, licenseToken) {
68
+ const resendKey = (process.env.RESEND_API_KEY || "").trim();
69
+ if (!resendKey) {
70
+ return { sent: false, reason: "missing_resend_key" };
71
+ }
72
+ try {
73
+ const resp = await fetch("https://api.resend.com/emails", {
74
+ method: "POST",
75
+ headers: {
76
+ Authorization: `Bearer ${resendKey}`,
77
+ "Content-Type": "application/json"
78
+ },
79
+ body: JSON.stringify({
80
+ from: "postbuild <login@postbuild.dev>",
81
+ to: [email],
82
+ subject: `${otpCode} is your postbuild verification code`,
83
+ html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif; max-width: 520px; margin: 0 auto; padding: 28px; border: 1px solid #e2e8f0; border-radius: 14px; color: #0f172a;">
84
+ <h2 style="margin: 0 0 8px; font-size: 20px;">postbuild · Account Verification</h2>
85
+ <p style="color: #475569; font-size: 14px; line-height: 1.6;">Enter this 6-digit verification code on <strong>postbuild.dev</strong> to view your Stripe receipts and manage your subscription:</p>
86
+ <div style="background: #ecfdf5; border: 1px solid #6ee7b7; color: #047857; font-family: monospace; font-size: 28px; font-weight: 800; letter-spacing: 6px; text-align: center; padding: 14px; border-radius: 10px; margin: 18px 0;">${otpCode}</div>
87
+ <p style="color: #475569; font-size: 13px; line-height: 1.6;">Your active CLI License Key (for <code>git paid login</code>):</p>
88
+ <pre style="background: #f8fafc; border: 1px solid #e2e8f0; padding: 10px; border-radius: 8px; font-size: 11px; overflow-x: auto;">${licenseToken}</pre>
89
+ </div>`
90
+ })
91
+ });
92
+ return { sent: resp.ok };
93
+ } catch {
94
+ return { sent: false };
95
+ }
96
+ }
97
+
98
+ export async function authenticateUser({ email, token, sessionId, otpCode, challengeToken }) {
99
+ // 1. If user passes an existing pb_live_ token, verify cryptographic HMAC signature
100
+ if (token && token.startsWith("pb_live_")) {
101
+ const verified = verifyLicenseToken(token);
102
+ if (verified.valid) {
103
+ return {
104
+ ok: true,
105
+ authenticated: true,
106
+ email: verified.email,
107
+ plan: verified.plan,
108
+ domain: verified.domain,
109
+ token
110
+ };
111
+ }
112
+ return { ok: false, authenticated: false, error: "Invalid pb_live_ license key signature." };
113
+ }
114
+
115
+ // 2. If user is completing Step 2 of Email OTP verification
116
+ if (otpCode && challengeToken) {
117
+ const check = verifySignedOtpChallenge(challengeToken, otpCode);
118
+ if (!check.valid) {
119
+ return { ok: false, authenticated: false, error: check.error || "Invalid verification code." };
120
+ }
121
+ const issued = signLicenseToken({ email: check.email, plan: check.plan, domain: "*" });
122
+ return {
123
+ ok: true,
124
+ authenticated: true,
125
+ email: check.email,
126
+ plan: check.plan,
127
+ domain: "*",
128
+ token: issued
129
+ };
130
+ }
131
+
132
+ const stripe = getStripeClient();
133
+
134
+ // 3. If user returned from Stripe Checkout with unguessable session_id (cs_...), verify session via StripeClient
135
+ if (sessionId && stripe) {
136
+ const session = await stripe.checkout.sessions.retrieve(sessionId);
137
+ if (session && session.payment_status !== "unpaid") {
138
+ const custEmail = (session.customer_details?.email || email || "founder@postbuild.dev").toLowerCase().trim();
139
+ const plan = session.metadata?.plan_id || "launch_pass";
140
+ const domain = session.client_reference_id || session.metadata?.target_domain || "*";
141
+ const newToken = signLicenseToken({ email: custEmail, plan, domain });
142
+ return {
143
+ ok: true,
144
+ authenticated: true,
145
+ email: custEmail,
146
+ plan,
147
+ domain,
148
+ token: newToken
149
+ };
150
+ }
151
+ }
152
+
153
+ // 4. If user enters an email address, verify they have a record in Stripe and require a 6-digit email OTP code (or their pb_live_ key)
154
+ const cleanEmail = (email || "").toLowerCase().trim();
155
+ if (!cleanEmail || !cleanEmail.includes("@")) {
156
+ return { ok: false, authenticated: false, error: "Enter your email address or pb_live_ license key." };
157
+ }
158
+
159
+ if (stripe) {
160
+ const customers = await stripe.customers.list({ email: cleanEmail, limit: 1 });
161
+ let hasPurchase = Array.isArray(customers.data) && customers.data.length > 0;
162
+
163
+ if (!hasPurchase) {
164
+ const sessions = await stripe.checkout.sessions.list({ limit: 20 });
165
+ hasPurchase = sessions.data.some(
166
+ (s) =>
167
+ s.payment_status !== "unpaid" &&
168
+ (s.customer_details?.email || s.customer_email || "").toLowerCase().trim() === cleanEmail
169
+ );
170
+ }
171
+
172
+ if (!hasPurchase) {
173
+ return {
174
+ ok: false,
175
+ authenticated: false,
176
+ error: "No active Launch Pass found for this email in Stripe. Purchase a Launch Pass ($29) first."
177
+ };
178
+ }
179
+
180
+ const generatedOtp = String(Math.floor(100000 + Math.random() * 900000));
181
+ const challenge = createSignedOtpChallenge(cleanEmail, generatedOtp, "launch_pass");
182
+ const licenseForEmail = signLicenseToken({ email: cleanEmail, plan: "launch_pass", domain: "*" });
183
+ const emailResult = await sendLoginOtpViaResend(cleanEmail, generatedOtp, licenseForEmail);
184
+
185
+ if (emailResult.sent) {
186
+ return {
187
+ ok: true,
188
+ authenticated: false,
189
+ requiresOtp: true,
190
+ email: cleanEmail,
191
+ challengeToken: challenge,
192
+ message: `We emailed a 6-digit verification code to ${cleanEmail}. Enter it below to unlock your billing & receipts.`
193
+ };
194
+ }
195
+
196
+ return {
197
+ ok: false,
198
+ authenticated: false,
199
+ error: "For security, email-only lookup is disabled until RESEND_API_KEY is set in Vercel. Please sign in using your pb_live_... license key."
200
+ };
201
+ }
202
+
203
+ return {
204
+ ok: false,
205
+ authenticated: false,
206
+ error: "Please sign in with your pb_live_... license key."
207
+ };
208
+ }
@@ -0,0 +1,123 @@
1
+ // Complete 85-Directory Database for Postbuild (postbuild.dev)
2
+ // Curated startup, AI, developer, indie SaaS, and software discovery directories.
3
+
4
+ export const DIRECTORIES = [
5
+ // Tier 1: Major Launch Boards & Communities (1-15)
6
+ { id: "dir-01", name: "Product Hunt", url: "https://www.producthunt.com/posts/new", dr: 91, category: "Launch Board", authType: "OAuth / Magic Link", autoSubmit: true },
7
+ { id: "dir-02", name: "Hacker News (Show HN)", url: "https://news.ycombinator.com/submit", dr: 92, category: "Developer Community", authType: "Account Session", autoSubmit: true },
8
+ { id: "dir-03", name: "AlternativeTo", url: "https://alternativeto.net/add-application/", dr: 86, category: "Software Discovery", authType: "OTP (@postbuild.dev)", autoSubmit: true },
9
+ { id: "dir-04", name: "There's An AI For That", url: "https://theresanaiforthat.com/submit/", dr: 82, category: "AI Directory", authType: "OTP (@postbuild.dev)", autoSubmit: true },
10
+ { id: "dir-05", name: "Indie Hackers Products", url: "https://www.indiehackers.com/products", dr: 81, category: "Indie SaaS", authType: "Email Link", autoSubmit: true },
11
+ { id: "dir-06", name: "Futurepedia", url: "https://www.futurepedia.io/submit-tool", dr: 79, category: "AI Directory", authType: "Email Verification", autoSubmit: true },
12
+ { id: "dir-07", name: "SaaSHub", url: "https://www.saashub.com/submit", dr: 77, category: "Software Directory", authType: "OTP (@postbuild.dev)", autoSubmit: true },
13
+ { id: "dir-08", name: "BetaList", url: "https://betalist.com/submit", dr: 76, category: "Early Adopters", authType: "Email Verification", autoSubmit: true },
14
+ { id: "dir-09", name: "FutureTools.io", url: "https://www.futuretools.io/submit-a-tool", dr: 75, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
15
+ { id: "dir-10", name: "Toolify.ai", url: "https://www.toolify.ai/submit", dr: 74, category: "AI Directory", authType: "Direct Form + Email", autoSubmit: true },
16
+ { id: "dir-11", name: "G2 Software", url: "https://www.g2.com/products/new", dr: 90, category: "B2B Software", authType: "Work Email (@postbuild.dev)", autoSubmit: true },
17
+ { id: "dir-12", name: "Crozdesk", url: "https://crozdesk.com/vendors", dr: 73, category: "B2B SaaS", authType: "Email Verification", autoSubmit: true },
18
+ { id: "dir-13", name: "SourceForge", url: "https://sourceforge.net/create/", dr: 91, category: "Developer Tools", authType: "OTP (@postbuild.dev)", autoSubmit: true },
19
+ { id: "dir-14", name: "Slant.co", url: "https://www.slant.co", dr: 80, category: "Comparison Engine", authType: "Account Session", autoSubmit: true },
20
+ { id: "dir-15", name: "StackShare", url: "https://stackshare.io/submit", dr: 82, category: "Developer Stack", authType: "GitHub / Email", autoSubmit: true },
21
+
22
+ // Tier 2: Developer & Open-Source Directories (16-30)
23
+ { id: "dir-16", name: "DevHunt", url: "https://devhunt.org/login", dr: 68, category: "Developer Tools", authType: "GitHub / Email", autoSubmit: true },
24
+ { id: "dir-17", name: "OpenAlternative", url: "https://openalternative.co/submit", dr: 66, category: "Open Source", authType: "Direct Form", autoSubmit: true },
25
+ { id: "dir-18", name: "Console.dev", url: "https://console.dev/submit", dr: 69, category: "Developer Tools", authType: "Direct Form", autoSubmit: true },
26
+ { id: "dir-19", name: "LibHunt", url: "https://www.libhunt.com", dr: 72, category: "Developer Libraries", authType: "GitHub Link", autoSubmit: true },
27
+ { id: "dir-20", name: "Awesome Lists Index", url: "https://awesomelists.top", dr: 61, category: "Open Source", authType: "PR / Form", autoSubmit: true },
28
+ { id: "dir-21", name: "FindDevTools", url: "https://finddevtools.com/submit", dr: 54, category: "Developer Tools", authType: "Direct Form", autoSubmit: true },
29
+ { id: "dir-22", name: "DevPages", url: "https://www.devpages.io/submit", dr: 52, category: "Developer Tools", authType: "Direct Form", autoSubmit: true },
30
+ { id: "dir-23", name: "Tooling.one", url: "https://tooling.one/submit", dr: 49, category: "Developer Tools", authType: "Direct Form", autoSubmit: true },
31
+ { id: "dir-24", name: "Daily.dev Picks", url: "https://daily.dev", dr: 76, category: "Developer Feed", authType: "Email Link", autoSubmit: true },
32
+ { id: "dir-25", name: "CodeGems", url: "https://codegems.io", dr: 48, category: "Developer Tools", authType: "Direct Form", autoSubmit: true },
33
+ { id: "dir-26", name: "HelloGitHub", url: "https://hellogithub.com", dr: 64, category: "Open Source", authType: "Direct Form", autoSubmit: true },
34
+ { id: "dir-27", name: "RepoTrends", url: "https://repotrends.com", dr: 51, category: "Developer Tools", authType: "Direct Form", autoSubmit: true },
35
+ { id: "dir-28", name: "Selfh.st", url: "https://selfh.st/submit", dr: 62, category: "Self-Hosted / Dev", authType: "Direct Form", autoSubmit: true },
36
+ { id: "dir-29", name: "CLI.club", url: "https://cli.club", dr: 46, category: "CLI Tools", authType: "Direct Form", autoSubmit: true },
37
+ { id: "dir-30", name: "TerminalTroves", url: "https://terminaltrove.com/submit/", dr: 58, category: "CLI & Terminal", authType: "Direct Form", autoSubmit: true },
38
+
39
+ // Tier 3: Indie Launch & Maker Platforms (31-50)
40
+ { id: "dir-31", name: "Uneed.best", url: "https://www.uneed.best/submit-a-tool", dr: 64, category: "Indie Launch", authType: "Magic Link (@postbuild.dev)", autoSubmit: true },
41
+ { id: "dir-32", name: "MicroLaunch", url: "https://microlaunch.net/submit", dr: 59, category: "Indie Launch", authType: "OTP (@postbuild.dev)", autoSubmit: true },
42
+ { id: "dir-33", name: "TinyLaunch", url: "https://tinylaunch.com/submit", dr: 54, category: "Indie Launch", authType: "Direct Form + Email", autoSubmit: true },
43
+ { id: "dir-34", name: "Launching Next", url: "https://www.launchingnext.com/submit/", dr: 67, category: "Startup Directory", authType: "Direct Form", autoSubmit: true },
44
+ { id: "dir-35", name: "SideProjectors", url: "https://www.sideprojectors.com/project/submit", dr: 62, category: "Side Projects", authType: "Email Verification", autoSubmit: true },
45
+ { id: "dir-36", name: "StartupBase", url: "https://startupbase.io/submit", dr: 61, category: "Startup Directory", authType: "OTP (@postbuild.dev)", autoSubmit: true },
46
+ { id: "dir-37", name: "Peerlist Spotlight", url: "https://peerlist.io/projects", dr: 74, category: "Maker Network", authType: "OTP (@postbuild.dev)", autoSubmit: true },
47
+ { id: "dir-38", name: "1000.tools", url: "https://1000.tools/submit", dr: 57, category: "Indie Tools", authType: "Direct Form", autoSubmit: true },
48
+ { id: "dir-39", name: "Fazier", url: "https://fazier.com/launch", dr: 56, category: "Indie Launch", authType: "Magic Link (@postbuild.dev)", autoSubmit: true },
49
+ { id: "dir-40", name: "Huzzler", url: "https://huzzler.so/products", dr: 55, category: "Indie Founders", authType: "Email Verification", autoSubmit: true },
50
+ { id: "dir-41", name: "IndiePage", url: "https://indiepa.ge", dr: 63, category: "Maker Portfolio", authType: "Magic Link (@postbuild.dev)", autoSubmit: true },
51
+ { id: "dir-42", name: "BetaPage", url: "https://betapage.co/submit-startup", dr: 68, category: "Startup Launch", authType: "Email Verification", autoSubmit: true },
52
+ { id: "dir-43", name: "StartupBuffer", url: "https://startupbuffer.com/site/submit", dr: 60, category: "Startup Directory", authType: "Direct Form", autoSubmit: true },
53
+ { id: "dir-44", name: "StartupStash", url: "https://startupstash.com/add-listing/", dr: 72, category: "Startup Resources", authType: "Direct Form + Email", autoSubmit: true },
54
+ { id: "dir-45", name: "KillerStartups", url: "https://www.killerstartups.com/submit-startup", dr: 71, category: "Startup Reviews", authType: "Direct Form", autoSubmit: true },
55
+ { id: "dir-46", name: "CrazyAboutStartups", url: "https://www.crazyaboutstartups.com/submit-startup/", dr: 55, category: "Startup Directory", authType: "Direct Form", autoSubmit: true },
56
+ { id: "dir-47", name: "Startups.fyi", url: "https://www.startups.fyi/submit", dr: 58, category: "Startup Discovery", authType: "Direct Form", autoSubmit: true },
57
+ { id: "dir-48", name: "NoCodeList", url: "https://nocodelist.co/submit", dr: 57, category: "Modern Software", authType: "Direct Form", autoSubmit: true },
58
+ { id: "dir-49", name: "Makerlog Products", url: "https://getmakerlog.com", dr: 60, category: "Maker Community", authType: "Email Verification", autoSubmit: true },
59
+ { id: "dir-50", name: "wip.co Products", url: "https://wip.co", dr: 63, category: "Maker Community", authType: "Account Session", autoSubmit: true },
60
+
61
+ // Tier 4: AI & Automation Directories (51-70)
62
+ { id: "dir-51", name: "TopAI.tools", url: "https://topai.tools/submit", dr: 69, category: "AI Directory", authType: "Email Verification", autoSubmit: true },
63
+ { id: "dir-52", name: "AI Scout", url: "https://aiscout.net/submit-listing/", dr: 58, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
64
+ { id: "dir-53", name: "Easy With AI", url: "https://easywithai.com/submit-tool/", dr: 66, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
65
+ { id: "dir-54", name: "AI Tool Hunt", url: "https://www.aitoolhunt.com/submit", dr: 61, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
66
+ { id: "dir-55", name: "Insidr.ai", url: "https://www.insidr.ai/submit-tools/", dr: 65, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
67
+ { id: "dir-56", name: "AI Valley", url: "https://aivalley.ai/submit-tool/", dr: 57, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
68
+ { id: "dir-57", name: "AllThingsAI", url: "https://allthingsai.com/submit", dr: 64, category: "AI Directory", authType: "Email Verification", autoSubmit: true },
69
+ { id: "dir-58", name: "AIDir.cc", url: "https://aidir.cc/submit", dr: 49, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
70
+ { id: "dir-59", name: "OpenTools.ai", url: "https://opentools.ai/submit-a-tool", dr: 67, category: "AI Directory", authType: "OTP (@postbuild.dev)", autoSubmit: true },
71
+ { id: "dir-60", name: "AIxploria", url: "https://www.aixploria.com/en/add-ai/", dr: 71, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
72
+ { id: "dir-61", name: "SaaS AI Tools", url: "https://saasaitools.com/submit/", dr: 59, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
73
+ { id: "dir-62", name: "GPTE.ai", url: "https://gpte.ai/submit-a-tool/", dr: 62, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
74
+ { id: "dir-63", name: "AI Center", url: "https://aicenter.ai/submit", dr: 53, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
75
+ { id: "dir-64", name: "AI Tools Directory", url: "https://aitoolsdirectory.com/submit-tool", dr: 63, category: "AI Directory", authType: "Direct Form + Email", autoSubmit: true },
76
+ { id: "dir-65", name: "NextGenTools", url: "https://www.nxgntools.com/submit", dr: 55, category: "AI & SaaS", authType: "Magic Link (@postbuild.dev)", autoSubmit: true },
77
+ { id: "dir-66", name: "BasedTools", url: "https://www.basedtools.ai/submit", dr: 54, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
78
+ { id: "dir-67", name: "Tap4 AI", url: "https://tap4.ai/submit", dr: 58, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
79
+ { id: "dir-68", name: "WaildWorld AI", url: "https://waildworld.com/submit", dr: 48, category: "AI Directory", authType: "Direct Form", autoSubmit: true },
80
+ { id: "dir-69", name: "AgentLocker", url: "https://agentlocker.ai/submit", dr: 51, category: "AI Agents", authType: "Direct Form", autoSubmit: true },
81
+ { id: "dir-70", name: "MCP.so Directory", url: "https://mcp.so/submit", dr: 64, category: "MCP & Agents", authType: "GitHub / Form", autoSubmit: true },
82
+
83
+ // Tier 5: SaaS, Growth & Niche Directories (71-85)
84
+ { id: "dir-71", name: "SaaSWorthy", url: "https://www.saasworthy.com/offerings", dr: 74, category: "SaaS Directory", authType: "Work Email (@postbuild.dev)", autoSubmit: true },
85
+ { id: "dir-72", name: "GetApp / Capterra", url: "https://www.capterra.com/vendors/sign-up", dr: 90, category: "B2B Software", authType: "Work Email (@postbuild.dev)", autoSubmit: true },
86
+ { id: "dir-73", name: "SoftwareSuggest", url: "https://www.softwaresuggest.com/vendors", dr: 76, category: "SaaS Directory", authType: "Email Verification", autoSubmit: true },
87
+ { id: "dir-74", name: "GoodFirms", url: "https://www.goodfirms.co/get-listed", dr: 84, category: "Software Directory", authType: "Email Verification", autoSubmit: true },
88
+ { id: "dir-75", name: "TrustRadius", url: "https://www.trustradius.com/vendor", dr: 85, category: "B2B Software", authType: "Work Email (@postbuild.dev)", autoSubmit: true },
89
+ { id: "dir-76", name: "Serchen", url: "https://www.serchen.com/get-listed/", dr: 68, category: "Cloud & SaaS", authType: "Email Verification", autoSubmit: true },
90
+ { id: "dir-77", name: "Cloudswave", url: "https://www.cloudswave.com", dr: 61, category: "Cloud SaaS", authType: "Direct Form", autoSubmit: true },
91
+ { id: "dir-78", name: "Feedough Startups", url: "https://www.feedough.com/submit-your-startup/", dr: 69, category: "Startup Media", authType: "Direct Form", autoSubmit: true },
92
+ { id: "dir-79", name: "EU-Startups Directory", url: "https://www.eu-startups.com/directory/", dr: 78, category: "Startup Directory", authType: "Email Verification", autoSubmit: true },
93
+ { id: "dir-80", name: "F6S Startup Network", url: "https://www.f6s.com", dr: 83, category: "Founder Network", authType: "OTP (@postbuild.dev)", autoSubmit: true },
94
+ { id: "dir-81", name: "Gust Startups", url: "https://gust.com", dr: 80, category: "Startup Platform", authType: "Email Verification", autoSubmit: true },
95
+ { id: "dir-82", name: "IndieDeals", url: "https://indiedeals.co/submit", dr: 50, category: "Deals & Launch", authType: "Direct Form", autoSubmit: true },
96
+ { id: "dir-83", name: "PitchWall", url: "https://pitchwall.co", dr: 65, category: "Product Pitch", authType: "Email Verification", autoSubmit: true },
97
+ { id: "dir-84", name: "Land-book", url: "https://land-book.com/submit", dr: 73, category: "Landing Design", authType: "Direct Form", autoSubmit: true },
98
+ { id: "dir-85", name: "OnePageLove", url: "https://onepagelove.com/submit", dr: 79, category: "Landing Showcase", authType: "Direct Form", autoSubmit: true }
99
+ ];
100
+
101
+ export function buildPrefilledDirectoryUrl(dir, launchKit) {
102
+ try {
103
+ const u = new URL(dir.url);
104
+ const utmTarget = launchKit?.utmUrl || launchKit?.targetUrl || "https://postbuild.dev";
105
+ const name = launchKit?.productName || "Postbuild";
106
+ const tagline = launchKit?.tagline || "Ship code, automate SEO and directory dispatch";
107
+
108
+ if (dir.url.includes("news.ycombinator.com/submit")) {
109
+ u.searchParams.set("u", utmTarget);
110
+ u.searchParams.set("t", `Show HN: ${name} – ${tagline}`);
111
+ return u.toString();
112
+ }
113
+ u.searchParams.set("url", utmTarget);
114
+ u.searchParams.set("name", name);
115
+ u.searchParams.set("tagline", tagline);
116
+ if (launchKit?.makerEmail) {
117
+ u.searchParams.set("email", launchKit.makerEmail);
118
+ }
119
+ return u.toString();
120
+ } catch {
121
+ return dir.url;
122
+ }
123
+ }