git-paid 2.1.1 → 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/LICENSE ADDED
@@ -0,0 +1,117 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity.
18
+
19
+ "Source" form shall mean the preferred form for making modifications,
20
+ including but not limited to software source code, documentation
21
+ source, and configuration files.
22
+
23
+ "Object" form shall mean any form resulting from mechanical
24
+ transformation or translation of a Source form, including but
25
+ not limited to compiled object code, generated documentation,
26
+ and conversions to other media types.
27
+
28
+ "Work" shall mean the work of authorship, whether in Source or
29
+ Object form, made available under the License, as indicated by a
30
+ copyright notice that is included in or attached to the work.
31
+
32
+ "Derivative Works" shall mean any work, whether in Source or Object
33
+ form, that is based on (or derived from) the Work and for which the
34
+ editorial revisions, annotations, elaborations, or other modifications
35
+ represent, as a whole, an original work of authorship.
36
+
37
+ "Contribution" shall mean any work of authorship, including
38
+ the original version of the Work and any modifications or additions
39
+ to that Work or Derivative Works thereof, that is intentionally
40
+ submitted to Licensor for inclusion in the Work by the copyright owner
41
+ or by an individual or Legal Entity authorized to submit on behalf of
42
+ the copyright owner.
43
+
44
+ "Contributor" shall mean Licensor and any individual or Legal Entity
45
+ on behalf of whom a Contribution has been received by Licensor and
46
+ subsequently incorporated within the Work.
47
+
48
+ 2. Grant of Copyright License. Subject to the terms and conditions of
49
+ this License, each Contributor hereby grants to You a perpetual,
50
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
51
+ copyright license to reproduce, prepare Derivative Works of,
52
+ publicly display, publicly perform, sublicense, and distribute the
53
+ Work and such Derivative Works in Source or Object form.
54
+
55
+ 3. Grant of Patent License. Subject to the terms and conditions of
56
+ this License, each Contributor hereby grants to You a perpetual,
57
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
58
+ (except as stated in this section) patent license to make, have made,
59
+ use, offer to sell, sell, import, and otherwise transfer the Work.
60
+
61
+ 4. Redistribution. You may reproduce and distribute copies of the
62
+ Work or Derivative Works thereof in any medium, with or without
63
+ modifications, and in Source or Object form, provided that You
64
+ meet the following conditions:
65
+
66
+ (a) You must give any other recipients of the Work or
67
+ Derivative Works a copy of this License; and
68
+
69
+ (b) You must cause any modified files to carry prominent notices
70
+ stating that You changed the files; and
71
+
72
+ (c) You must retain, in the Source form of any Derivative Works
73
+ that You distribute, all copyright, patent, trademark, and
74
+ attribution notices from the Source form of the Work; and
75
+
76
+ (d) If the Work includes a "NOTICE" text file as part of its
77
+ distribution, then any Derivative Works that You distribute must
78
+ include a readable copy of the attribution notices contained
79
+ within such NOTICE file.
80
+
81
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
82
+ any Contribution intentionally submitted for inclusion in the Work
83
+ by You to the Licensor shall be under the terms and conditions of
84
+ this License, without any additional terms or conditions.
85
+
86
+ 6. Trademarks. This License does not grant permission to use the trade
87
+ names, trademarks, service marks, or product names of the Licensor
88
+ (including "postbuild", "git-paid", and "postbuild.dev"), except as
89
+ required for reasonable and customary use in describing the origin
90
+ of the Work and reproducing the content of the NOTICE file.
91
+
92
+ 7. Disclaimer of Warranty. Unless required by applicable law or
93
+ agreed to in writing, Licensor provides the Work (and each
94
+ Contributor provides its Contributions) on an "AS IS" BASIS,
95
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
96
+ implied, including, without limitation, any warranties or conditions
97
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
98
+ PARTICULAR PURPOSE.
99
+
100
+ 8. Limitation of Liability. In no event and under no legal theory,
101
+ whether in tort (including negligence), contract, or otherwise,
102
+ unless required by applicable law (such as deliberate and grossly
103
+ negligent acts) or agreed to in writing, shall any Contributor be
104
+ liable to You for damages, including any direct, indirect, special,
105
+ incidental, or consequential damages of any character arising as a
106
+ result of this License or out of the use or inability to use the
107
+ Work.
108
+
109
+ END OF TERMS AND CONDITIONS
110
+
111
+ Copyright 2026 Bennett Moore (https://postbuild.dev)
112
+
113
+ Licensed under the Apache License, Version 2.0 (the "License");
114
+ you may not use this file except in compliance with the License.
115
+ You may obtain a copy of the License at
116
+
117
+ http://www.apache.org/licenses/LICENSE-2.0
package/README.md CHANGED
@@ -106,10 +106,8 @@ Writes `_meta/postbuild-telemetry.json` and `_dashboards/Postbuild Launch.md` wi
106
106
 
107
107
  ---
108
108
 
109
- ## License & Commercial Terms
109
+ ## License
110
110
 
111
- **Proprietary — Commercial Software (`postbuild.dev`)**
111
+ **Apache-2.0** — Copyright © 2026 Bennett Moore ([https://postbuild.dev](https://postbuild.dev)).
112
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.
113
+ The `git-paid` CLI and MCP client are licensed under the [Apache License, Version 2.0](./LICENSE). Under Section 6 of the Apache-2.0 License, no permission is granted to use the `postbuild`, `git-paid`, or `postbuild.dev` trade names, trademarks, or logos. The hosted `postbuild.dev` cloud service, `@postbuild.dev` verification inbox infrastructure, and commercial Launch Pass / Studio tiers remain proprietary services of [https://postbuild.dev](https://postbuild.dev).
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.1",
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"
@@ -12,7 +12,8 @@
12
12
  "bin/",
13
13
  "lib/",
14
14
  "brand/",
15
- "README.md"
15
+ "README.md",
16
+ "LICENSE"
16
17
  ],
17
18
  "scripts": {
18
19
  "start": "node server.js",
@@ -36,7 +37,7 @@
36
37
  "url": "git+https://github.com/Rbm3267/postbuild.git"
37
38
  },
38
39
  "homepage": "https://postbuild.dev",
39
- "license": "SEE LICENSE IN README.md",
40
+ "license": "Apache-2.0",
40
41
  "dependencies": {
41
42
  "stripe": "^23.1.0"
42
43
  }
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
- });