git-paid 2.1.5 → 2.1.6

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/bin/git-paid.js CHANGED
@@ -26,7 +26,10 @@ if (subcmd === "mcp" || args.includes("--mcp")) {
26
26
  }
27
27
 
28
28
  function printProGateBanner(licenseCheck, featureName) {
29
- console.log(` [LOCKED] ${featureName} requires an active postbuild Launch Pass ($29) or Studio ($49/mo) license.`);
29
+ console.log(` [LOCKED] ${featureName} requires an active postbuild Launch Pass ($29) license.`);
30
+ if (licenseCheck && licenseCheck.reason === "DOMAIN_MISMATCH") {
31
+ console.log(` [SCOPE] Your active license covers ${licenseCheck.licensedDomain}, not ${licenseCheck.requestedDomain}.`);
32
+ }
30
33
  console.log(` [UNLOCK] Instant Stripe Checkout ($29 Launch Pass):`);
31
34
  console.log(` ${licenseCheck.checkoutUrl}\n`);
32
35
  console.log(` [LOGIN] Already purchased? Authenticate your CLI & MCP server:`);
@@ -42,11 +45,11 @@ async function runCli() {
42
45
  if (subcmd === "--help" || subcmd === "-h" || subcmd === "help") {
43
46
  console.log(" Usage:");
44
47
  console.log(" npx git-paid <url> Run free 8-point SEO/paywall audit & 5-directory preview");
45
- console.log(" git paid login <pb_live_key> Authenticate CLI & MCP with your Launch Pass / Studio license");
48
+ console.log(" git paid login <pb_live_key> Authenticate CLI & MCP with your Launch Pass license");
46
49
  console.log(" git paid fix [url] [PRO] Write 25 pSEO HTML pages, robots.txt, sitemap.xml & llms.txt");
47
50
  console.log(" git paid submit [url] [PRO] Headless 85-directory form submitter + OTP auto-verifier");
48
51
  console.log(" git paid inbox [bot-email] [PRO] Check & auto-verify <domain-tld>-bot@postbuild.dev (e.g. acme-com-bot@) links & OTPs");
49
- console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.5)");
52
+ console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.6)");
50
53
  console.log(" git paid init-agent Scaffold .agent/skills/postbuild/SKILL.md & MCP config");
51
54
  console.log(" git paid vault [path] [PRO] Sync live telemetry & Bento dashboard to Obsidian VaultOS\n");
52
55
  process.exit(0);
@@ -150,7 +153,7 @@ async function runCli() {
150
153
  if (subcmd === "init-agent" || subcmd === "agent" || subcmd === "skill") {
151
154
  const res = installAgentSkillFiles({ cwd: process.cwd() });
152
155
  console.log(` [+] Scaffolded Agent Skill: ${res.skillMdPath}`);
153
- console.log(` [+] Powered by: postbuild-mcp@2.1.5 (Stateful Delta Compaction)\n`);
156
+ console.log(` [+] Powered by: postbuild-mcp@2.1.6 (Stateful Delta Compaction)\n`);
154
157
  console.log(" Add this to your Claude Code / Cursor / Antigravity MCP config:\n");
155
158
  console.log(JSON.stringify(res.mcpConfigSnippet, null, 2));
156
159
  console.log("");
@@ -158,7 +161,7 @@ async function runCli() {
158
161
  }
159
162
 
160
163
  if (subcmd === "vault" || subcmd === "vaultos") {
161
- const license = await verifyCliLicense({ domain: "postbuild.dev" });
164
+ const license = await verifyCliLicense({ domain: "" });
162
165
  if (!license.valid) {
163
166
  printProGateBanner(license, "Markdown / Docs & Telemetry Sync");
164
167
  process.exit(1);
@@ -168,7 +171,7 @@ async function runCli() {
168
171
  args.find((a, i) => i > 0 && !a.startsWith("--") && !/^https?:\/\//i.test(a)) ||
169
172
  process.cwd();
170
173
  const urlArgs = args.filter((a, i) => i > 0 && /^https?:\/\//i.test(a));
171
- const targetUrls = urlArgs.length ? urlArgs : ["https://postbuild.dev"];
174
+ const targetUrls = urlArgs.length ? urlArgs : [license.domain ? `https://${license.domain}` : "https://postbuild.dev"];
172
175
 
173
176
  console.log(` [*] Syncing Postbuild MCP Telemetry & Launch Status...`);
174
177
  console.log(` [*] Target Directory: ${vaultArg}`);
@@ -200,12 +203,12 @@ async function runCli() {
200
203
  }
201
204
 
202
205
  if (subcmd === "inbox") {
203
- const inboxAddr = args.find((a, i) => i > 0 && !a.startsWith("--")) || "postbuild-bot@postbuild.dev";
204
- const license = await verifyCliLicense({ domain: "postbuild.dev" });
206
+ const license = await verifyCliLicense({ domain: "" });
205
207
  if (!license.valid) {
206
208
  printProGateBanner(license, "Dedicated @postbuild.dev Verification Inbox & OTP Auto-Verifier");
207
209
  process.exit(1);
208
210
  }
211
+ const inboxAddr = args.find((a, i) => i > 0 && !a.startsWith("--")) || license.inbox || "postbuild-bot@postbuild.dev";
209
212
 
210
213
  if (args.includes("--test")) {
211
214
  await sendTestVerificationEmail(inboxAddr, "postbuild.dev");
package/lib/auth.js CHANGED
@@ -207,12 +207,37 @@ export function loadLocalCredentials() {
207
207
 
208
208
  export async function verifyCliLicense({ domain = "postbuild.dev", token = "" } = {}) {
209
209
  const candidateToken = (token || "").trim() || loadLocalCredentials()?.token || "";
210
+ const cleanReqDomain = String(domain || "")
211
+ .toLowerCase()
212
+ .trim()
213
+ .replace(/^https?:\/\//, "")
214
+ .replace(/^(www\.)+/, "")
215
+ .split("/")[0]
216
+ .split(":")[0];
210
217
 
211
218
  if (candidateToken && candidateToken.startsWith("pb_live_")) {
212
219
  // 1. If running on the server (where signing secret is present), verify signature & revocation directly
213
220
  try {
214
221
  const localCheck = verifyLicenseToken(candidateToken);
215
222
  if (localCheck.valid) {
223
+ const licDomain = String(localCheck.domain || "")
224
+ .toLowerCase()
225
+ .trim()
226
+ .replace(/^https?:\/\//, "")
227
+ .replace(/^(www\.)+/, "")
228
+ .split("/")[0]
229
+ .split(":")[0];
230
+ if (cleanReqDomain && licDomain && licDomain !== "*" && cleanReqDomain !== licDomain) {
231
+ return {
232
+ valid: false,
233
+ reason: "DOMAIN_MISMATCH",
234
+ licensedDomain: licDomain,
235
+ requestedDomain: cleanReqDomain,
236
+ checkoutUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain)}`,
237
+ webUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain)}`,
238
+ message: `Your Launch Pass license key is scoped to ${licDomain} and does not cover ${cleanReqDomain}.`
239
+ };
240
+ }
216
241
  return {
217
242
  valid: true,
218
243
  email: localCheck.email,
@@ -226,8 +251,8 @@ export async function verifyCliLicense({ domain = "postbuild.dev", token = "" }
226
251
  return {
227
252
  valid: false,
228
253
  reason: localCheck.error || "INVALID_OR_REVOKED_LICENSE",
229
- checkoutUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(domain)}`,
230
- webUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(domain)}`,
254
+ checkoutUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain || domain)}`,
255
+ webUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain || domain)}`,
231
256
  message: localCheck.error || "Invalid or revoked pb_live_ license key."
232
257
  };
233
258
  } catch {
@@ -245,6 +270,24 @@ export async function verifyCliLicense({ domain = "postbuild.dev", token = "" }
245
270
  if (resp.ok) {
246
271
  const data = await resp.json();
247
272
  if (data && data.ok && data.authenticated) {
273
+ const licDomain = String(data.domain || "")
274
+ .toLowerCase()
275
+ .trim()
276
+ .replace(/^https?:\/\//, "")
277
+ .replace(/^(www\.)+/, "")
278
+ .split("/")[0]
279
+ .split(":")[0];
280
+ if (cleanReqDomain && licDomain && licDomain !== "*" && cleanReqDomain !== licDomain) {
281
+ return {
282
+ valid: false,
283
+ reason: "DOMAIN_MISMATCH",
284
+ licensedDomain: licDomain,
285
+ requestedDomain: cleanReqDomain,
286
+ checkoutUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain)}`,
287
+ webUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain)}`,
288
+ message: `Your Launch Pass license key is scoped to ${licDomain} and does not cover ${cleanReqDomain}.`
289
+ };
290
+ }
248
291
  return {
249
292
  valid: true,
250
293
  email: data.email,
@@ -262,10 +305,10 @@ export async function verifyCliLicense({ domain = "postbuild.dev", token = "" }
262
305
  return {
263
306
  valid: false,
264
307
  reason: "PRO_LICENSE_REQUIRED",
265
- checkoutUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(domain)}`,
266
- webUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(domain)}`,
308
+ checkoutUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain || domain)}`,
309
+ webUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(cleanReqDomain || domain)}`,
267
310
  message:
268
- "A verified postbuild Launch Pass ($29) or Studio ($49/mo) license (pb_live_...) is required to unlock 25 Programmatic SEO HTML pages, 85-directory dispatch, and @postbuild.dev inbox verification."
311
+ "A verified postbuild Launch Pass ($29) license (pb_live_...) is required to unlock 25 Programmatic SEO HTML pages, 85-directory dispatch, and @postbuild.dev inbox verification."
269
312
  };
270
313
  }
271
314
 
package/lib/engine.js CHANGED
@@ -384,6 +384,11 @@ Allow: /
384
384
 
385
385
  Sitemap: ${origin}/sitemap.xml`;
386
386
 
387
+ const isPostbuildSelf = origin.includes("postbuild.dev");
388
+ const offersBlock = isPostbuildSelf
389
+ ? `,\n "offers": {\n "@type": "Offer",\n "price": "29.00",\n "priceCurrency": "USD"\n }`
390
+ : "";
391
+
387
392
  const jsonLd = `<script type="application/ld+json">
388
393
  {
389
394
  "@context": "https://schema.org",
@@ -392,12 +397,7 @@ Sitemap: ${origin}/sitemap.xml`;
392
397
  "url": "${origin}",
393
398
  "applicationCategory": "BusinessApplication",
394
399
  "operatingSystem": "Web",
395
- "description": "${description.replace(/"/g, '\\"')}",
396
- "offers": {
397
- "@type": "Offer",
398
- "price": "29.00",
399
- "priceCurrency": "USD"
400
- }
400
+ "description": "${description.replace(/"/g, '\\"')}"${offersBlock}
401
401
  }
402
402
  </script>`;
403
403
 
package/lib/fixer.js CHANGED
@@ -53,6 +53,7 @@ export function buildHeadSeoSnippet(report) {
53
53
  ? `${report.launchKit.brandName || report.launchKit.productName || report.domain} — ${report.launchKit.tagline}`
54
54
  : report.meta?.title || report.domain;
55
55
  const desc = report.launchKit?.shortPitch || report.meta?.description || "";
56
+ const cleanOrigin = String(report.targetUrl || `https://${report.domain}`).replace(/\/$/, "");
56
57
  return `<!-- Generated by postbuild (git paid fix) for ${report.domain} -->
57
58
  <title>${title}</title>
58
59
  <meta name="description" content="${desc.replace(/"/g, "&quot;")}" />
@@ -64,9 +65,11 @@ export function buildHeadSeoSnippet(report) {
64
65
  <meta property="og:title" content="${title.replace(/"/g, "&quot;")}" />
65
66
  <meta property="og:description" content="${desc.replace(/"/g, "&quot;")}" />
66
67
  <meta property="og:url" content="${report.targetUrl}" />
68
+ <meta property="og:image" content="${cleanOrigin}/og-card.png" />
67
69
  <meta name="twitter:card" content="summary_large_image" />
68
70
  <meta name="twitter:title" content="${title.replace(/"/g, "&quot;")}" />
69
71
  <meta name="twitter:description" content="${desc.replace(/"/g, "&quot;")}" />
72
+ <meta name="twitter:image" content="${cleanOrigin}/og-card.png" />
70
73
 
71
74
  <!-- Structured Data (JSON-LD) -->
72
75
  ${report.artifacts?.jsonLd || ""}
package/lib/guard.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Unified Authentication, Inbox Ownership, Admin Guard, CORS & Rate Limiting for Postbuild API Routes
3
+ */
4
+
5
+ import crypto from "node:crypto";
6
+ import { verifyLicenseToken, checkStripeCustomerStatusCached, getFounderEmailsSet } from "./auth.js";
7
+ import { getBotInboxForDomain, normalizeInboxInput } from "./inbox.js";
8
+
9
+ const rateLimitBuckets = new Map(); // `${bucket}:${ip}` -> { count: number, resetAt: number }
10
+
11
+ const ALLOWED_ORIGINS = new Set([
12
+ "https://www.postbuild.dev",
13
+ "https://postbuild.dev"
14
+ ]);
15
+
16
+ export function applyCorsHeaders(req, res) {
17
+ const origin = req.headers?.origin || "";
18
+ if (ALLOWED_ORIGINS.has(origin)) {
19
+ res.setHeader("Access-Control-Allow-Origin", origin);
20
+ res.setHeader("Vary", "Origin");
21
+ } else {
22
+ res.setHeader("Access-Control-Allow-Origin", "https://www.postbuild.dev");
23
+ }
24
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
25
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Admin-Secret");
26
+ }
27
+
28
+ export function extractBearerToken(req, body = null) {
29
+ const header = req.headers?.authorization || req.headers?.Authorization || "";
30
+ if (typeof header === "string" && header.startsWith("Bearer ")) {
31
+ return header.slice(7).trim();
32
+ }
33
+ if (body && typeof body === "object" && typeof body.token === "string") {
34
+ return body.token.trim();
35
+ }
36
+ return "";
37
+ }
38
+
39
+ export function requireLicense(req, body = null) {
40
+ const token = extractBearerToken(req, body);
41
+ if (!token) return null;
42
+ try {
43
+ const verified = verifyLicenseToken(token);
44
+ return verified && verified.valid && verified.email ? { ...verified, token } : null;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ export async function requireLicenseAsync(req, body = null) {
51
+ const verified = requireLicense(req, body);
52
+ if (!verified) return null;
53
+ const remoteStatus = await checkStripeCustomerStatusCached(verified);
54
+ if (remoteStatus !== "active") {
55
+ return null;
56
+ }
57
+ return verified;
58
+ }
59
+
60
+ export function isValidProjectDomain(rawDomain = "", allowPostbuildOperator = false) {
61
+ const clean = String(rawDomain || "")
62
+ .toLowerCase()
63
+ .trim()
64
+ .replace(/^https?:\/\//, "")
65
+ .replace(/^(www\.)+/, "")
66
+ .split("/")[0]
67
+ .split("?")[0]
68
+ .split("#")[0]
69
+ .split(":")[0];
70
+
71
+ if (!clean || !clean.includes(".") || clean.length < 4 || clean.length > 253) {
72
+ return { valid: false, domain: "" };
73
+ }
74
+ if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/.test(clean)) {
75
+ return { valid: false, domain: "" };
76
+ }
77
+ const labels = clean.split(".");
78
+ const tld = labels[labels.length - 1];
79
+ if (tld.length < 2 || /^\d+$/.test(tld)) {
80
+ return { valid: false, domain: "" };
81
+ }
82
+ if (
83
+ clean === "example.com" ||
84
+ clean.endsWith(".invalid") ||
85
+ clean.endsWith(".localhost") ||
86
+ clean.endsWith(".local") ||
87
+ clean.endsWith(".internal")
88
+ ) {
89
+ return { valid: false, domain: "" };
90
+ }
91
+ if (!allowPostbuildOperator && (clean === "postbuild.dev" || clean.endsWith(".postbuild.dev"))) {
92
+ return { valid: false, domain: "" };
93
+ }
94
+ return { valid: true, domain: clean };
95
+ }
96
+
97
+ export function requireAdmin(req) {
98
+ const givenStr = String(req.headers?.["x-admin-secret"] || "").trim();
99
+ const expectedStr = String(process.env.POSTBUILD_ADMIN_SECRET || "").trim();
100
+ if (expectedStr.length < 32 || givenStr.length !== expectedStr.length) {
101
+ return false;
102
+ }
103
+ const given = Buffer.from(givenStr, "utf8");
104
+ const expected = Buffer.from(expectedStr, "utf8");
105
+ return crypto.timingSafeEqual(given, expected);
106
+ }
107
+
108
+ /**
109
+ * Verifies that the authenticated license holder owns the requested inbox and domain.
110
+ * Every license is strictly bound to its own verified domain and inbox.
111
+ */
112
+ export function isInboxOwnedByLicense(verified, requestedInbox = "", requestedDomain = "") {
113
+ if (!verified || !verified.valid) return false;
114
+
115
+ const licenseDomain = String(verified.domain || "")
116
+ .toLowerCase()
117
+ .trim()
118
+ .replace(/^https?:\/\//, "")
119
+ .replace(/^www\./, "")
120
+ .split("/")[0]
121
+ .split(":")[0];
122
+ const licenseEmail = String(verified.email || "").toLowerCase().trim();
123
+ if (!licenseDomain || !licenseDomain.includes(".")) return false;
124
+
125
+ const isPostbuildOperator = getFounderEmailsSet().has(licenseEmail);
126
+
127
+ // Only an explicit operator may access the postbuild.dev inbox
128
+ if (licenseDomain === "postbuild.dev" && !isPostbuildOperator) {
129
+ return false;
130
+ }
131
+
132
+ if (requestedDomain) {
133
+ const cleanReqDomain = String(requestedDomain)
134
+ .toLowerCase()
135
+ .trim()
136
+ .replace(/^https?:\/\//, "")
137
+ .replace(/^www\./, "")
138
+ .split("/")[0]
139
+ .split(":")[0];
140
+ if (!cleanReqDomain || cleanReqDomain !== licenseDomain) {
141
+ return false;
142
+ }
143
+ }
144
+
145
+ const targetInbox = normalizeInboxInput(requestedInbox || requestedDomain || verified.inbox || licenseDomain);
146
+ if (!targetInbox || targetInbox === "unassigned-bot@postbuild.dev") return false;
147
+ if (targetInbox === "postbuild-bot@postbuild.dev" && !isPostbuildOperator) return false;
148
+
149
+ const expectedInboxWithEmail = getBotInboxForDomain(licenseDomain, licenseEmail);
150
+ const expectedInboxDomainOnly = getBotInboxForDomain(licenseDomain);
151
+
152
+ return (
153
+ targetInbox === verified.inbox ||
154
+ targetInbox === expectedInboxWithEmail ||
155
+ targetInbox === expectedInboxDomainOnly
156
+ );
157
+ }
158
+
159
+ export function getClientIp(req) {
160
+ const xff = req.headers?.["x-forwarded-for"];
161
+ if (typeof xff === "string" && xff.trim()) {
162
+ return xff.split(",")[0].trim();
163
+ }
164
+ return req.socket?.remoteAddress || "127.0.0.1";
165
+ }
166
+
167
+ export function checkRateLimit(req, bucket = "default", limitPerMinute = 20) {
168
+ if (process.env.POSTBUILD_DISABLE_RATE_LIMIT === "1") {
169
+ return { allowed: true, remaining: limitPerMinute };
170
+ }
171
+ const ip = getClientIp(req);
172
+ const key = `${bucket}:${ip}`;
173
+ const now = Date.now();
174
+ const windowMs = 60 * 1000;
175
+
176
+ let entry = rateLimitBuckets.get(key);
177
+ if (!entry || now >= entry.resetAt) {
178
+ entry = { count: 0, resetAt: now + windowMs };
179
+ rateLimitBuckets.set(key, entry);
180
+ }
181
+
182
+ entry.count += 1;
183
+ if (entry.count > limitPerMinute) {
184
+ return {
185
+ allowed: false,
186
+ remaining: 0,
187
+ retryAfterSeconds: Math.max(1, Math.ceil((entry.resetAt - now) / 1000))
188
+ };
189
+ }
190
+
191
+ return {
192
+ allowed: true,
193
+ remaining: Math.max(0, limitPerMinute - entry.count),
194
+ retryAfterSeconds: 0
195
+ };
196
+ }
197
+
198
+ export function resetRateLimitsForTests() {
199
+ rateLimitBuckets.clear();
200
+ }
package/lib/inbox.js CHANGED
@@ -232,7 +232,7 @@ async function syncFromResendReceivingApi(targetInbox = "") {
232
232
  const normalizedTarget = targetInbox.trim().toLowerCase();
233
233
 
234
234
  try {
235
- const resp = await fetch("https://api.resend.com/emails/receiving?limit=25", {
235
+ const resp = await fetch("https://api.resend.com/emails/receiving?limit=100", {
236
236
  headers: { Authorization: `Bearer ${resendApiKey}` },
237
237
  signal: AbortSignal.timeout(3500)
238
238
  });
@@ -241,15 +241,16 @@ async function syncFromResendReceivingApi(targetInbox = "") {
241
241
  const data = await resp.json();
242
242
  const items = Array.isArray(data?.data) ? data.data : Array.isArray(data) ? data : [];
243
243
 
244
- for (const item of items.slice(0, 15)) {
245
- const id = item.id || item.email_id;
246
- if (!id || memoryInboxMessages.has(id)) continue;
247
-
244
+ const matchingItems = items.filter((item) => {
248
245
  const recipients = (Array.isArray(item.to) ? item.to : [item.to])
249
246
  .filter(Boolean)
250
247
  .map((r) => String(r).replace(/^.*<([^>]+)>.*$/, "$1").trim().toLowerCase());
248
+ return recipients.includes(normalizedTarget);
249
+ });
251
250
 
252
- if (!recipients.includes(normalizedTarget)) continue;
251
+ for (const item of matchingItems.slice(0, 15)) {
252
+ const id = item.id || item.email_id;
253
+ if (!id || memoryInboxMessages.has(id)) continue;
253
254
 
254
255
  let fullItem = item;
255
256
  if (!item.text && !item.html) {
package/lib/mcp.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Postbuild Stateful MCP Server & Obsidian VaultOS Sync Engine
3
- * Powered by Postbuild MCP Middleware (postbuild-mcp v2.1.0)
3
+ * Powered by Postbuild MCP Middleware (postbuild-mcp v2.1.6)
4
4
  */
5
5
 
6
6
  import fs from "node:fs";
@@ -8,6 +8,7 @@ import path from "node:path";
8
8
  import readline from "node:readline";
9
9
  import { scanTargetUrl, searchBuyerIntentRadar, dispatchDirectories, DIRECTORIES } from "./engine.js";
10
10
  import { verifyCliLicense } from "./auth.js";
11
+ import { isInboxOwnedByLicense } from "./guard.js";
11
12
  import { applyLocalSeoFixes } from "./fixer.js";
12
13
  import { listInboxMessages, sendTestVerificationEmail, getBotInboxForDomain, markMessageVerified } from "./inbox.js";
13
14
  import { withStatefulMcp, createGenericEntityAdapter, spotlightUntrustedText } from "./stateful-mcp.js";
@@ -238,7 +239,7 @@ export async function runStatefulMcpSimulation({
238
239
  });
239
240
 
240
241
  return {
241
- server: "postbuild-mcp@2.1.0",
242
+ server: "postbuild-mcp@2.1.6",
242
243
  domain: state.domain,
243
244
  readinessScore: report.readinessScore,
244
245
  botEmail: report.botEmail,
@@ -317,9 +318,9 @@ export async function syncPostbuildToVault({
317
318
 
318
319
  const telemetryPayload = {
319
320
  generatedAt: new Date().toISOString(),
320
- engine: "postbuild-mcp@2.1.0",
321
+ engine: "postbuild-mcp@2.1.6",
321
322
  mcpTelemetry: {
322
- server: "postbuild-mcp@2.1.0",
323
+ server: "postbuild-mcp@2.1.6",
323
324
  tokenReductionPct: projectsData[0]?.mcpReductionPct || "-75.4%",
324
325
  supersetCacheHits: mcpStats.supersetCacheHits,
325
326
  deltasEmitted: mcpStats.deltasEmitted,
@@ -424,7 +425,7 @@ try {
424
425
 
425
426
  const projects = pb && pb.projects ? pb.projects : [];
426
427
  const radar = pb && pb.radarThreads ? pb.radarThreads : [];
427
- const mcp = pb && pb.mcpTelemetry ? pb.mcpTelemetry : { server: 'postbuild-mcp@2.1.0', tokenReductionPct: '-75.4%', supersetCacheHits: 4 };
428
+ const mcp = pb && pb.mcpTelemetry ? pb.mcpTelemetry : { server: 'postbuild-mcp@2.1.6', tokenReductionPct: '-75.4%', supersetCacheHits: 4 };
428
429
 
429
430
  root.innerHTML = \`
430
431
  <div class="meta-bar">
@@ -865,7 +866,7 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
865
866
  }
866
867
 
867
868
  if (name === "postbuild_inbox") {
868
- const license = await verifyCliLicense({ domain: "postbuild.dev", token: authToken });
869
+ const license = await verifyCliLicense({ domain: "", token: authToken });
869
870
  if (!license.valid) {
870
871
  return {
871
872
  isError: true,
@@ -876,7 +877,16 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
876
877
  };
877
878
  }
878
879
 
879
- const inboxAddr = args.inbox || license.inbox || getBotInboxForDomain(license.domain || "postbuild.dev", license.email);
880
+ const defaultInbox = license.inbox || getBotInboxForDomain(license.domain || "postbuild.dev", license.email);
881
+ const inboxAddr = args.inbox || defaultInbox;
882
+ if (args.inbox && !isInboxOwnedByLicense({ email: license.email, domain: license.domain, inbox: defaultInbox }, inboxAddr, "")) {
883
+ return {
884
+ isError: true,
885
+ status: "FORBIDDEN_INBOX",
886
+ message: `Your license covers ${defaultInbox}. You cannot read ${inboxAddr}.`
887
+ };
888
+ }
889
+
880
890
  const autoVerify = Boolean(args.auto_verify);
881
891
  const result = await autoVerifyInboxLinks(inboxAddr, {
882
892
  autoVerify,
@@ -919,7 +929,7 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
919
929
  }
920
930
 
921
931
  if (name === "postbuild_vault_sync") {
922
- const license = await verifyCliLicense({ domain: "postbuild.dev", token: authToken });
932
+ const license = await verifyCliLicense({ domain: "", token: authToken });
923
933
  if (!license.valid) {
924
934
  return {
925
935
  isError: true,
@@ -932,7 +942,7 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
932
942
 
933
943
  return await syncPostbuildToVault({
934
944
  vaultPath: args.vault_path || process.cwd(),
935
- urls: Array.isArray(args.urls) && args.urls.length ? args.urls : ["https://www.postbuild.dev"],
945
+ urls: Array.isArray(args.urls) && args.urls.length ? args.urls : [license.domain ? `https://${license.domain}` : "https://www.postbuild.dev"],
936
946
  sessionId,
937
947
  bearerToken: license.token
938
948
  });
@@ -975,7 +985,7 @@ export function startPostbuildMcpServer() {
975
985
  },
976
986
  serverInfo: {
977
987
  name: "postbuild-mcp",
978
- version: "2.1.0"
988
+ version: "2.1.6"
979
989
  }
980
990
  }
981
991
  });
package/lib/pseo.js CHANGED
@@ -68,19 +68,19 @@ function renderTopicSpecificSection(pageSlug, cleanName, domain) {
68
68
  <td><strong>Free Audit &amp; Preview</strong></td>
69
69
  <td><strong>$0</strong></td>
70
70
  <td>Any public URL</td>
71
- <td>8-point SEO &amp; AI crawler readiness scan, copy-paste <code>robots.txt</code> / <code>llms.txt</code> / JSON-LD snippets, and 5-directory preview.</td>
71
+ <td>8-point SEO &amp; AI crawler readiness scan, copy-paste <code>robots.txt</code> / <code>llms.txt</code> / JSON-LD snippets, Auto-Fill Bookmarklet, and 5-directory preview.</td>
72
72
  </tr>
73
73
  <tr>
74
74
  <td><strong>Launch Pass</strong> <span class="pill emerald" style="margin-left:6px;">Recommended</span></td>
75
75
  <td><strong>$29</strong> (one-time)</td>
76
76
  <td>1 project domain</td>
77
- <td>All 85 prefilled directory launch links + CSV export, 25 static HTML SEO route generators (<code>npx git-paid fix</code>), dedicated <code>&lt;domain&gt;-bot@postbuild.dev</code> verification inbox (30-day retention), and CLI/MCP key.</td>
77
+ <td>All 85 prefilled directory launch links + CSV export, 1-click <code>.zip</code> Launch Bundle with 25 static HTML SEO routes (<code>npx git-paid fix</code>), dedicated <code>&lt;domain&gt;-bot@postbuild.dev</code> verification inbox (30-day retention), and CLI/MCP key.</td>
78
78
  </tr>
79
79
  <tr>
80
- <td><strong>Studio</strong></td>
81
- <td><strong>$49 / month</strong></td>
82
- <td>Multi-project</td>
83
- <td>Everything in Launch Pass across multiple domains, IndexNow &amp; WebSub batch pinging, and Stripe Customer Portal billing management.</td>
80
+ <td><strong>Done-For-You &amp; Studio</strong></td>
81
+ <td><strong>Waitlist</strong></td>
82
+ <td>Multi-project / Concierge</td>
83
+ <td>Done-for-you manual &amp; CAPTCHA directory submissions, weekly AI search visibility checks, and Buyer Intent Radar alerts (contact <code>support@postbuild.dev</code>).</td>
84
84
  </tr>
85
85
  </tbody>
86
86
  </table>
@@ -91,39 +91,39 @@ function renderTopicSpecificSection(pageSlug, cleanName, domain) {
91
91
  if (pageSlug.includes("roi-case-study")) {
92
92
  return `
93
93
  <section class="card" style="margin-bottom:28px;">
94
- <h2>Dogfooding Case Study: Launching <code>www.postbuild.dev</code> with <code>npx git-paid</code></h2>
94
+ <h2>Live Self-Audit Walkthrough: How <code>www.postbuild.dev</code> Uses <code>npx git-paid</code></h2>
95
95
  <p style="font-size:14px;color:var(--muted);margin-top:4px;">
96
- We built and launched <code>www.postbuild.dev</code> using our own CLI and MCP server. Here is the exact baseline vs. post-build output measured on our own repository:
96
+ Every deliverable generated for <code>www.postbuild.dev</code> is publicly deployed and verifiable via live HTTP inspection:
97
97
  </p>
98
- <div class="table-wrap" role="region" aria-label="Dogfooding launch metrics" tabindex="0">
98
+ <div class="table-wrap" role="region" aria-label="Live self-audit verification" tabindex="0">
99
99
  <table>
100
100
  <thead>
101
101
  <tr>
102
102
  <th>Deliverable</th>
103
- <th>Before <code>npx git-paid fix</code></th>
104
- <th>After <code>npx git-paid fix</code></th>
103
+ <th>Without Postbuild</th>
104
+ <th>Live on <code>www.postbuild.dev</code> (Verifiable Now)</th>
105
105
  </tr>
106
106
  </thead>
107
107
  <tbody>
108
108
  <tr>
109
- <td><strong>Technical Readiness Score</strong></td>
110
- <td>38 / 100 (missing <code>llms.txt</code>, <code>sitemap.xml</code>, and AI crawler rules)</td>
111
- <td>100 / 100 (all 8 SEO, JSON-LD, OpenGraph, and telemetry checks passing)</td>
109
+ <td><strong>8-Point Technical Audit</strong></td>
110
+ <td>Manual inspection of headers, schema, and crawler rules</td>
111
+ <td><strong>100 / 100</strong> across all 8 checks (run <code>npx git-paid https://www.postbuild.dev</code> to verify live)</td>
112
112
  </tr>
113
113
  <tr>
114
114
  <td><strong>Static SEO Landing Pages</strong></td>
115
- <td>1 route (<code>/</code> only)</td>
116
- <td>26 routes indexed in <code>sitemap.xml</code> (<code>/</code> + 25 pre-rendered guides &amp; comparisons)</td>
115
+ <td>Single-page app root (<code>/</code>) only</td>
116
+ <td>26 routes indexed in <a href="/sitemap.xml"><code>/sitemap.xml</code></a> (<code>/</code> + 25 pre-rendered static HTML guides)</td>
117
117
  </tr>
118
118
  <tr>
119
- <td><strong>Directory Submission Prep</strong></td>
120
- <td>Manual spreadsheet tracking</td>
121
- <td>85 prefilled submission URLs with UTM parameters + dedicated <code>postbuild-bot@postbuild.dev</code> inbox</td>
119
+ <td><strong>AI Search Crawler Manifest</strong></td>
120
+ <td>No <code>llms.txt</code> or explicit AI bot rules</td>
121
+ <td>Live <a href="/llms.txt"><code>/llms.txt</code></a> and <a href="/robots.txt"><code>/robots.txt</code></a> allowing <code>OAI-SearchBot</code>, <code>PerplexityBot</code>, and <code>ClaudeBot</code></td>
122
122
  </tr>
123
123
  <tr>
124
- <td><strong>Setup Time</strong></td>
125
- <td>~28 hours estimated manual work</td>
126
- <td>Under 5 minutes via CLI + Vercel deploy</td>
124
+ <td><strong>Directory Launch Queue</strong></td>
125
+ <td>Manual spreadsheet copy-pasting &amp; personal inbox clutter</td>
126
+ <td>85 curated directories ranked by DR with prefilled UTM links + dedicated <code>postbuild-bot@postbuild.dev</code> inbox</td>
127
127
  </tr>
128
128
  </tbody>
129
129
  </table>
@@ -151,7 +151,7 @@ function renderTopicSpecificSection(pageSlug, cleanName, domain) {
151
151
  <tbody>
152
152
  <tr>
153
153
  <td><strong>Manual Spreadsheets</strong></td>
154
- <td>$0 (14–28 hrs founder time)</td>
154
+ <td>$0 (manual form entry)</td>
155
155
  <td>Clutters personal email</td>
156
156
  <td>Manual authoring required</td>
157
157
  </tr>
@@ -192,22 +192,28 @@ export function renderSinglePseoPage(page, allPages = [], options = {}) {
192
192
  const relatedPages = allPages.filter((p) => p.path !== page.path).slice(0, 8);
193
193
  const topicSection = renderTopicSpecificSection(pageSlug, cleanName, domain);
194
194
 
195
+ const softwareAppNode = {
196
+ "@type": "SoftwareApplication",
197
+ name: cleanName,
198
+ url: origin,
199
+ applicationCategory: "DeveloperApplication",
200
+ operatingSystem: "Web, macOS, Windows, Linux",
201
+ description: metaDescription,
202
+ ...(domain === "postbuild.dev"
203
+ ? {
204
+ offers: {
205
+ "@type": "Offer",
206
+ price: "29.00",
207
+ priceCurrency: "USD"
208
+ }
209
+ }
210
+ : {})
211
+ };
212
+
195
213
  const jsonLd = {
196
214
  "@context": "https://schema.org",
197
215
  "@graph": [
198
- {
199
- "@type": "SoftwareApplication",
200
- name: cleanName,
201
- url: origin,
202
- applicationCategory: "DeveloperApplication",
203
- operatingSystem: "Web, macOS, Windows, Linux",
204
- description: metaDescription,
205
- offers: {
206
- "@type": "Offer",
207
- price: "29.00",
208
- priceCurrency: "USD"
209
- }
210
- },
216
+ softwareAppNode,
211
217
  {
212
218
  "@type": "BreadcrumbList",
213
219
  itemListElement: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-paid",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
4
4
  "description": "git push ships your code. git paid sells it. Autonomous SEO, Paywall, MCP & Directory Launch Agent (www.postbuild.dev).",
5
5
  "type": "module",
6
6
  "main": "bin/git-paid.js",
@@ -13,6 +13,7 @@
13
13
  "lib/directories.js",
14
14
  "lib/engine.js",
15
15
  "lib/fixer.js",
16
+ "lib/guard.js",
16
17
  "lib/inbox.js",
17
18
  "lib/mcp.js",
18
19
  "lib/pseo.js",