git-paid 0.0.0-stage → 2.1.1

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.
package/README.md CHANGED
@@ -1,3 +1,115 @@
1
- # Temporary Holding Version
1
+ # postbuild (`git-paid`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **`git push` ships your code. `git paid` sells it.**
4
+ >
5
+ > Autonomous Post-Build Technical SEO, 25-Route Programmatic SEO Generator, Dedicated `@postbuild.dev` Verification Inbox, 85-Directory Launch Dispatcher, and Stateful MCP Server for AI Coding Agents.
6
+
7
+ - **Web Studio & Live Audit**: [https://postbuild.dev](https://postbuild.dev)
8
+ - **CLI Binary**: `npx git-paid` (or `npx postbuild`)
9
+ - **MCP Server**: `npx git-paid mcp`
10
+
11
+ ---
12
+
13
+ ## Quick Start
14
+
15
+ Run directly from your project root after deploying to Vercel, Railway, Render, or Fly.io:
16
+
17
+ ```bash
18
+ # 1. Run a live technical SEO & monetization readiness audit
19
+ npx git-paid https://your-app.com
20
+
21
+ # 2. Auto-generate & write robots.txt, sitemap.xml, llms.txt, and 25 static Programmatic SEO HTML pages
22
+ npx git-paid fix https://your-app.com
23
+
24
+ # 3. Dispatch your launch kit across 85 curated SaaS/AI directories with headless form inspection
25
+ npx git-paid submit https://your-app.com --limit 10
26
+
27
+ # 4. Poll your dedicated @postbuild.dev verification inbox & auto-verify inbound confirmation links / OTPs
28
+ npx git-paid inbox https://your-app.com --verify
29
+
30
+ # 5. Install agent skill files (.agent/skills/postbuild/SKILL.md & CLAUDE.md) into your repository
31
+ npx git-paid skill
32
+ ```
33
+
34
+ Need a git alias so `git paid` works natively alongside `git push`?
35
+
36
+ ```bash
37
+ git config --global alias.paid '!npx git-paid'
38
+ git paid https://your-app.com
39
+ ```
40
+
41
+ ---
42
+
43
+ ## What `git-paid` Automates After You Ship
44
+
45
+ | Capability | Manual Post-Build Workflow | `git-paid` (`postbuild.dev`) |
46
+ | :--- | :--- | :--- |
47
+ | **Technical & AI Search SEO** | Hand-writing meta tags, JSON-LD, and crawler rules | Writes `robots.txt` (`OAI-SearchBot`, `PerplexityBot`, `ClaudeBot`), `sitemap.xml`, `llms.txt`, and `SoftwareApplication` JSON-LD in 1 command |
48
+ | **25 Programmatic SEO Pages** | Days of keyword research and static page coding | Pre-renders 25 high-intent `/use-cases/*`, `/compare/*`, `/calculators/*`, `/guides/*`, `/templates/*`, `/alternatives/*`, and `/checklists/*` HTML landing pages |
49
+ | **85-Directory Dispatch** | 14+ hours of repetitive form filling & UTM tagging | Synthesizes Show HN / Product Hunt / directory copy and dispatches via `git paid submit` |
50
+ | **Verification Inbox (`@postbuild.dev`)** | Clutters personal inbox with 85+ confirmation emails | Provisions `<slug>-bot@postbuild.dev`, extracts 6-digit OTPs, and auto-clicks verification URLs |
51
+ | **Self-Serve Stripe Checkout Gate** | Custom billing boilerplate | Drop-in `PostbuildCheckoutGate.jsx` + Customer Billing Portal & receipt management |
52
+
53
+ ---
54
+
55
+ ## Stateful MCP Server (`npx git-paid mcp`)
56
+
57
+ `git-paid` includes a built-in **Stateful Model Context Protocol (MCP) Server** over `stdio` (JSON-RPC 2.0) designed for **Claude Code**, **Cursor**, **Windsurf**, and **Google Antigravity / Gemini**.
58
+
59
+ Unlike stateless MCP servers that re-serialize identical directory lists and SEO payloads on every turn, the Postbuild MCP middleware provides:
60
+
61
+ 1. **Watermark Context Residuals (`unchanged@vN` / `updated@vN`)**:
62
+ - Turn 1 projects clean entity stubs; Turn 2+ emits lightweight `@vN` watermark deltas for unchanged records, cutting multi-turn token consumption by **75% to 84%**.
63
+ 2. **In-Memory Superset Query Cache (`<1ms` Latency)**:
64
+ - Follow-up filtered tool calls (`category`, `min_da`, `status`, `limit`) resolve in-memory in `<1ms` with zero redundant network round-trips.
65
+ 3. **Two-Phase Cryptographic OCC Write Guard (`ct_...`)**:
66
+ - Live directory dispatch mutations require a Phase 1 dry-run (`dry_run: true`) that issues an HMAC-signed `commit_token` (`ct_...`) before Phase 2 commits the launch state.
67
+ 4. **Untrusted Content Spotlighting**:
68
+ - External scraped metadata and inbound email bodies are automatically wrapped in `<<<UNTRUSTED_CONTENT_DATA_ONLY: ... >>>` delimiters to prevent indirect prompt injection.
69
+
70
+ ### Available MCP Tools
71
+
72
+ | Tool Name | Description |
73
+ | :--- | :--- |
74
+ | `postbuild_scan` | Audits any live URL (8 technical/AI SEO & paywall checks), synthesizes launch copy, and caches working-set state. |
75
+ | `postbuild_directories` | Queries the 85-directory catalog using the in-memory superset cache and `@vN` watermark deltas. |
76
+ | `postbuild_pseo` | Queries and filters the 25 programmatic SEO routes with stateful delta compaction. |
77
+ | `postbuild_dispatch` | Executes Two-Phase Cryptographic OCC directory dispatch (`dry_run: true` → `commit_token` → commit). |
78
+ | `postbuild_inbox` | Polls `<slug>-bot@postbuild.dev`, extracts 6-digit OTPs, and auto-verifies inbound confirmation links. |
79
+ | `postbuild_fix` | Writes `robots.txt`, `sitemap.xml`, `llms.txt`, `.postbuild/pseo/*.html`, and `PostbuildCheckoutGate.jsx` to disk. |
80
+ | `postbuild_vault_sync` | Syncs live SEO scores, directory status, inbox OTPs, and MCP telemetry into an **Obsidian VaultOS** dashboard. |
81
+
82
+ ### MCP Configuration (`mcp_config.json` / `.cursor/mcp.json` / `claude_desktop_config.json`)
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "postbuild": {
88
+ "command": "npx",
89
+ "args": ["-y", "git-paid", "mcp"]
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Obsidian VaultOS Integration
98
+
99
+ Sync your live project launches directly into your **Obsidian VaultOS** command center:
100
+
101
+ ```bash
102
+ npx git-paid vault https://your-app.com --vault "C:/Users/You/ObsidianVault"
103
+ ```
104
+
105
+ Writes `_meta/postbuild-telemetry.json` and `_dashboards/Postbuild Launch.md` with live SEO readiness scores, 85-directory dispatch progress, `@postbuild.dev` OTP logs, and Buyer Intent Radar threads.
106
+
107
+ ---
108
+
109
+ ## License & Commercial Terms
110
+
111
+ **Proprietary — Commercial Software (`postbuild.dev`)**
112
+
113
+ Copyright © 2026 Bennett Moore ([https://postbuild.dev](https://postbuild.dev)). All rights reserved.
114
+
115
+ The `git-paid` CLI and MCP client package may be installed and executed via `npm` / `npx` to interact with [https://postbuild.dev](https://postbuild.dev). Redistribution, re-hosting, or commercial resale of the underlying platform, directory dispatch engine, or source bundle without an active license from [https://postbuild.dev](https://postbuild.dev) is prohibited.
@@ -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
+ }