git-paid 2.1.2 → 2.1.3

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
@@ -3,7 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
5
5
  import { scanTargetUrl } from "../lib/engine.js";
6
- import { authenticateUser } from "../lib/auth.js";
6
+ import { verifyCliLicense } from "../lib/auth.js";
7
7
  import { applyLocalSeoFixes } from "../lib/fixer.js";
8
8
  import { sendTestVerificationEmail } from "../lib/inbox.js";
9
9
  import {
@@ -25,6 +25,14 @@ if (subcmd === "mcp" || args.includes("--mcp")) {
25
25
  await runCli();
26
26
  }
27
27
 
28
+ function printProGateBanner(licenseCheck, featureName) {
29
+ console.log(` [LOCKED] ${featureName} requires an active postbuild Launch Pass ($29) or Studio ($49/mo) license.`);
30
+ console.log(` [UNLOCK] Instant Stripe Checkout ($29 Launch Pass):`);
31
+ console.log(` ${licenseCheck.checkoutUrl}\n`);
32
+ console.log(` [LOGIN] Already purchased? Authenticate your CLI & MCP server:`);
33
+ console.log(` npx git-paid login <your-pb_live_key>\n`);
34
+ }
35
+
28
36
  async function runCli() {
29
37
  console.log("\n========================================================");
30
38
  console.log(" postbuild (postbuild.dev) — `git paid` CLI & MCP");
@@ -33,19 +41,81 @@ async function runCli() {
33
41
 
34
42
  if (subcmd === "--help" || subcmd === "-h" || subcmd === "help") {
35
43
  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)");
44
+ 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");
46
+ console.log(" git paid fix [url] [PRO] Write 25 pSEO HTML pages, robots.txt, sitemap.xml & llms.txt");
47
+ console.log(" git paid submit [url] [PRO] Headless 85-directory form submitter + OTP auto-verifier");
48
+ console.log(" git paid inbox [bot-email] [PRO] Check & auto-verify <slug>-bot@postbuild.dev links & OTPs");
49
+ console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.3)");
41
50
  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");
51
+ console.log(" git paid vault [path] [PRO] Sync live telemetry & Bento cockpit to Obsidian VaultOS\n");
52
+ process.exit(0);
53
+ }
54
+
55
+ if (subcmd === "login") {
56
+ const input = (args[1] || "").trim();
57
+ if (!input) {
58
+ console.log(" Usage: npx git-paid login <pb_live_key | cs_live_session_id | email>\n");
59
+ process.exit(1);
60
+ }
61
+
62
+ const payload = input.startsWith("pb_live_")
63
+ ? { token: input }
64
+ : input.startsWith("cs_")
65
+ ? { sessionId: input }
66
+ : { email: input };
67
+
68
+ let auth = null;
69
+ try {
70
+ const resp = await fetch("https://www.postbuild.dev/api/auth", {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ body: JSON.stringify(payload),
74
+ signal: AbortSignal.timeout(8000)
75
+ });
76
+ auth = await resp.json();
77
+ } catch (err) {
78
+ console.log(` [!] Could not reach https://www.postbuild.dev/api/auth: ${err.message}\n`);
79
+ process.exit(1);
80
+ }
81
+
82
+ if (auth && auth.requiresOtp) {
83
+ console.log(` [*] ${auth.message}`);
84
+ console.log(` [*] Verify on https://www.postbuild.dev/?tab=account or use your pb_live_... key from the email.\n`);
85
+ process.exit(0);
86
+ }
87
+
88
+ if (!auth || !auth.ok || !auth.token) {
89
+ console.log(` [!] Login failed: ${(auth && auth.error) || "Invalid license key."}\n`);
90
+ process.exit(1);
91
+ }
92
+
93
+ const globalDir = path.join(os.homedir(), ".postbuild");
94
+ fs.mkdirSync(globalDir, { recursive: true });
95
+ fs.writeFileSync(
96
+ path.join(globalDir, "credentials.json"),
97
+ JSON.stringify(
98
+ { email: auth.email, plan: auth.plan, token: auth.token, updatedAt: new Date().toISOString() },
99
+ null,
100
+ 2
101
+ ),
102
+ "utf8"
103
+ );
104
+ console.log(` [+] Authenticated as: ${auth.email}`);
105
+ console.log(` [+] Active Plan: ${auth.plan}`);
106
+ console.log(` [+] License Key: ${auth.token.slice(0, 28)}...`);
107
+ console.log(` [ok] Saved credentials to ~/.postbuild/credentials.json\n`);
44
108
  process.exit(0);
45
109
  }
46
110
 
47
111
  if (subcmd === "submit") {
48
112
  const targetUrl = args.find((a, i) => i > 0 && !a.startsWith("--")) || "https://postbuild.dev";
113
+ const license = await verifyCliLicense({ domain: targetUrl.replace(/^https?:\/\//, "") });
114
+ if (!license.valid) {
115
+ printProGateBanner(license, "85-Directory Headless Submitter & @postbuild.dev Auto-Verifier");
116
+ process.exit(1);
117
+ }
118
+
49
119
  console.log(` [*] Running Headless Directory Form Submitter for: ${targetUrl} ...`);
50
120
  const submitRes = await runHeadlessDirectorySubmit({
51
121
  targetUrl,
@@ -77,10 +147,10 @@ async function runCli() {
77
147
  process.exit(0);
78
148
  }
79
149
 
80
- if (subcmd === "init-agent" || subcmd === "agent") {
150
+ if (subcmd === "init-agent" || subcmd === "agent" || subcmd === "skill") {
81
151
  const res = installAgentSkillFiles({ cwd: process.cwd() });
82
152
  console.log(` [+] Scaffolded Agent Skill: ${res.skillMdPath}`);
83
- console.log(` [+] Powered by: postbuild-mcp@2.1.0 (Stateful Delta Compaction)\n`);
153
+ console.log(` [+] Powered by: postbuild-mcp@2.1.3 (Stateful Delta Compaction)\n`);
84
154
  console.log(" Add this to your Claude Code / Cursor / Antigravity MCP config:\n");
85
155
  console.log(JSON.stringify(res.mcpConfigSnippet, null, 2));
86
156
  console.log("");
@@ -88,6 +158,12 @@ async function runCli() {
88
158
  }
89
159
 
90
160
  if (subcmd === "vault" || subcmd === "vaultos") {
161
+ const license = await verifyCliLicense({ domain: "postbuild.dev" });
162
+ if (!license.valid) {
163
+ printProGateBanner(license, "Obsidian VaultOS Telemetry & Bento Dashboard Sync");
164
+ process.exit(1);
165
+ }
166
+
91
167
  const vaultArg =
92
168
  args.find((a, i) => i > 0 && !a.startsWith("--") && !/^https?:\/\//i.test(a)) ||
93
169
  "C:\\Users\\Bennett\\Bennett Labs";
@@ -123,39 +199,14 @@ async function runCli() {
123
199
  process.exit(0);
124
200
  }
125
201
 
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`);
202
+ 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" });
205
+ if (!license.valid) {
206
+ printProGateBanner(license, "Dedicated @postbuild.dev Verification Inbox & OTP Auto-Verifier");
137
207
  process.exit(1);
138
208
  }
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
209
 
157
- if (subcmd === "inbox") {
158
- const inboxAddr = args.find((a, i) => i > 0 && !a.startsWith("--")) || "postbuild-bot@postbuild.dev";
159
210
  if (args.includes("--test")) {
160
211
  await sendTestVerificationEmail(inboxAddr, "postbuild.dev");
161
212
  }
@@ -185,24 +236,48 @@ async function runCli() {
185
236
  console.log(` [*] Scanning Target: ${targetArg} ...`);
186
237
 
187
238
  const report = await scanTargetUrl(targetArg);
239
+ const license = await verifyCliLicense({ domain: report.domain });
188
240
 
189
- console.log(` [+] Provisioned Bot Inbox: ${report.botEmail}`);
241
+ console.log(` [+] Provisioned Bot Inbox: ${report.botEmail} ${license.valid ? "[ACTIVE]" : "[LOCKED — $29 Launch Pass]"}`);
190
242
  console.log(` [+] Readiness Score: ${report.readinessScore} / 100`);
191
243
  console.log(` [+] Auto-SEO Pages Ready: ${report.pseoPages.length} high-intent routes`);
192
244
  console.log(` [+] Directory Launch Queue: ${report.directoryCount} AI, Dev & Startup Directories\n`);
193
245
 
194
- console.log(" --- Audit Signals ---");
246
+ console.log(" --- 8-Point Technical & Monetization Audit ---");
195
247
  for (const c of report.checks) {
196
248
  const icon = c.passed ? "[PASS]" : "[WARN]";
197
249
  console.log(` ${icon} ${c.label.padEnd(40)} -> ${c.detail}`);
198
250
  }
199
251
 
252
+ if (isFixMode && !license.valid) {
253
+ console.log("");
254
+ printProGateBanner(license, "Auto-Fix Bundle (25 pSEO HTML Pages, llms.txt, sitemap.xml & 85-Directory CSV)");
255
+ process.exit(1);
256
+ }
257
+
258
+ if (!license.valid) {
259
+ console.log("\n --- Directory Launch Queue Preview (First 5 of 85) ---");
260
+ for (const d of report.directories.slice(0, 5)) {
261
+ console.log(` [PREVIEW] ${d.name.padEnd(26)} (DR ${d.dr}) -> ${d.category}`);
262
+ }
263
+ console.log(` ... +80 additional high-DR directories locked.\n`);
264
+
265
+ console.log(" --- Programmatic SEO Route Preview (First 3 of 25) ---");
266
+ for (const p of report.pseoPages.slice(0, 3)) {
267
+ console.log(` [PREVIEW] ${p.path.padEnd(42)} (${p.volume}, ${p.kd})`);
268
+ }
269
+ console.log(` ... +22 additional Programmatic SEO HTML pages locked.\n`);
270
+
271
+ printProGateBanner(license, "Full 25-Page pSEO Generator, 85-Directory Dispatch & @postbuild.dev Inbox");
272
+ process.exit(0);
273
+ }
274
+
200
275
  const fixResult = applyLocalSeoFixes(report, {
201
276
  cwd: process.cwd(),
202
277
  overwritePublic: isFixMode || args.includes("--force")
203
278
  });
204
279
 
205
- console.log(`\n --- Local Repository Auto-Fix (${fixResult.framework}) ---`);
280
+ console.log(`\n --- Local Repository Auto-Fix (${fixResult.framework} — Licensed to ${license.email}) ---`);
206
281
  for (const f of fixResult.writtenFiles) {
207
282
  console.log(` [WRITE] ${f.path.padEnd(36)} (${f.role})`);
208
283
  }
package/lib/auth.js CHANGED
@@ -1,9 +1,83 @@
1
1
  // Zero-DB Auth, Email OTP via Resend & Cryptographic License Verification for postbuild (postbuild.dev)
2
2
 
3
3
  import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import os from "node:os";
4
7
  import { getStripeClient } from "./stripe.js";
5
8
 
6
- const SECRET = process.env.POSTBUILD_AUTH_SECRET || process.env.STRIPE_WEBHOOK_SECRET || "postbuild-dev-secret-key-2026";
9
+ const SECRET =
10
+ process.env.POSTBUILD_AUTH_SECRET ||
11
+ process.env.STRIPE_WEBHOOK_SECRET ||
12
+ process.env.STRIPE_RESTRICTED_KEY ||
13
+ process.env.STRIPE_SECRET_KEY ||
14
+ "postbuild-dev-secret-key-2026";
15
+
16
+ export function loadLocalCredentials() {
17
+ if (process.env.POSTBUILD_LICENSE_KEY) {
18
+ return { token: process.env.POSTBUILD_LICENSE_KEY.trim(), email: "cli@postbuild.dev", plan: "launch_pass" };
19
+ }
20
+ try {
21
+ const credPath = path.join(os.homedir(), ".postbuild", "credentials.json");
22
+ if (fs.existsSync(credPath)) {
23
+ const data = JSON.parse(fs.readFileSync(credPath, "utf8"));
24
+ if (data && data.token) return data;
25
+ }
26
+ } catch {}
27
+ return null;
28
+ }
29
+
30
+ export async function verifyCliLicense({ domain = "postbuild.dev" } = {}) {
31
+ // Always allow internal server-side execution on Vercel
32
+ if (process.env.VERCEL || process.env.STRIPE_RESTRICTED_KEY || process.env.STRIPE_SECRET_KEY) {
33
+ return { valid: true, email: "server@postbuild.dev", plan: "studio_monthly" };
34
+ }
35
+
36
+ const creds = loadLocalCredentials();
37
+ if (creds && creds.token && creds.token.startsWith("pb_live_")) {
38
+ try {
39
+ const resp = await fetch("https://www.postbuild.dev/api/auth", {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: JSON.stringify({ token: creds.token }),
43
+ signal: AbortSignal.timeout(5000)
44
+ });
45
+ if (resp.ok) {
46
+ const data = await resp.json();
47
+ if (data && data.ok && data.authenticated) {
48
+ return { valid: true, email: data.email, plan: data.plan, token: creds.token };
49
+ }
50
+ }
51
+ } catch {
52
+ // Offline fallback: verify structural format if network is unreachable
53
+ const parts = creds.token.slice("pb_live_".length).split(".");
54
+ if (parts.length === 2 && parts[0].length > 10 && parts[1].length === 24) {
55
+ return { valid: true, email: creds.email || "offline@postbuild.dev", plan: creds.plan || "launch_pass", token: creds.token };
56
+ }
57
+ }
58
+ }
59
+
60
+ let checkoutUrl = `https://www.postbuild.dev/?domain=${encodeURIComponent(domain)}`;
61
+ try {
62
+ const cResp = await fetch(
63
+ `https://www.postbuild.dev/api/checkout?plan=launch_pass&domain=${encodeURIComponent(domain)}`,
64
+ { signal: AbortSignal.timeout(4500) }
65
+ );
66
+ if (cResp.ok) {
67
+ const cData = await cResp.json();
68
+ if (cData && cData.checkoutUrl) checkoutUrl = cData.checkoutUrl;
69
+ }
70
+ } catch {}
71
+
72
+ return {
73
+ valid: false,
74
+ reason: "PRO_LICENSE_REQUIRED",
75
+ checkoutUrl,
76
+ webUrl: `https://www.postbuild.dev/?domain=${encodeURIComponent(domain)}`,
77
+ message:
78
+ "A 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 auto-verification."
79
+ };
80
+ }
7
81
 
8
82
  export function signLicenseToken({ email, plan = "launch_pass", domain = "*" }) {
9
83
  const payload = {
package/lib/mcp.js CHANGED
@@ -7,6 +7,7 @@ import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import readline from "node:readline";
9
9
  import { scanTargetUrl, searchBuyerIntentRadar, dispatchDirectories, DIRECTORIES } from "./engine.js";
10
+ import { verifyCliLicense } from "./auth.js";
10
11
  import { applyLocalSeoFixes } from "./fixer.js";
11
12
  import { listInboxMessages, sendTestVerificationEmail } from "./inbox.js";
12
13
  import { withStatefulMcp, createGenericEntityAdapter } from "./stateful-mcp.js";
@@ -635,25 +636,37 @@ export const MCP_TOOLS = [
635
636
  export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio-session") {
636
637
  if (name === "postbuild_scan") {
637
638
  const report = await scanTargetUrl(args.url || "https://postbuild.dev");
639
+ const license = await verifyCliLicense({ domain: report.domain });
638
640
  getDomainDirectories(report.targetUrl, report);
639
641
  return {
640
642
  domain: report.domain,
641
643
  targetUrl: report.targetUrl,
642
644
  readinessScore: report.readinessScore,
643
645
  botEmail: report.botEmail,
646
+ licenseStatus: license.valid ? "PRO_ACTIVE" : "FREE_PREVIEW",
644
647
  checks: report.checks,
645
648
  pseoRouteCount: report.pseoPages.length,
646
- directoryCount: report.directoryCount
649
+ directoryCount: report.directoryCount,
650
+ ...(license.valid
651
+ ? {}
652
+ : {
653
+ upgradeNotice: license.message,
654
+ checkoutUrl: license.checkoutUrl,
655
+ loginCommand: "npx git-paid login <your-pb_live_key>"
656
+ })
647
657
  };
648
658
  }
649
659
 
650
660
  if (name === "postbuild_directories") {
651
661
  const targetUrl = args.url || "https://postbuild.dev";
652
662
  const state = getDomainDirectories(targetUrl);
663
+ const license = await verifyCliLicense({ domain: state.domain });
653
664
  const catFilter = (args.category || "").toLowerCase();
654
665
  const statusFilter = (args.status || "").toLowerCase();
655
666
  const minDa = Number(args.min_da) || 0;
656
667
 
668
+ const maxAllowed = license.valid ? Number(args.limit) || 25 : 5;
669
+
657
670
  const records = await mcpDirectories.queryWithSupersetCache({
658
671
  sessionId,
659
672
  cacheKey: `dirs:${state.domain}`,
@@ -662,20 +675,34 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
662
675
  (!catFilter || d.category.toLowerCase().includes(catFilter)) &&
663
676
  (!statusFilter || d.status.toLowerCase().includes(statusFilter)) &&
664
677
  d.dr >= minDa,
665
- limit: Number(args.limit) || 25,
678
+ limit: maxAllowed,
666
679
  bypassCache: Boolean(args.bypass_cache)
667
680
  });
668
681
 
669
- return mcpDirectories.compactList({
682
+ const compacted = mcpDirectories.compactList({
670
683
  sessionId,
671
684
  records,
672
685
  bypassCache: Boolean(args.bypass_cache)
673
686
  });
687
+
688
+ if (!license.valid) {
689
+ return {
690
+ ...compacted,
691
+ _license_status: "PREVIEW_ONLY_5_OF_85",
692
+ _locked_directories: 80,
693
+ _checkout_url: license.checkoutUrl,
694
+ _unlock_instructions: "Purchase a $29 Launch Pass and run: npx git-paid login <pb_live_key>"
695
+ };
696
+ }
697
+ return compacted;
674
698
  }
675
699
 
676
700
  if (name === "postbuild_pseo") {
677
701
  const targetUrl = args.url || "https://postbuild.dev";
702
+ const license = await verifyCliLicense({ domain: targetUrl.replace(/^https?:\/\//, "") });
678
703
  const intentFilter = (args.intent || "").toLowerCase();
704
+ const maxAllowed = license.valid ? 25 : 3;
705
+
679
706
  const records = await mcpPseo.queryWithSupersetCache({
680
707
  sessionId,
681
708
  cacheKey: `pseo:${targetUrl}`,
@@ -688,20 +715,43 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
688
715
  }));
689
716
  },
690
717
  filter: (p) => !intentFilter || p.intent.toLowerCase().includes(intentFilter),
691
- limit: 25,
718
+ limit: maxAllowed,
692
719
  bypassCache: Boolean(args.bypass_cache)
693
720
  });
694
721
 
695
- return mcpPseo.compactList({
722
+ const compacted = mcpPseo.compactList({
696
723
  sessionId,
697
724
  records,
698
725
  bypassCache: Boolean(args.bypass_cache)
699
726
  });
727
+
728
+ if (!license.valid) {
729
+ return {
730
+ ...compacted,
731
+ _license_status: "PREVIEW_ONLY_3_OF_25",
732
+ _locked_routes: 22,
733
+ _checkout_url: license.checkoutUrl,
734
+ _unlock_instructions: "Purchase a $29 Launch Pass and run: npx git-paid login <pb_live_key>"
735
+ };
736
+ }
737
+ return compacted;
700
738
  }
701
739
 
702
740
  if (name === "postbuild_dispatch") {
703
741
  const targetUrl = args.url || "https://postbuild.dev";
704
742
  const state = getDomainDirectories(targetUrl);
743
+ const license = await verifyCliLicense({ domain: state.domain });
744
+ if (!license.valid) {
745
+ return {
746
+ isError: true,
747
+ status: "PRO_LICENSE_REQUIRED",
748
+ domain: state.domain,
749
+ checkoutUrl: license.checkoutUrl,
750
+ message: license.message,
751
+ loginCommand: "npx git-paid login <your-pb_live_key>"
752
+ };
753
+ }
754
+
705
755
  const dryRun = args.dry_run !== undefined ? Boolean(args.dry_run) : true;
706
756
  const targetIds = Array.isArray(args.directory_ids) ? args.directory_ids : [];
707
757
 
@@ -765,6 +815,17 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
765
815
  }
766
816
 
767
817
  if (name === "postbuild_inbox") {
818
+ const license = await verifyCliLicense({ domain: "postbuild.dev" });
819
+ if (!license.valid) {
820
+ return {
821
+ isError: true,
822
+ status: "PRO_LICENSE_REQUIRED",
823
+ checkoutUrl: license.checkoutUrl,
824
+ message: license.message,
825
+ loginCommand: "npx git-paid login <your-pb_live_key>"
826
+ };
827
+ }
828
+
768
829
  const inboxAddr = args.inbox || "postbuild-bot@postbuild.dev";
769
830
  if (args.send_test) {
770
831
  await sendTestVerificationEmail(inboxAddr, "postbuild.dev");
@@ -790,6 +851,18 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
790
851
 
791
852
  if (name === "postbuild_fix") {
792
853
  const report = await scanTargetUrl(args.url || "https://postbuild.dev");
854
+ const license = await verifyCliLicense({ domain: report.domain });
855
+ if (!license.valid) {
856
+ return {
857
+ isError: true,
858
+ status: "PRO_LICENSE_REQUIRED",
859
+ domain: report.domain,
860
+ checkoutUrl: license.checkoutUrl,
861
+ message: license.message,
862
+ loginCommand: "npx git-paid login <your-pb_live_key>"
863
+ };
864
+ }
865
+
793
866
  const fixRes = applyLocalSeoFixes(report, {
794
867
  cwd: args.cwd || process.cwd(),
795
868
  overwritePublic: true
@@ -798,6 +871,17 @@ export async function executeMcpToolCall(name, args = {}, sessionId = "mcp-stdio
798
871
  }
799
872
 
800
873
  if (name === "postbuild_vault_sync") {
874
+ const license = await verifyCliLicense({ domain: "postbuild.dev" });
875
+ if (!license.valid) {
876
+ return {
877
+ isError: true,
878
+ status: "PRO_LICENSE_REQUIRED",
879
+ checkoutUrl: license.checkoutUrl,
880
+ message: license.message,
881
+ loginCommand: "npx git-paid login <your-pb_live_key>"
882
+ };
883
+ }
884
+
801
885
  return await syncPostbuildToVault({
802
886
  vaultPath: args.vault_path || "C:\\Users\\Bennett\\Bennett Labs",
803
887
  urls: Array.isArray(args.urls) && args.urls.length ? args.urls : ["https://postbuild.dev", "https://payarity.com"],
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "git-paid",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "git push ships your code. git paid sells it. Autonomous SEO, Paywall, MCP & Directory Launch Agent (postbuild.dev).",
5
5
  "type": "module",
6
- "main": "server.js",
6
+ "main": "bin/git-paid.js",
7
7
  "bin": {
8
8
  "git-paid": "bin/git-paid.js",
9
9
  "postbuild": "bin/git-paid.js"
package/server.js DELETED
@@ -1,252 +0,0 @@
1
- import http from "node:http";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { scanTargetUrl, searchBuyerIntentRadar, dispatchDirectories } from "./lib/engine.js";
6
- import { createStripeCheckout, createCustomerPortalSession, verifyAndHandleStripeWebhook, configureStripeBrandingAndPortal, getCustomerBillingSummary, cancelCustomerSubscription } from "./lib/stripe.js";
7
- import { authenticateUser, verifyLicenseToken } from "./lib/auth.js";
8
- import { ingestInboundEmail, listInboxMessages, sendTestVerificationEmail } from "./lib/inbox.js";
9
- import { autoVerifyInboxLinks, runStatefulMcpSimulation, executeMcpToolCall } from "./lib/mcp.js";
10
-
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = path.dirname(__filename);
13
- const PORT = Number(process.env.POSTBUILD_PORT) || 8096;
14
-
15
- // Load local .env if present
16
- const envPath = path.join(__dirname, ".env");
17
- if (fs.existsSync(envPath)) {
18
- const envContent = fs.readFileSync(envPath, "utf8");
19
- for (const line of envContent.split(/\r?\n/)) {
20
- const trimmed = line.trim();
21
- if (!trimmed || trimmed.startsWith("#")) continue;
22
- const eqIdx = trimmed.indexOf("=");
23
- if (eqIdx > 0) {
24
- const key = trimmed.slice(0, eqIdx).trim();
25
- const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
26
- if (!process.env[key]) process.env[key] = val;
27
- }
28
- }
29
- }
30
-
31
- const MIME_TYPES = {
32
- ".html": "text/html; charset=utf-8",
33
- ".js": "application/javascript; charset=utf-8",
34
- ".css": "text/css; charset=utf-8",
35
- ".json": "application/json; charset=utf-8",
36
- ".xml": "application/xml; charset=utf-8",
37
- ".txt": "text/plain; charset=utf-8",
38
- ".svg": "image/svg+xml",
39
- ".png": "image/png"
40
- };
41
-
42
- function readRawBuffer(req) {
43
- return new Promise((resolve) => {
44
- const chunks = [];
45
- req.on("data", chunk => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
46
- req.on("end", () => resolve(Buffer.concat(chunks)));
47
- });
48
- }
49
-
50
- async function readJsonBody(req) {
51
- const buf = await readRawBuffer(req);
52
- try {
53
- return buf.length ? JSON.parse(buf.toString("utf8")) : {};
54
- } catch {
55
- return {};
56
- }
57
- }
58
-
59
- const server = http.createServer(async (req, res) => {
60
- try {
61
- const host = req.headers.host || `localhost:${PORT}`;
62
- const parsed = new URL(req.url || "/", `http://${host}`);
63
-
64
- if (parsed.pathname === "/api/scan") {
65
- const target = parsed.searchParams.get("url") || "https://postbuild.dev";
66
- const result = await scanTargetUrl(target);
67
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
68
- res.end(JSON.stringify(result));
69
- return;
70
- }
71
-
72
- if (parsed.pathname === "/api/radar") {
73
- const q = parsed.searchParams.get("q") || "seo marketing indie hacker";
74
- const threads = await searchBuyerIntentRadar(q);
75
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
76
- res.end(JSON.stringify({ query: q, threads }));
77
- return;
78
- }
79
-
80
- if (parsed.pathname === "/api/dispatch") {
81
- const body = req.method === "POST" ? await readJsonBody(req) : {};
82
- const action = body.action || parsed.searchParams.get("action") || "";
83
- const inbox = body.inbox || body.to || parsed.searchParams.get("inbox") || "postbuild-bot@postbuild.dev";
84
- if (action === "inbound_email") {
85
- const msg = await ingestInboundEmail(body);
86
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
87
- res.end(JSON.stringify({ ok: true, message: msg }));
88
- return;
89
- }
90
- if (action === "inbox_test") {
91
- const domain = body.domain || parsed.searchParams.get("domain") || "postbuild.dev";
92
- const msg = await sendTestVerificationEmail(inbox, domain);
93
- const list = await listInboxMessages(inbox, { allowRemoteFallback: false });
94
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
95
- res.end(JSON.stringify({ ok: true, latest: msg, ...list }));
96
- return;
97
- }
98
- if (action === "inbox_autoverify") {
99
- const current = await listInboxMessages(inbox, { allowRemoteFallback: false });
100
- if (current.count === 0) {
101
- const domain = body.domain || parsed.searchParams.get("domain") || "postbuild.dev";
102
- await sendTestVerificationEmail(inbox, domain);
103
- }
104
- const verified = await autoVerifyInboxLinks(inbox);
105
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
106
- res.end(JSON.stringify({ ok: true, ...verified }));
107
- return;
108
- }
109
- if (action === "inbox_list") {
110
- const list = await listInboxMessages(inbox, { allowRemoteFallback: false });
111
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
112
- res.end(JSON.stringify({ ok: true, ...list }));
113
- return;
114
- }
115
- if (action === "mcp_simulate") {
116
- const targetUrl = body.url || body.targetUrl || parsed.searchParams.get("url") || "https://postbuild.dev";
117
- const sim = await runStatefulMcpSimulation({
118
- url: targetUrl,
119
- sessionId: body.sessionId || "local-studio-mcp"
120
- });
121
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
122
- res.end(JSON.stringify({ ok: true, ...sim }));
123
- return;
124
- }
125
- if (action === "mcp_call") {
126
- const targetUrl = body.url || body.targetUrl || parsed.searchParams.get("url") || "https://postbuild.dev";
127
- const toolName = body.tool || parsed.searchParams.get("tool") || "postbuild_directories";
128
- const toolArgs = body.args || { url: targetUrl };
129
- const output = await executeMcpToolCall(toolName, toolArgs, body.sessionId || "local-studio-session");
130
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
131
- res.end(JSON.stringify({ ok: true, tool: toolName, result: output }));
132
- return;
133
- }
134
- const targetUrl = body.url || body.targetUrl || parsed.searchParams.get("url") || "https://postbuild.dev";
135
- const directoryIds = Array.isArray(body.directoryIds) ? body.directoryIds : [];
136
- const result = await dispatchDirectories({ targetUrl, directoryIds });
137
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
138
- res.end(JSON.stringify(result));
139
- return;
140
- }
141
-
142
- if (parsed.pathname === "/api/checkout") {
143
- const body = req.method === "POST" ? await readJsonBody(req) : {};
144
- const planId = body.plan || parsed.searchParams.get("plan") || "launch_pass";
145
- const targetDomain = body.domain || parsed.searchParams.get("domain") || "postbuild.dev";
146
- const customerEmail = body.email || parsed.searchParams.get("email") || "";
147
- const coupon = body.coupon || parsed.searchParams.get("coupon") || "";
148
- const origin = `http://${host}`;
149
- const result = await createStripeCheckout({ planId, targetDomain, customerEmail, coupon, origin });
150
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
151
- res.end(JSON.stringify(result));
152
- return;
153
- }
154
-
155
- if (parsed.pathname === "/api/portal") {
156
- const body = req.method === "POST" ? await readJsonBody(req) : {};
157
- const action = body.action || parsed.searchParams.get("action") || "";
158
- const token = body.token || parsed.searchParams.get("token") || "";
159
- const subscriptionId = body.subscriptionId || parsed.searchParams.get("subscriptionId") || "";
160
- if (action === "setup_branding") {
161
- const iconPath = path.join(__dirname, "public", "brand", "postbuild-icon.png");
162
- const iconBuf = fs.existsSync(iconPath) ? fs.readFileSync(iconPath) : null;
163
- const brandingResult = await configureStripeBrandingAndPortal(iconBuf);
164
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
165
- res.end(JSON.stringify(brandingResult));
166
- return;
167
- }
168
-
169
- const verified = verifyLicenseToken(token);
170
- if (!verified.valid || !verified.email) {
171
- res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
172
- res.end(
173
- JSON.stringify({
174
- ok: false,
175
- error: "Unauthorized: A verified pb_live_ license token is required to view receipts or manage billing."
176
- })
177
- );
178
- return;
179
- }
180
-
181
- if (action === "billing_summary") {
182
- const summary = await getCustomerBillingSummary({ customerEmail: verified.email });
183
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
184
- res.end(JSON.stringify(summary));
185
- return;
186
- }
187
- if (action === "cancel_subscription") {
188
- const cancelled = await cancelCustomerSubscription({
189
- subscriptionId,
190
- customerEmail: verified.email
191
- });
192
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
193
- res.end(JSON.stringify(cancelled));
194
- return;
195
- }
196
- const returnUrl = `http://${host}/?tab=account`;
197
- const result = await createCustomerPortalSession({ customerEmail: verified.email, returnUrl });
198
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
199
- res.end(JSON.stringify(result));
200
- return;
201
- }
202
-
203
- if (parsed.pathname === "/api/webhook" && req.method === "POST") {
204
- const rawBody = await readRawBuffer(req);
205
- const sig = req.headers["stripe-signature"] || "";
206
- const result = await verifyAndHandleStripeWebhook(rawBody, sig);
207
- res.writeHead(result.status || 200, { "Content-Type": "application/json; charset=utf-8" });
208
- res.end(JSON.stringify(result));
209
- return;
210
- }
211
-
212
- if (parsed.pathname === "/api/auth") {
213
- const body = req.method === "POST" ? await readJsonBody(req) : {};
214
- const email = body.email || parsed.searchParams.get("email") || "";
215
- const token = body.token || parsed.searchParams.get("token") || "";
216
- const sessionId = body.sessionId || parsed.searchParams.get("session_id") || "";
217
- const otpCode = body.otpCode || parsed.searchParams.get("otpCode") || "";
218
- const challengeToken = body.challengeToken || parsed.searchParams.get("challengeToken") || "";
219
- const result = await authenticateUser({ email, token, sessionId, otpCode, challengeToken });
220
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
221
- res.end(JSON.stringify(result));
222
- return;
223
- }
224
-
225
- let safePath = parsed.pathname === "/" ? "/index.html" : parsed.pathname;
226
- let filePath = path.join(__dirname, "public", safePath);
227
- if (!fs.existsSync(filePath) && fs.existsSync(`${filePath}.html`)) {
228
- filePath = `${filePath}.html`;
229
- }
230
-
231
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
232
- const ext = path.extname(filePath).toLowerCase();
233
- res.writeHead(200, { "Content-Type": MIME_TYPES[ext] || "application/octet-stream" });
234
- fs.createReadStream(filePath).pipe(res);
235
- return;
236
- }
237
-
238
- // Fallback SPA route
239
- const indexHtml = path.join(__dirname, "public", "index.html");
240
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
241
- fs.createReadStream(indexHtml).pipe(res);
242
- } catch (err) {
243
- res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
244
- res.end(JSON.stringify({ error: err.message }));
245
- }
246
- });
247
-
248
- server.listen(PORT, "0.0.0.0", () => {
249
- console.log(`\n postbuild (postbuild.dev) live at:`);
250
- console.log(` -> Local: http://localhost:${PORT}`);
251
- console.log(` -> Tailscale: http://100.117.7.27:${PORT}\n`);
252
- });