spl-pay-per-suite 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) 2026 Slid Phi Labs. All rights reserved.
2
+
3
+ This package provides client tooling for SPL Pay Per Suite (quotes, checkout links, job intake).
4
+ Proprietary compression engines are not included. Commercial processing is fulfilled by Slid Phi Labs after payment.
5
+ See https://www.slidphilabs.com/pps
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # SPL Pay Per Suite
2
+
3
+ **On-spot project quotes · dual payment rails · lab job intake** for Slid Phi Labs.
4
+
5
+ | Who | Rail |
6
+ |-----|------|
7
+ | **Humans** | Stripe · [slidphilabs.com/pps](https://www.slidphilabs.com/pps) |
8
+ | **Agents** | **x402** · `POST /api/x402-suite` (402 → pay → retry) |
9
+
10
+ - **npm:** `spl-pay-per-suite`
11
+ - **CLI:** `spl-pps` · `pay-per-suite`
12
+ - **MCP:** stdio tools including `spl_pps_x402_*`
13
+ - **Discovery:** `GET https://www.slidphilabs.com/api/agent`
14
+
15
+ Private engines stay in the lab. This package quotes, routes payment, and submits jobs — it does **not** ship proprietary codecs.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm i spl-pay-per-suite
21
+ # or one-shot
22
+ npx spl-pay-per-suite quote --bytes 1048576 --class zeros --product zrw
23
+ ```
24
+
25
+ ## CLI
26
+
27
+ ```bash
28
+ spl-pps quote --bytes 5242880 --class mixed_ints --product auto --op compress
29
+ spl-pps quote ./payload.bin
30
+ spl-pps pay ./payload.bin --email you@company.com
31
+ spl-pps job --email you@company.com --paid --file ./payload.bin
32
+ spl-pps products
33
+ ```
34
+
35
+ ## Library
36
+
37
+ ```js
38
+ import { computeQuote, createCheckout, submitJob, quoteFile } from "spl-pay-per-suite";
39
+
40
+ const q = computeQuote({ product: "zrw", dataClass: "zeros", op: "compress", bytes: 1_000_000 });
41
+ console.log(q.amount_display); // e.g. "37.00"
42
+
43
+ const pay = await createCheckout({ ...q.breakdown, email: "you@company.com" });
44
+ // open pay.url
45
+
46
+ await submitJob({
47
+ email: "you@company.com",
48
+ paidConfirm: true,
49
+ amount_display: q.amount_display,
50
+ product: "zrw",
51
+ dataClass: "zeros",
52
+ op: "compress",
53
+ bytes: 1_000_000,
54
+ });
55
+ ```
56
+
57
+ ## Agentic commerce (x402)
58
+
59
+ **All standing products** + **metered suite jobs**.
60
+
61
+ ```bash
62
+ # Discovery + catalog
63
+ curl -s https://www.slidphilabs.com/api/agent | jq .
64
+ curl -s https://www.slidphilabs.com/api/x402-products | jq .
65
+ spl-pps x402
66
+ spl-pps catalog
67
+
68
+ # Buy CDDG:Split (probe 402, then pay + retry)
69
+ spl-pps buy --sku cddg-split --email you@x.com
70
+ # payFetch("https://www.slidphilabs.com/api/x402-products", { method:"POST", body: JSON.stringify({ sku: "cddg-split", email }) })
71
+
72
+ # Metered suite job
73
+ spl-pps x402-req --bytes 1048576 --class zeros --product zrw
74
+ # payFetch("https://www.slidphilabs.com/api/x402-suite", { ... })
75
+ ```
76
+
77
+ **SKUs:** `cddg-split` · `zrw-n00b` · `zrw-pro` · `zrw-l33t` · `blackjack` · `shard-zip` · `shard-tsdb` · `slid-phi` · `support-integration` · `consulting` · `sponsor` · `donate` · `try-gate`
78
+
79
+ Server env (Vercel): `X402_PAY_TO`, `X402_ASSET`, `X402_NETWORK`, `SOLANA_RPC_URL`. Optional staging: `X402_DEV_BYPASS=1` + header `X-PAYMENT-DEV: ok`.
80
+
81
+ ## MCP
82
+
83
+ ```bash
84
+ npx spl-pay-per-suite mcp-serve
85
+ ```
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "spl-pay-per-suite": {
91
+ "command": "npx",
92
+ "args": ["-y", "spl-pay-per-suite", "mcp-serve"]
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ **Tools:** `spl_pps_info` · `spl_pps_quote` · `spl_pps_checkout` · `spl_pps_submit_job` · `spl_pps_classify` · **`spl_pps_x402_info`** · **`spl_pps_x402_catalog`** · **`spl_pps_x402_buy`** · **`spl_pps_x402_requirements`** · **`spl_pps_x402_submit`**
99
+
100
+ ## Pricing model (public)
101
+
102
+ | Factor | Role |
103
+ |--------|------|
104
+ | Product base | Service value (auto / ZRW / CDDG Split / family) |
105
+ | Size | Progressive $/MB |
106
+ | Data class | Shape multiplier |
107
+ | Operation | compress · decompress · roundtrip |
108
+
109
+ Min **$9** · max **$10,000** per job (Stripe custom-amount limit).
110
+
111
+ ## Env (optional)
112
+
113
+ | Var | Default |
114
+ |-----|---------|
115
+ | `SPL_PPS_API` | `https://www.slidphilabs.com` |
116
+ | `SPL_PPS_SITE` | `https://www.slidphilabs.com/pps` |
117
+ | `SPL_PPS_PAYMENT_LINK` | live Stripe PPP / suite link |
118
+
119
+ ## License
120
+
121
+ © 2026 Slid Phi Labs. Client tooling only; engines and commercial fulfillment remain proprietary.
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "spl-pay-per-suite",
3
+ "version": "1.1.1",
4
+ "description": "SPL Pay Per Suite \u2014 on-spot quotes, Stripe (humans) + x402 agentic commerce (machines). Library, CLI, and MCP.",
5
+ "type": "module",
6
+ "main": "src/index.mjs",
7
+ "exports": {
8
+ ".": "./src/index.mjs",
9
+ "./quote": "./src/quote.mjs"
10
+ },
11
+ "bin": {
12
+ "spl-pps": "src/cli.mjs",
13
+ "spl-pay-per-suite": "src/cli.mjs",
14
+ "pay-per-suite": "src/cli.mjs"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "test": "node --test test/*.test.mjs",
23
+ "mcp": "node src/mcp-server.mjs"
24
+ },
25
+ "keywords": [
26
+ "slid-phi-labs",
27
+ "spl",
28
+ "pay-per-suite",
29
+ "ppp",
30
+ "compression",
31
+ "stripe",
32
+ "x402",
33
+ "agentic",
34
+ "mcp",
35
+ "cli",
36
+ "quote",
37
+ "agentic-commerce",
38
+ "agent-commerce",
39
+ "agents.txt",
40
+ "llms.txt",
41
+ "solana",
42
+ "base",
43
+ "usdc"
44
+ ],
45
+ "author": "Slid Phi Labs",
46
+ "license": "SEE LICENSE IN LICENSE",
47
+ "homepage": "https://www.slidphilabs.com",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/ceedot-rock/SlidPhiLabs.git",
51
+ "directory": "packages/spl-pay-per-suite"
52
+ },
53
+ "engines": {
54
+ "node": ">=18"
55
+ },
56
+ "funding": {
57
+ "type": "individual",
58
+ "url": "https://www.slidphilabs.com/api/agent"
59
+ },
60
+ "agentic": {
61
+ "commerce": "x402",
62
+ "discovery": "https://www.slidphilabs.com/api/agent",
63
+ "catalog": "https://www.slidphilabs.com/api/x402-products",
64
+ "agents_txt": "https://www.slidphilabs.com/agents.txt",
65
+ "agents_json": "https://www.slidphilabs.com/agents.json",
66
+ "llms_txt": "https://www.slidphilabs.com/llms.txt",
67
+ "agent_card": "https://www.slidphilabs.com/.well-known/agent-card.json",
68
+ "agentic_commerce": "https://www.slidphilabs.com/.well-known/agentic-commerce.json",
69
+ "networks": [
70
+ "solana-mainnet-beta",
71
+ "eip155:8453"
72
+ ],
73
+ "payment_header": "X-PAYMENT",
74
+ "one_liner": "Agent commerce: GET https://www.slidphilabs.com/api/agent then pay via x402 on Solana or Base mainnet (X-PAYMENT) on catalog POST.",
75
+ "site": "https://www.slidphilabs.com"
76
+ },
77
+ "x402": {
78
+ "discovery": "https://www.slidphilabs.com/api/agent",
79
+ "catalog": "https://www.slidphilabs.com/api/x402-products",
80
+ "networks": [
81
+ "solana-mainnet-beta",
82
+ "eip155:8453"
83
+ ],
84
+ "payment_header": "X-PAYMENT"
85
+ },
86
+ "bugs": {
87
+ "url": "https://www.slidphilabs.com"
88
+ }
89
+ }
package/src/cli.mjs ADDED
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SPL Pay Per Suite CLI
4
+ * spl-pps quote --bytes 1048576 --class zeros --product zrw --op compress
5
+ * spl-pps quote ./data.bin
6
+ * spl-pps pay --bytes ... [--email you@x.com]
7
+ * spl-pps job --email you@x.com --paid --file data.bin
8
+ * spl-pps mcp (hint: run MCP server)
9
+ */
10
+ import {
11
+ computeQuote,
12
+ quoteRemote,
13
+ createCheckout,
14
+ submitJob,
15
+ quoteFile,
16
+ agentDiscovery,
17
+ x402Catalog,
18
+ x402ProductRequirements,
19
+ x402BuyProduct,
20
+ x402Requirements,
21
+ x402SubmitJob,
22
+ X402_SUITE_URL,
23
+ X402_PRODUCTS_URL,
24
+ SERVICE_NAME,
25
+ SITE_PPS,
26
+ STRIPE_PAYMENT_LINK,
27
+ PRODUCT_BASE,
28
+ DATA_MULT,
29
+ OP_MULT,
30
+ } from "./index.mjs";
31
+
32
+ function usage() {
33
+ console.log(`
34
+ ${SERVICE_NAME} — humans: Stripe · agents: x402
35
+
36
+ Usage:
37
+ spl-pps quote [file] [--bytes N] [--class CLASS] [--product P] [--op compress|decompress|roundtrip]
38
+ spl-pps pay [file] [same flags] [--email you@domain.com] # Stripe (humans)
39
+ spl-pps job --email you@domain.com [--paid] [--file path] # after Stripe
40
+ spl-pps x402 # agent discovery
41
+ spl-pps catalog # standing products (x402)
42
+ spl-pps buy --sku cddg-split [--email] [--payment HDR | --dev-bypass]
43
+ spl-pps x402-req [flags] # metered suite job 402 probe
44
+ spl-pps x402-job [flags] # metered suite job submit
45
+ spl-pps products
46
+ spl-pps open
47
+
48
+ Suite tools: ${Object.keys(PRODUCT_BASE).join(", ")}
49
+ Classes: ${Object.keys(DATA_MULT).join(", ")}
50
+ Ops: ${Object.keys(OP_MULT).join(", ")}
51
+
52
+ Site: ${SITE_PPS}
53
+ Stripe: ${STRIPE_PAYMENT_LINK}
54
+ x402 products: ${X402_PRODUCTS_URL}
55
+ x402 jobs: ${X402_SUITE_URL}
56
+ Agent pay: x402-client payFetch against product or job URL
57
+ `);
58
+ }
59
+
60
+ function parseArgs(argv) {
61
+ const args = { _: [] };
62
+ for (let i = 0; i < argv.length; i++) {
63
+ const a = argv[i];
64
+ if (a === "--bytes" || a === "-b") args.bytes = Number(argv[++i]);
65
+ else if (a === "--class" || a === "-c") args.dataClass = argv[++i];
66
+ else if (a === "--product" || a === "-p") args.product = argv[++i];
67
+ else if (a === "--op" || a === "-o") args.op = argv[++i];
68
+ else if (a === "--email" || a === "-e") args.email = argv[++i];
69
+ else if (a === "--file" || a === "-f") args.file = argv[++i];
70
+ else if (a === "--note") args.note = argv[++i];
71
+ else if (a === "--paid") args.paid = true;
72
+ else if (a === "--remote") args.remote = true;
73
+ else if (a === "--payment") args.payment = argv[++i];
74
+ else if (a === "--sku") args.sku = argv[++i];
75
+ else if (a === "--dev-bypass") args.devBypass = true;
76
+ else if (a === "--help" || a === "-h") args.help = true;
77
+ else if (a.startsWith("-")) {
78
+ console.error("Unknown flag:", a);
79
+ process.exit(2);
80
+ } else args._.push(a);
81
+ }
82
+ return args;
83
+ }
84
+
85
+ async function main() {
86
+ const argv = process.argv.slice(2);
87
+ const cmd = argv[0] || "help";
88
+ const args = parseArgs(argv.slice(1));
89
+
90
+ if (cmd === "help" || args.help) {
91
+ usage();
92
+ return;
93
+ }
94
+
95
+ if (cmd === "products") {
96
+ console.log(JSON.stringify({ service: SERVICE_NAME, products: PRODUCT_BASE, classes: DATA_MULT, ops: OP_MULT }, null, 2));
97
+ return;
98
+ }
99
+
100
+ if (cmd === "open") {
101
+ console.log(SITE_PPS);
102
+ return;
103
+ }
104
+
105
+ if (cmd === "mcp") {
106
+ console.log("Start MCP: node src/mcp-server.mjs");
107
+ console.log("Or: npm run mcp --prefix packages/spl-pay-per-suite");
108
+ console.log("Config: { \"command\": \"npx\", \"args\": [\"-y\", \"spl-pay-per-suite\", \"mcp-serve\"] }");
109
+ return;
110
+ }
111
+
112
+ if (cmd === "mcp-serve") {
113
+ await import("./mcp-server.mjs");
114
+ return;
115
+ }
116
+
117
+ if (cmd === "x402" || cmd === "agent") {
118
+ console.log(JSON.stringify(await agentDiscovery(), null, 2));
119
+ return;
120
+ }
121
+
122
+ if (cmd === "catalog" || cmd === "x402-catalog") {
123
+ console.log(JSON.stringify(await x402Catalog(), null, 2));
124
+ return;
125
+ }
126
+
127
+ if (cmd === "buy" || cmd === "x402-buy") {
128
+ const sku = args.sku || args.product || args._[0];
129
+ if (!sku) {
130
+ console.error("--sku required (e.g. cddg-split, zrw-n00b, blackjack)");
131
+ process.exit(1);
132
+ }
133
+ const opts = {
134
+ sku,
135
+ email: args.email || "",
136
+ note: args.note || "",
137
+ paymentHeader: args.payment,
138
+ devBypass: !!args.devBypass,
139
+ };
140
+ try {
141
+ // no payment → show requirements
142
+ if (!opts.paymentHeader && !opts.devBypass) {
143
+ console.log(JSON.stringify(await x402ProductRequirements(opts), null, 2));
144
+ return;
145
+ }
146
+ console.log(JSON.stringify(await x402BuyProduct(opts), null, 2));
147
+ } catch (e) {
148
+ if (e.status === 402) {
149
+ console.error("# Payment required — pay accepts[0] then: spl-pps buy --sku " + sku + " --payment <HDR>");
150
+ console.log(JSON.stringify(e.requirements, null, 2));
151
+ process.exit(402 % 256);
152
+ }
153
+ throw e;
154
+ }
155
+ return;
156
+ }
157
+
158
+ if (cmd === "x402-req" || cmd === "x402-requirements") {
159
+ const file = args.file || args._[0];
160
+ let opts = {
161
+ product: args.product || "auto",
162
+ dataClass: args.dataClass || "unknown",
163
+ op: args.op || "compress",
164
+ bytes: args.bytes || 0,
165
+ email: args.email || "",
166
+ note: args.note || "",
167
+ };
168
+ if (file) {
169
+ const qf = await quoteFile(file, args);
170
+ opts = {
171
+ ...opts,
172
+ product: args.product || qf.classification.tool || "auto",
173
+ dataClass: args.dataClass || qf.classification.dataClass,
174
+ bytes: qf.bytes,
175
+ fileName: qf.fileName,
176
+ };
177
+ }
178
+ console.log(JSON.stringify(await x402Requirements(opts), null, 2));
179
+ return;
180
+ }
181
+
182
+ if (cmd === "x402-job") {
183
+ const file = args.file || args._[0];
184
+ let opts = {
185
+ product: args.product || "auto",
186
+ dataClass: args.dataClass || "unknown",
187
+ op: args.op || "compress",
188
+ bytes: args.bytes || 0,
189
+ email: args.email || "",
190
+ note: args.note || "",
191
+ paymentHeader: args.payment,
192
+ devBypass: !!args.devBypass,
193
+ };
194
+ if (file) {
195
+ const qf = await quoteFile(file, args);
196
+ opts.bytes = qf.bytes;
197
+ opts.fileName = qf.fileName;
198
+ opts.product = args.product || qf.classification.tool || "auto";
199
+ opts.dataClass = args.dataClass || qf.classification.dataClass;
200
+ }
201
+ try {
202
+ console.log(JSON.stringify(await x402SubmitJob(opts), null, 2));
203
+ } catch (e) {
204
+ if (e.status === 402) {
205
+ console.error("# Payment required — pay accepts[0] then retry with --payment");
206
+ console.log(JSON.stringify(e.requirements, null, 2));
207
+ process.exit(402 % 256);
208
+ }
209
+ throw e;
210
+ }
211
+ return;
212
+ }
213
+
214
+ if (cmd === "quote") {
215
+ const file = args.file || args._[0];
216
+ let result;
217
+ if (file) {
218
+ result = await quoteFile(file, args);
219
+ } else {
220
+ const opts = {
221
+ product: args.product || "auto",
222
+ dataClass: args.dataClass || "unknown",
223
+ op: args.op || "compress",
224
+ bytes: args.bytes || 0,
225
+ };
226
+ result = args.remote ? await quoteRemote(opts) : computeQuote(opts);
227
+ }
228
+ console.log(JSON.stringify(result, null, 2));
229
+ return;
230
+ }
231
+
232
+ if (cmd === "pay") {
233
+ const file = args.file || args._[0];
234
+ let opts = {
235
+ product: args.product || "auto",
236
+ dataClass: args.dataClass || "unknown",
237
+ op: args.op || "compress",
238
+ bytes: args.bytes || 0,
239
+ email: args.email || "",
240
+ };
241
+ if (file) {
242
+ const qf = await quoteFile(file, args);
243
+ opts = {
244
+ ...opts,
245
+ product: args.product || qf.classification.tool || "auto",
246
+ dataClass: args.dataClass || qf.classification.dataClass,
247
+ bytes: qf.bytes,
248
+ };
249
+ console.error(`# ${SERVICE_NAME} quote for ${qf.fileName}: $${qf.quote.amount_display}`);
250
+ } else {
251
+ const q = computeQuote(opts);
252
+ console.error(`# ${SERVICE_NAME} quote: $${q.amount_display}`);
253
+ }
254
+ const chk = await createCheckout(opts);
255
+ console.log(JSON.stringify(chk, null, 2));
256
+ if (chk.url) console.error(`\nOpen: ${chk.url}`);
257
+ if (chk.instructions) console.error(chk.instructions);
258
+ return;
259
+ }
260
+
261
+ if (cmd === "job") {
262
+ if (!args.email) {
263
+ console.error("--email required");
264
+ process.exit(1);
265
+ }
266
+ let opts = {
267
+ email: args.email,
268
+ product: args.product || "auto",
269
+ dataClass: args.dataClass || "unknown",
270
+ op: args.op || "compress",
271
+ bytes: args.bytes || 0,
272
+ paidConfirm: !!args.paid,
273
+ note: args.note || "",
274
+ fileName: "",
275
+ };
276
+ const file = args.file || args._[0];
277
+ if (file) {
278
+ const qf = await quoteFile(file, args);
279
+ opts.fileName = qf.fileName;
280
+ opts.bytes = qf.bytes;
281
+ opts.product = args.product || qf.classification.tool;
282
+ opts.dataClass = args.dataClass || qf.classification.dataClass;
283
+ opts.amount_display = qf.quote.amount_display;
284
+ opts.tool = qf.classification.tool;
285
+ } else {
286
+ opts.amount_display = computeQuote(opts).amount_display;
287
+ }
288
+ const job = await submitJob(opts);
289
+ console.log(JSON.stringify(job, null, 2));
290
+ return;
291
+ }
292
+
293
+ usage();
294
+ process.exit(1);
295
+ }
296
+
297
+ main().catch((e) => {
298
+ console.error(e.message || e);
299
+ process.exit(1);
300
+ });
package/src/index.mjs ADDED
@@ -0,0 +1,333 @@
1
+ /**
2
+ * SPL Pay Per Suite — client SDK
3
+ * Quote on the spot · Stripe pay · submit job to the lab suite.
4
+ */
5
+ import {
6
+ computeQuote,
7
+ classifyBytes,
8
+ STRIPE_PAYMENT_LINK,
9
+ SITE_PPS,
10
+ API_BASE,
11
+ PRODUCT_BASE,
12
+ DATA_MULT,
13
+ OP_MULT,
14
+ } from "./quote.mjs";
15
+
16
+ export {
17
+ computeQuote,
18
+ classifyBytes,
19
+ STRIPE_PAYMENT_LINK,
20
+ SITE_PPS,
21
+ API_BASE,
22
+ PRODUCT_BASE,
23
+ DATA_MULT,
24
+ OP_MULT,
25
+ };
26
+
27
+ export const SERVICE_NAME = "SPL Pay Per Suite";
28
+
29
+ /**
30
+ * Live quote from site API (falls back to local computeQuote).
31
+ */
32
+ export async function quoteRemote(opts = {}) {
33
+ try {
34
+ const r = await fetch(`${API_BASE}/api/ppp-quote`, {
35
+ method: "POST",
36
+ headers: { "Content-Type": "application/json" },
37
+ body: JSON.stringify({
38
+ product: opts.product || "auto",
39
+ dataClass: opts.dataClass || opts.data_class || "unknown",
40
+ op: opts.op || "compress",
41
+ bytes: opts.bytes || 0,
42
+ }),
43
+ });
44
+ if (r.ok) {
45
+ const data = await r.json();
46
+ return { ...data, service: SERVICE_NAME, suite_url: SITE_PPS, source: "api" };
47
+ }
48
+ } catch {
49
+ /* local fallback */
50
+ }
51
+ return { ...computeQuote(opts), source: "local" };
52
+ }
53
+
54
+ /**
55
+ * Get Stripe checkout URL (session if server has secret; else payment link).
56
+ */
57
+ export async function createCheckout(opts = {}) {
58
+ try {
59
+ const r = await fetch(`${API_BASE}/api/ppp-checkout`, {
60
+ method: "POST",
61
+ headers: { "Content-Type": "application/json" },
62
+ body: JSON.stringify({
63
+ product: opts.product || "auto",
64
+ dataClass: opts.dataClass || opts.data_class || "unknown",
65
+ op: opts.op || "compress",
66
+ bytes: opts.bytes || 0,
67
+ email: opts.email || "",
68
+ }),
69
+ });
70
+ if (r.ok) {
71
+ const data = await r.json();
72
+ return { ...data, service: SERVICE_NAME };
73
+ }
74
+ } catch {
75
+ /* fall through */
76
+ }
77
+ const q = computeQuote(opts);
78
+ const url = new URL(STRIPE_PAYMENT_LINK);
79
+ if (opts.email) url.searchParams.set("prefilled_email", opts.email);
80
+ return {
81
+ ok: true,
82
+ mode: "payment_link",
83
+ url: url.toString(),
84
+ quote: q,
85
+ service: SERVICE_NAME,
86
+ instructions: `On Stripe, enter amount $${q.amount_display} (your SPL Pay Per Suite quote).`,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Submit a paid (or ready) project job to the lab.
92
+ */
93
+ export async function submitJob(opts = {}) {
94
+ const r = await fetch(`${API_BASE}/api/ppp-job`, {
95
+ method: "POST",
96
+ headers: { "Content-Type": "application/json" },
97
+ body: JSON.stringify({
98
+ email: opts.email,
99
+ product: opts.product || opts.tool || "auto",
100
+ dataClass: opts.dataClass || opts.data_class || "unknown",
101
+ op: opts.op || "compress",
102
+ bytes: opts.bytes || 0,
103
+ amount_display: opts.amount_display || opts.amount || "",
104
+ tool: opts.tool || opts.product || "auto",
105
+ fileName: opts.fileName || opts.file_name || "",
106
+ paidConfirm: opts.paidConfirm ?? opts.paid ?? false,
107
+ note: opts.note || "",
108
+ sampleHint: opts.sampleHint || opts.sample_hint || "",
109
+ }),
110
+ });
111
+ const data = await r.json().catch(() => ({}));
112
+ if (!r.ok) {
113
+ const err = new Error(data.error || `Job submit failed (${r.status})`);
114
+ err.data = data;
115
+ throw err;
116
+ }
117
+ return { ...data, service: SERVICE_NAME };
118
+ }
119
+
120
+ /** Agentic x402 endpoints (machines). Humans use Stripe. */
121
+ export const X402_SUITE_URL = `${API_BASE}/api/x402-suite`;
122
+ export const X402_PRODUCTS_URL = `${API_BASE}/api/x402-products`;
123
+ export const AGENT_DISCOVERY_URL = `${API_BASE}/api/agent`;
124
+
125
+ /**
126
+ * Discover agentic commerce rails (Stripe human + x402 agent).
127
+ */
128
+ export async function agentDiscovery() {
129
+ try {
130
+ const r = await fetch(AGENT_DISCOVERY_URL);
131
+ if (r.ok) return await r.json();
132
+ } catch {
133
+ /* fall through */
134
+ }
135
+ return {
136
+ service: SERVICE_NAME,
137
+ suite_ui: SITE_PPS,
138
+ rails: {
139
+ human: { stripe: true, ui: SITE_PPS },
140
+ agent: { protocol: "x402", job: X402_SUITE_URL },
141
+ },
142
+ source: "local-fallback",
143
+ };
144
+ }
145
+
146
+ /**
147
+ * List all standing products available for agent x402 purchase.
148
+ */
149
+ export async function x402Catalog() {
150
+ try {
151
+ const r = await fetch(X402_PRODUCTS_URL);
152
+ if (r.ok) return { ...(await r.json()), source: "api" };
153
+ } catch {
154
+ /* fall through */
155
+ }
156
+ return {
157
+ service: "Slid Phi Labs",
158
+ buy_product: X402_PRODUCTS_URL,
159
+ error: "catalog unreachable",
160
+ source: "local-fallback",
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Probe 402 requirements for a standing product SKU (no pay).
166
+ */
167
+ export async function x402ProductRequirements(opts = {}) {
168
+ const sku = opts.sku || opts.product || "cddg-split";
169
+ const r = await fetch(X402_PRODUCTS_URL, {
170
+ method: "POST",
171
+ headers: { "Content-Type": "application/json" },
172
+ body: JSON.stringify({
173
+ sku,
174
+ email: opts.email || "",
175
+ note: opts.note || "",
176
+ }),
177
+ });
178
+ const data = await r.json().catch(() => ({}));
179
+ return {
180
+ service: SERVICE_NAME,
181
+ rail: "x402",
182
+ kind: "standing_product",
183
+ status: r.status,
184
+ payment_required: r.status === 402,
185
+ endpoint: X402_PRODUCTS_URL,
186
+ sku,
187
+ requirements: data,
188
+ how:
189
+ r.status === 402
190
+ ? "Retry POST with X-PAYMENT. Use x402-client payFetch against X402_PRODUCTS_URL."
191
+ : data.message || data.error || "see body",
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Buy standing product after payment (or dev bypass).
197
+ */
198
+ export async function x402BuyProduct(opts = {}) {
199
+ const headers = { "Content-Type": "application/json" };
200
+ if (opts.paymentHeader || opts.xPayment) {
201
+ headers["X-PAYMENT"] = opts.paymentHeader || opts.xPayment;
202
+ }
203
+ if (opts.devBypass || process.env.X402_DEV_BYPASS === "1") {
204
+ headers["X-PAYMENT-DEV"] = "ok";
205
+ }
206
+ const r = await fetch(X402_PRODUCTS_URL, {
207
+ method: "POST",
208
+ headers,
209
+ body: JSON.stringify({
210
+ sku: opts.sku || opts.product,
211
+ email: opts.email || "",
212
+ note: opts.note || "",
213
+ }),
214
+ });
215
+ const data = await r.json().catch(() => ({}));
216
+ if (r.status === 402) {
217
+ const err = new Error("Payment required (x402 product)");
218
+ err.status = 402;
219
+ err.requirements = data;
220
+ throw err;
221
+ }
222
+ if (!r.ok) {
223
+ const err = new Error(data.error || data.detail || `x402 buy failed (${r.status})`);
224
+ err.data = data;
225
+ throw err;
226
+ }
227
+ return { ...data, service: SERVICE_NAME, rail: "x402" };
228
+ }
229
+
230
+ /**
231
+ * Probe x402 requirements for a metered suite job (expects HTTP 402 body with accepts[]).
232
+ * Does not pay — agents use x402-client payFetch to complete.
233
+ */
234
+ export async function x402Requirements(opts = {}) {
235
+ const body = {
236
+ product: opts.product || "auto",
237
+ dataClass: opts.dataClass || opts.data_class || "unknown",
238
+ op: opts.op || "compress",
239
+ bytes: opts.bytes || 0,
240
+ email: opts.email || "",
241
+ note: opts.note || "",
242
+ fileName: opts.fileName || opts.file_name || "",
243
+ tool: opts.tool || opts.product || "auto",
244
+ };
245
+ const r = await fetch(X402_SUITE_URL, {
246
+ method: "POST",
247
+ headers: { "Content-Type": "application/json" },
248
+ body: JSON.stringify(body),
249
+ });
250
+ const data = await r.json().catch(() => ({}));
251
+ return {
252
+ service: SERVICE_NAME,
253
+ rail: "x402",
254
+ status: r.status,
255
+ payment_required: r.status === 402,
256
+ endpoint: X402_SUITE_URL,
257
+ requirements: data,
258
+ quote: data.quote || data.accepts?.[0]?.extra?.quote || null,
259
+ how: r.status === 402
260
+ ? "Retry POST with X-PAYMENT header (base64 payment proof). Use x402-client payFetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body })."
261
+ : data.message || "Unexpected status",
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Submit agent job with optional pre-built X-PAYMENT header, or X402_DEV_BYPASS.
267
+ * For live pay+retry from Node, prefer: import { payFetch } from 'x402-client' against X402_SUITE_URL.
268
+ */
269
+ export async function x402SubmitJob(opts = {}) {
270
+ const headers = { "Content-Type": "application/json" };
271
+ if (opts.paymentHeader || opts.xPayment) {
272
+ headers["X-PAYMENT"] = opts.paymentHeader || opts.xPayment;
273
+ }
274
+ if (opts.devBypass || process.env.X402_DEV_BYPASS === "1") {
275
+ headers["X-PAYMENT-DEV"] = "ok";
276
+ }
277
+ const r = await fetch(X402_SUITE_URL, {
278
+ method: "POST",
279
+ headers,
280
+ body: JSON.stringify({
281
+ product: opts.product || opts.tool || "auto",
282
+ dataClass: opts.dataClass || opts.data_class || "unknown",
283
+ op: opts.op || "compress",
284
+ bytes: opts.bytes || 0,
285
+ email: opts.email || "",
286
+ note: opts.note || "",
287
+ fileName: opts.fileName || opts.file_name || "",
288
+ tool: opts.tool || opts.product || "auto",
289
+ sampleHint: opts.sampleHint || "",
290
+ }),
291
+ });
292
+ const data = await r.json().catch(() => ({}));
293
+ if (r.status === 402) {
294
+ const err = new Error("Payment required (x402)");
295
+ err.status = 402;
296
+ err.requirements = data;
297
+ throw err;
298
+ }
299
+ if (!r.ok) {
300
+ const err = new Error(data.error || data.detail || `x402 job failed (${r.status})`);
301
+ err.data = data;
302
+ throw err;
303
+ }
304
+ return { ...data, service: SERVICE_NAME, rail: "x402" };
305
+ }
306
+
307
+ /**
308
+ * Quote + classify from a file path (Node).
309
+ */
310
+ export async function quoteFile(filePath, opts = {}) {
311
+ const fs = await import("node:fs/promises");
312
+ const path = await import("node:path");
313
+ const st = await fs.stat(filePath);
314
+ const sample = await fs.readFile(filePath);
315
+ const head = sample.subarray(0, Math.min(sample.length, 8192));
316
+ const cls = classifyBytes(head);
317
+ const product = opts.product || (opts.autoTool === false ? "auto" : cls.tool) || "auto";
318
+ const dataClass = opts.dataClass || cls.dataClass;
319
+ const quote = computeQuote({
320
+ product,
321
+ dataClass,
322
+ op: opts.op || "compress",
323
+ bytes: st.size,
324
+ });
325
+ return {
326
+ service: SERVICE_NAME,
327
+ fileName: path.basename(filePath),
328
+ bytes: st.size,
329
+ classification: cls,
330
+ quote,
331
+ suite_url: SITE_PPS,
332
+ };
333
+ }
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SPL Pay Per Suite — MCP server (stdio JSON-RPC / tools-list style)
4
+ *
5
+ * Tools:
6
+ * spl_pps_quote
7
+ * spl_pps_checkout
8
+ * spl_pps_submit_job
9
+ * spl_pps_classify
10
+ * spl_pps_info
11
+ * spl_pps_x402_info
12
+ * spl_pps_x402_requirements
13
+ * spl_pps_x402_submit
14
+ *
15
+ * Env:
16
+ * SPL_PPS_API, SPL_PPS_SITE, SPL_PPS_PAYMENT_LINK, X402_DEV_BYPASS
17
+ */
18
+ import readline from "node:readline";
19
+ import {
20
+ computeQuote,
21
+ classifyBytes,
22
+ createCheckout,
23
+ submitJob,
24
+ quoteRemote,
25
+ agentDiscovery,
26
+ x402Catalog,
27
+ x402ProductRequirements,
28
+ x402BuyProduct,
29
+ x402Requirements,
30
+ x402SubmitJob,
31
+ X402_SUITE_URL,
32
+ X402_PRODUCTS_URL,
33
+ SERVICE_NAME,
34
+ SITE_PPS,
35
+ STRIPE_PAYMENT_LINK,
36
+ PRODUCT_BASE,
37
+ DATA_MULT,
38
+ OP_MULT,
39
+ } from "./index.mjs";
40
+
41
+ const TOOLS = [
42
+ {
43
+ name: "spl_pps_info",
44
+ description:
45
+ "SPL Pay Per Suite overview: service name, site URL, Stripe (humans) + x402 (agents), products, data classes, ops.",
46
+ inputSchema: { type: "object", properties: {} },
47
+ },
48
+ {
49
+ name: "spl_pps_x402_info",
50
+ description:
51
+ "Agentic commerce discovery: standing product catalog + suite job endpoints, headers, flow. Stripe remains for humans.",
52
+ inputSchema: { type: "object", properties: {} },
53
+ },
54
+ {
55
+ name: "spl_pps_x402_catalog",
56
+ description:
57
+ "List all standing Slid Phi Labs products agents can buy via x402 (CDDG:Split, ZRW tiers, Blackjack, shards, support, etc.) with USD prices.",
58
+ inputSchema: { type: "object", properties: {} },
59
+ },
60
+ {
61
+ name: "spl_pps_x402_buy",
62
+ description:
63
+ "Buy a standing product via x402. Without paymentHeader → returns 402 requirements. With paymentHeader (or devBypass) → order_id + access_url.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ sku: {
68
+ type: "string",
69
+ description:
70
+ "cddg-split | zrw-n00b | zrw-pro | zrw-l33t | blackjack | shard-zip | shard-tsdb | slid-phi | support-integration | consulting | sponsor | donate | try-gate",
71
+ },
72
+ email: { type: "string" },
73
+ note: { type: "string" },
74
+ paymentHeader: { type: "string" },
75
+ devBypass: { type: "boolean" },
76
+ },
77
+ required: ["sku"],
78
+ },
79
+ },
80
+ {
81
+ name: "spl_pps_x402_requirements",
82
+ description:
83
+ "Probe POST /api/x402-suite (metered job) without paying. Returns 402 accepts[] for suite compress/decompress jobs.",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ product: { type: "string" },
88
+ dataClass: { type: "string" },
89
+ op: { type: "string" },
90
+ bytes: { type: "number" },
91
+ email: { type: "string" },
92
+ note: { type: "string" },
93
+ },
94
+ },
95
+ },
96
+ {
97
+ name: "spl_pps_x402_submit",
98
+ description:
99
+ "Submit metered suite job via x402 (not fixed SKU). Pass paymentHeader after paying, or devBypass for staging.",
100
+ inputSchema: {
101
+ type: "object",
102
+ properties: {
103
+ product: { type: "string" },
104
+ dataClass: { type: "string" },
105
+ op: { type: "string" },
106
+ bytes: { type: "number" },
107
+ email: { type: "string" },
108
+ note: { type: "string" },
109
+ fileName: { type: "string" },
110
+ paymentHeader: { type: "string", description: "Base64 X-PAYMENT proof" },
111
+ devBypass: { type: "boolean", description: "Staging only if server allows" },
112
+ },
113
+ },
114
+ },
115
+ {
116
+ name: "spl_pps_quote",
117
+ description:
118
+ "Instant quote for an SPL Pay Per Suite project. Inputs: product, dataClass, op, bytes. Returns USD amount and breakdown.",
119
+ inputSchema: {
120
+ type: "object",
121
+ properties: {
122
+ product: {
123
+ type: "string",
124
+ description: "auto | zrw | cddg-split | blackjack | shard-zip | shard-tsdb | slid-phi",
125
+ },
126
+ dataClass: {
127
+ type: "string",
128
+ description:
129
+ "zeros | ramp | walk | mixed_ints | timeseries | json_series | binary | unknown",
130
+ },
131
+ op: { type: "string", description: "compress | decompress | roundtrip" },
132
+ bytes: { type: "number", description: "Payload size in bytes" },
133
+ remote: { type: "boolean", description: "If true, use live site API" },
134
+ },
135
+ },
136
+ },
137
+ {
138
+ name: "spl_pps_checkout",
139
+ description:
140
+ "Create Stripe checkout URL for the quoted SPL Pay Per Suite amount. Prefer after spl_pps_quote.",
141
+ inputSchema: {
142
+ type: "object",
143
+ properties: {
144
+ product: { type: "string" },
145
+ dataClass: { type: "string" },
146
+ op: { type: "string" },
147
+ bytes: { type: "number" },
148
+ email: { type: "string" },
149
+ },
150
+ },
151
+ },
152
+ {
153
+ name: "spl_pps_submit_job",
154
+ description:
155
+ "Submit a Pay Per Suite job after payment. Lab runs the best available tool and emails results. Requires email.",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ email: { type: "string" },
160
+ product: { type: "string" },
161
+ dataClass: { type: "string" },
162
+ op: { type: "string" },
163
+ bytes: { type: "number" },
164
+ amount_display: { type: "string" },
165
+ tool: { type: "string" },
166
+ fileName: { type: "string" },
167
+ paidConfirm: { type: "boolean" },
168
+ note: { type: "string" },
169
+ sampleHint: { type: "string" },
170
+ },
171
+ required: ["email"],
172
+ },
173
+ },
174
+ {
175
+ name: "spl_pps_classify",
176
+ description:
177
+ "Classify a base64 sample (first few KB) into a data class and recommended tool. No proprietary encode.",
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ sample_base64: { type: "string", description: "Base64 of file head (≤8KB recommended)" },
182
+ },
183
+ required: ["sample_base64"],
184
+ },
185
+ },
186
+ ];
187
+
188
+ function ok(id, result) {
189
+ return { jsonrpc: "2.0", id, result };
190
+ }
191
+ function err(id, code, message) {
192
+ return { jsonrpc: "2.0", id, error: { code, message } };
193
+ }
194
+
195
+ async function callTool(name, args = {}) {
196
+ switch (name) {
197
+ case "spl_pps_info":
198
+ return {
199
+ service: SERVICE_NAME,
200
+ site: SITE_PPS,
201
+ pay_human: STRIPE_PAYMENT_LINK,
202
+ pay_agent_products: X402_PRODUCTS_URL,
203
+ pay_agent_jobs: X402_SUITE_URL,
204
+ rails: { human: "stripe", agent: "x402" },
205
+ suite_tools: Object.keys(PRODUCT_BASE),
206
+ data_classes: Object.keys(DATA_MULT),
207
+ ops: Object.keys(OP_MULT),
208
+ npm: "spl-pay-per-suite",
209
+ cli: "npx spl-pay-per-suite quote|pay|job|x402|catalog|buy",
210
+ };
211
+ case "spl_pps_x402_info":
212
+ return agentDiscovery();
213
+ case "spl_pps_x402_catalog":
214
+ return x402Catalog();
215
+ case "spl_pps_x402_buy": {
216
+ try {
217
+ return await x402BuyProduct(args);
218
+ } catch (e) {
219
+ if (e.status === 402) {
220
+ return {
221
+ payment_required: true,
222
+ status: 402,
223
+ sku: args.sku,
224
+ requirements: e.requirements,
225
+ how: "Pay accepts[0] then call again with paymentHeader",
226
+ };
227
+ }
228
+ throw e;
229
+ }
230
+ }
231
+ case "spl_pps_x402_requirements":
232
+ return x402Requirements({
233
+ product: args.product || "auto",
234
+ dataClass: args.dataClass || args.data_class || "unknown",
235
+ op: args.op || "compress",
236
+ bytes: args.bytes || 0,
237
+ email: args.email || "",
238
+ note: args.note || "",
239
+ });
240
+ case "spl_pps_x402_submit":
241
+ return x402SubmitJob(args);
242
+ case "spl_pps_quote": {
243
+ const opts = {
244
+ product: args.product || "auto",
245
+ dataClass: args.dataClass || args.data_class || "unknown",
246
+ op: args.op || "compress",
247
+ bytes: args.bytes || 0,
248
+ };
249
+ return args.remote ? await quoteRemote(opts) : computeQuote(opts);
250
+ }
251
+ case "spl_pps_checkout":
252
+ return createCheckout({
253
+ product: args.product || "auto",
254
+ dataClass: args.dataClass || args.data_class || "unknown",
255
+ op: args.op || "compress",
256
+ bytes: args.bytes || 0,
257
+ email: args.email || "",
258
+ });
259
+ case "spl_pps_submit_job":
260
+ return submitJob(args);
261
+ case "spl_pps_classify": {
262
+ const raw = Buffer.from(String(args.sample_base64 || ""), "base64");
263
+ return { service: SERVICE_NAME, ...classifyBytes(raw), bytes_sampled: raw.length };
264
+ }
265
+ default:
266
+ throw new Error(`Unknown tool: ${name}`);
267
+ }
268
+ }
269
+
270
+ async function handle(msg) {
271
+ const { id, method, params } = msg;
272
+ if (method === "initialize") {
273
+ return ok(id, {
274
+ protocolVersion: "2024-11-05",
275
+ serverInfo: { name: "spl-pay-per-suite", version: "1.0.0" },
276
+ capabilities: { tools: {} },
277
+ });
278
+ }
279
+ if (method === "notifications/initialized" || method === "initialized") {
280
+ return null;
281
+ }
282
+ if (method === "tools/list" || method === "list_tools") {
283
+ return ok(id, { tools: TOOLS });
284
+ }
285
+ if (method === "tools/call" || method === "call_tool") {
286
+ const name = params?.name || params?.tool;
287
+ const args = params?.arguments || params?.args || {};
288
+ try {
289
+ const result = await callTool(name, args);
290
+ return ok(id, {
291
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
292
+ structuredContent: result,
293
+ });
294
+ } catch (e) {
295
+ return err(id, -32000, e.message || String(e));
296
+ }
297
+ }
298
+ if (method === "ping") return ok(id, {});
299
+ return err(id, -32601, `Method not found: ${method}`);
300
+ }
301
+
302
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
303
+ rl.on("line", async (line) => {
304
+ const t = line.trim();
305
+ if (!t) return;
306
+ let msg;
307
+ try {
308
+ msg = JSON.parse(t);
309
+ } catch {
310
+ process.stdout.write(JSON.stringify(err(null, -32700, "Parse error")) + "\n");
311
+ return;
312
+ }
313
+ const out = await handle(msg);
314
+ if (out) process.stdout.write(JSON.stringify(out) + "\n");
315
+ });
316
+
317
+ process.stderr.write(`${SERVICE_NAME} MCP ready (stdio)\n`);
package/src/quote.mjs ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * SPL Pay Per Suite — local quote engine (matches site /api/ppp-quote).
3
+ * Price = product value base + size fee × data-class × operation.
4
+ */
5
+
6
+ export const PRODUCT_BASE = {
7
+ auto: 2900,
8
+ zrw: 2900,
9
+ "cddg-split": 4900,
10
+ blackjack: 3900,
11
+ "shard-zip": 3900,
12
+ "shard-tsdb": 3900,
13
+ "slid-phi": 3900,
14
+ };
15
+
16
+ export const DATA_MULT = {
17
+ zeros: 0.85,
18
+ ramp: 0.95,
19
+ walk: 1.05,
20
+ mixed_ints: 1.15,
21
+ timeseries: 1.2,
22
+ json_series: 1.1,
23
+ binary: 1.25,
24
+ unknown: 1.15,
25
+ };
26
+
27
+ export const OP_MULT = {
28
+ compress: 1.0,
29
+ decompress: 0.85,
30
+ roundtrip: 1.35,
31
+ };
32
+
33
+ export const MIN_CENTS = 900;
34
+ export const MAX_CENTS = 1_000_000;
35
+
36
+ export const STRIPE_PAYMENT_LINK =
37
+ process.env.SPL_PPS_PAYMENT_LINK ||
38
+ "https://buy.stripe.com/aFa00k4B70OYetL0O46wE0g";
39
+
40
+ export const SITE_PPS = process.env.SPL_PPS_SITE || "https://www.slidphilabs.com/pps";
41
+ export const API_BASE = process.env.SPL_PPS_API || "https://www.slidphilabs.com";
42
+
43
+ function sizeFeeCents(bytes) {
44
+ const mb = Math.max(0, Number(bytes) || 0) / (1024 * 1024);
45
+ if (mb <= 0) return 0;
46
+ if (mb <= 1) return Math.round(mb * 800);
47
+ if (mb <= 10) return 800 + Math.round((mb - 1) * 450);
48
+ if (mb <= 100) return 800 + 9 * 450 + Math.round((mb - 10) * 220);
49
+ return 800 + 9 * 450 + 90 * 220 + Math.round((mb - 100) * 90);
50
+ }
51
+
52
+ /**
53
+ * @param {{ product?: string, dataClass?: string, op?: string, bytes?: number }} opts
54
+ */
55
+ export function computeQuote({
56
+ product = "auto",
57
+ dataClass = "unknown",
58
+ op = "compress",
59
+ bytes = 0,
60
+ } = {}) {
61
+ const prod = PRODUCT_BASE[product] != null ? product : "auto";
62
+ const cls = DATA_MULT[dataClass] != null ? dataClass : "unknown";
63
+ const operation = OP_MULT[op] != null ? op : "compress";
64
+ const b = Math.max(0, Math.min(Number(bytes) || 0, 5 * 1024 * 1024 * 1024));
65
+ const base = PRODUCT_BASE[prod];
66
+ const size = sizeFeeCents(b);
67
+ const raw = Math.round((base + size) * DATA_MULT[cls] * OP_MULT[operation]);
68
+ const cents = Math.min(MAX_CENTS, Math.max(MIN_CENTS, raw));
69
+
70
+ return {
71
+ ok: true,
72
+ service: "SPL Pay Per Suite",
73
+ currency: "usd",
74
+ amount_cents: cents,
75
+ amount_display: (cents / 100).toFixed(2),
76
+ breakdown: {
77
+ product: prod,
78
+ product_base_cents: base,
79
+ size_cents: size,
80
+ data_class: cls,
81
+ data_multiplier: DATA_MULT[cls],
82
+ op: operation,
83
+ op_multiplier: OP_MULT[operation],
84
+ bytes: b,
85
+ mb: +(b / (1024 * 1024)).toFixed(4),
86
+ min_cents: MIN_CENTS,
87
+ max_cents: MAX_CENTS,
88
+ },
89
+ pay_url: STRIPE_PAYMENT_LINK,
90
+ suite_url: SITE_PPS,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Lightweight data-class heuristic from a Buffer / Uint8Array sample.
96
+ * Does not run proprietary codecs.
97
+ */
98
+ export function classifyBytes(buf) {
99
+ if (!buf || !buf.length) return { dataClass: "unknown", tool: "auto", confidence: 0 };
100
+ const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
101
+ const n = Math.min(u8.length, 4096);
102
+ let zeros = 0;
103
+ let printable = 0;
104
+ for (let i = 0; i < n; i++) {
105
+ if (u8[i] === 0) zeros++;
106
+ if (u8[i] >= 32 && u8[i] < 127) printable++;
107
+ }
108
+ const zeroRatio = zeros / n;
109
+ const printRatio = printable / n;
110
+
111
+ // JSON-ish
112
+ if (printRatio > 0.85 && (u8[0] === 0x7b || u8[0] === 0x5b)) {
113
+ return { dataClass: "json_series", tool: "zrw", confidence: 0.7 };
114
+ }
115
+
116
+ // Look for int32-like ramp/zeros if length multiple of 4
117
+ if (u8.length >= 16 && u8.length % 4 === 0) {
118
+ const view = new DataView(u8.buffer, u8.byteOffset, Math.min(u8.length, 400));
119
+ const ints = [];
120
+ for (let i = 0; i + 4 <= view.byteLength; i += 4) {
121
+ ints.push(view.getInt32(i, true));
122
+ }
123
+ if (ints.length >= 4) {
124
+ const allZero = ints.every((v) => v === 0);
125
+ if (allZero) return { dataClass: "zeros", tool: "zrw", confidence: 0.9 };
126
+ let ramp = true;
127
+ for (let i = 1; i < Math.min(ints.length, 64); i++) {
128
+ if (ints[i] !== ints[0] + i) {
129
+ ramp = false;
130
+ break;
131
+ }
132
+ }
133
+ if (ramp) return { dataClass: "ramp", tool: "zrw", confidence: 0.85 };
134
+ const diffs = [];
135
+ for (let i = 1; i < Math.min(ints.length, 64); i++) diffs.push(Math.abs(ints[i] - ints[i - 1]));
136
+ const maxD = Math.max(...diffs);
137
+ if (maxD > 0 && maxD < 1000) {
138
+ return { dataClass: "walk", tool: "zrw", confidence: 0.65 };
139
+ }
140
+ return { dataClass: "mixed_ints", tool: "blackjack", confidence: 0.55 };
141
+ }
142
+ }
143
+
144
+ if (zeroRatio > 0.6) return { dataClass: "zeros", tool: "zrw", confidence: 0.6 };
145
+ if (printRatio > 0.9) return { dataClass: "json_series", tool: "shard-zip", confidence: 0.45 };
146
+ return { dataClass: "binary", tool: "auto", confidence: 0.4 };
147
+ }