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 +117 -0
- package/README.md +3 -5
- package/bin/git-paid.js +118 -43
- package/lib/auth.js +75 -1
- package/lib/mcp.js +89 -5
- package/package.json +5 -4
- package/server.js +0 -252
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
|
|
109
|
+
## License
|
|
110
110
|
|
|
111
|
-
**
|
|
111
|
+
**Apache-2.0** — Copyright © 2026 Bennett Moore ([https://postbuild.dev](https://postbuild.dev)).
|
|
112
112
|
|
|
113
|
-
|
|
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 {
|
|
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 &
|
|
37
|
-
console.log(" git paid
|
|
38
|
-
console.log(" git paid fix [url]
|
|
39
|
-
console.log(" git paid
|
|
40
|
-
console.log(" git paid
|
|
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
|
-
|
|
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.
|
|
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 === "
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
678
|
+
limit: maxAllowed,
|
|
666
679
|
bypassCache: Boolean(args.bypass_cache)
|
|
667
680
|
});
|
|
668
681
|
|
|
669
|
-
|
|
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:
|
|
718
|
+
limit: maxAllowed,
|
|
692
719
|
bypassCache: Boolean(args.bypass_cache)
|
|
693
720
|
});
|
|
694
721
|
|
|
695
|
-
|
|
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.
|
|
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": "
|
|
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": "
|
|
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
|
-
});
|