leak-cli 2026.2.17-beta.1 → 2026.2.17
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/.env.example +2 -0
- package/README.md +164 -17
- package/examples/multi-host.example.json +50 -0
- package/package.json +9 -4
- package/scripts/buy.js +224 -189
- package/scripts/cli.js +81 -14
- package/scripts/config.js +128 -28
- package/scripts/config_store.js +23 -0
- package/scripts/host.js +1131 -0
- package/scripts/leak.js +1240 -173
- package/scripts/ui.js +106 -0
- package/src/access_mode.js +51 -0
- package/src/download_code.js +91 -0
- package/src/index.js +271 -95
package/scripts/config.js
CHANGED
|
@@ -15,17 +15,46 @@ import {
|
|
|
15
15
|
writeConfig,
|
|
16
16
|
} from "./config_store.js";
|
|
17
17
|
import { resolveSupportedChain } from "../src/chain_meta.js";
|
|
18
|
+
import {
|
|
19
|
+
ACCESS_MODE_VALUES,
|
|
20
|
+
DEFAULT_ACCESS_MODE,
|
|
21
|
+
accessModeRequiresDownloadCode,
|
|
22
|
+
accessModeRequiresPayment,
|
|
23
|
+
isValidAccessMode,
|
|
24
|
+
} from "../src/access_mode.js";
|
|
25
|
+
import { hashDownloadCode } from "../src/download_code.js";
|
|
26
|
+
import { createUi } from "./ui.js";
|
|
18
27
|
|
|
19
28
|
const ALLOWED_FACILITATOR_MODES = new Set(["testnet", "cdp_mainnet"]);
|
|
20
29
|
const ALLOWED_CONFIRMATION_POLICIES = new Set(["confirmed", "optimistic"]);
|
|
30
|
+
const outUi = createUi(output);
|
|
31
|
+
const errUi = createUi(process.stderr);
|
|
32
|
+
|
|
33
|
+
function logInfo(message) {
|
|
34
|
+
console.log(outUi.statusLine("info", message));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function logOk(message) {
|
|
38
|
+
console.log(outUi.statusLine("ok", message));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function logWarn(message) {
|
|
42
|
+
console.error(errUi.statusLine("warn", message));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function logError(message) {
|
|
46
|
+
console.error(errUi.statusLine("error", message));
|
|
47
|
+
}
|
|
21
48
|
|
|
22
49
|
function usageAndExit(code = 0) {
|
|
23
|
-
console.log("
|
|
50
|
+
console.log(outUi.heading("Leak Config CLI"));
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log(outUi.section("Usage"));
|
|
24
53
|
console.log(" leak config");
|
|
25
54
|
console.log(" leak config show");
|
|
26
55
|
console.log(" leak config --write-env");
|
|
27
56
|
console.log("");
|
|
28
|
-
console.log("Notes
|
|
57
|
+
console.log(outUi.section("Notes"));
|
|
29
58
|
console.log(" - Stores defaults in ~/.leak/config.json");
|
|
30
59
|
console.log(" - `--write-env` writes a project .env scaffold in the current directory");
|
|
31
60
|
process.exit(code);
|
|
@@ -123,19 +152,48 @@ async function askOgField(rl, label, currentValue, fieldType) {
|
|
|
123
152
|
}
|
|
124
153
|
}
|
|
125
154
|
|
|
155
|
+
async function askDownloadCodeHash(rl, existingHash, accessMode) {
|
|
156
|
+
if (!accessModeRequiresDownloadCode(accessMode)) return "";
|
|
157
|
+
|
|
158
|
+
if (existingHash) {
|
|
159
|
+
const prompt = new Select({
|
|
160
|
+
name: "DOWNLOAD_CODE",
|
|
161
|
+
message: "DOWNLOAD_CODE (required by selected ACCESS_MODE)",
|
|
162
|
+
choices: [
|
|
163
|
+
{ name: "keep", message: "Keep existing stored download code hash" },
|
|
164
|
+
{ name: "replace", message: "Replace with new download code" },
|
|
165
|
+
],
|
|
166
|
+
initial: 0,
|
|
167
|
+
});
|
|
168
|
+
const choice = await prompt.run();
|
|
169
|
+
if (choice === "keep") return existingHash;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let raw = (await rl.question("DOWNLOAD_CODE (input visible): ")).trim();
|
|
173
|
+
while (!raw) {
|
|
174
|
+
logError("DOWNLOAD_CODE is required for the selected ACCESS_MODE.");
|
|
175
|
+
raw = (await rl.question("DOWNLOAD_CODE (input visible): ")).trim();
|
|
176
|
+
}
|
|
177
|
+
return hashDownloadCode(raw);
|
|
178
|
+
}
|
|
179
|
+
|
|
126
180
|
function printShow() {
|
|
127
181
|
const loaded = readConfig();
|
|
128
182
|
if (loaded.error) {
|
|
129
|
-
|
|
183
|
+
logWarn(loaded.error);
|
|
130
184
|
}
|
|
131
185
|
if (!loaded.exists) {
|
|
132
|
-
|
|
133
|
-
|
|
186
|
+
logInfo(`No leak config found at ${loaded.path}`);
|
|
187
|
+
logInfo("Run `leak config` to initialize your config file.");
|
|
134
188
|
return;
|
|
135
189
|
}
|
|
136
190
|
|
|
137
191
|
const redacted = redactConfig(loaded.config);
|
|
138
|
-
console.log(
|
|
192
|
+
console.log(outUi.section("Leak Config"));
|
|
193
|
+
for (const line of outUi.formatRows([{ key: "path", value: loaded.path }])) {
|
|
194
|
+
console.log(line);
|
|
195
|
+
}
|
|
196
|
+
console.log("");
|
|
139
197
|
console.log(JSON.stringify(redacted, null, 2));
|
|
140
198
|
}
|
|
141
199
|
|
|
@@ -158,6 +216,10 @@ function buildEnvScaffold(defaults) {
|
|
|
158
216
|
`CHAIN_ID=${defaults.chainId || "eip155:84532"}`,
|
|
159
217
|
`WINDOW_SECONDS=${windowSeconds ?? 3600}`,
|
|
160
218
|
"",
|
|
219
|
+
"# Access control",
|
|
220
|
+
`ACCESS_MODE=${defaults.accessMode || DEFAULT_ACCESS_MODE}`,
|
|
221
|
+
`DOWNLOAD_CODE_HASH=${defaults.downloadCodeHash || ""}`,
|
|
222
|
+
"",
|
|
161
223
|
"# Required when FACILITATOR_MODE=cdp_mainnet (Base mainnet path)",
|
|
162
224
|
`CDP_API_KEY_ID=${defaults.cdpApiKeyId || ""}`,
|
|
163
225
|
`CDP_API_KEY_SECRET=${defaults.cdpApiKeySecret || ""}`,
|
|
@@ -182,19 +244,19 @@ function buildEnvScaffold(defaults) {
|
|
|
182
244
|
function maybeWriteEnvScaffold(defaults) {
|
|
183
245
|
const envPath = path.resolve(process.cwd(), ".env");
|
|
184
246
|
if (fs.existsSync(envPath)) {
|
|
185
|
-
|
|
247
|
+
logWarn(`${envPath} already exists; skipping .env scaffold write.`);
|
|
186
248
|
return;
|
|
187
249
|
}
|
|
188
250
|
|
|
189
251
|
const body = buildEnvScaffold(defaults);
|
|
190
252
|
fs.writeFileSync(envPath, body);
|
|
191
|
-
|
|
253
|
+
logOk(`Wrote ${envPath}`);
|
|
192
254
|
}
|
|
193
255
|
|
|
194
256
|
async function runWizard({ writeEnv }) {
|
|
195
257
|
const loaded = readConfig();
|
|
196
258
|
if (loaded.error) {
|
|
197
|
-
|
|
259
|
+
logWarn(loaded.error);
|
|
198
260
|
}
|
|
199
261
|
|
|
200
262
|
const existing = loaded.config.defaults || {};
|
|
@@ -206,16 +268,12 @@ async function runWizard({ writeEnv }) {
|
|
|
206
268
|
"SELLER_PAY_TO (seller payout address)",
|
|
207
269
|
existing.sellerPayTo || "",
|
|
208
270
|
);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
} else {
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
sellerPayTo = await askWithDefault(rl, "SELLER_PAY_TO (seller payout address)", sellerPayTo);
|
|
271
|
+
sellerPayTo = String(sellerPayTo || "").trim();
|
|
272
|
+
while (sellerPayTo && !isAddress(sellerPayTo)) {
|
|
273
|
+
logError("Invalid SELLER_PAY_TO. Expected a valid Ethereum address (0x + 40 hex chars).");
|
|
274
|
+
sellerPayTo = String(
|
|
275
|
+
await askWithDefault(rl, "SELLER_PAY_TO (seller payout address)", sellerPayTo),
|
|
276
|
+
).trim();
|
|
219
277
|
}
|
|
220
278
|
|
|
221
279
|
let chainIdInput = await askWithDefault(
|
|
@@ -229,7 +287,7 @@ async function runWizard({ writeEnv }) {
|
|
|
229
287
|
chainId = resolveSupportedChain(chainIdInput).caip2;
|
|
230
288
|
break;
|
|
231
289
|
} catch (err) {
|
|
232
|
-
|
|
290
|
+
logError(err.message || String(err));
|
|
233
291
|
chainIdInput = await askWithDefault(rl, "CHAIN_ID", chainIdInput || "eip155:84532");
|
|
234
292
|
}
|
|
235
293
|
}
|
|
@@ -241,7 +299,7 @@ async function runWizard({ writeEnv }) {
|
|
|
241
299
|
);
|
|
242
300
|
facilitatorMode = facilitatorMode.toLowerCase();
|
|
243
301
|
while (!ALLOWED_FACILITATOR_MODES.has(facilitatorMode)) {
|
|
244
|
-
|
|
302
|
+
logError("Invalid FACILITATOR_MODE. Use: testnet or cdp_mainnet");
|
|
245
303
|
facilitatorMode = (await askWithDefault(rl, "FACILITATOR_MODE", "testnet")).toLowerCase();
|
|
246
304
|
}
|
|
247
305
|
|
|
@@ -265,7 +323,7 @@ async function runWizard({ writeEnv }) {
|
|
|
265
323
|
existing.cdpApiKeyId || "",
|
|
266
324
|
);
|
|
267
325
|
while (!cdpApiKeyId) {
|
|
268
|
-
|
|
326
|
+
logError("CDP_API_KEY_ID is required when FACILITATOR_MODE=cdp_mainnet");
|
|
269
327
|
cdpApiKeyId = await askWithDefault(rl, "CDP_API_KEY_ID", "");
|
|
270
328
|
}
|
|
271
329
|
|
|
@@ -275,7 +333,7 @@ async function runWizard({ writeEnv }) {
|
|
|
275
333
|
existing.cdpApiKeySecret || "",
|
|
276
334
|
);
|
|
277
335
|
while (!cdpApiKeySecret) {
|
|
278
|
-
|
|
336
|
+
logError("CDP_API_KEY_SECRET is required when FACILITATOR_MODE=cdp_mainnet");
|
|
279
337
|
cdpApiKeySecret = await askWithDefault(rl, "CDP_API_KEY_SECRET", "");
|
|
280
338
|
}
|
|
281
339
|
}
|
|
@@ -287,12 +345,50 @@ async function runWizard({ writeEnv }) {
|
|
|
287
345
|
);
|
|
288
346
|
confirmationPolicy = confirmationPolicy.toLowerCase();
|
|
289
347
|
while (!ALLOWED_CONFIRMATION_POLICIES.has(confirmationPolicy)) {
|
|
290
|
-
|
|
348
|
+
logError("Invalid CONFIRMATION_POLICY. Use: confirmed or optimistic");
|
|
291
349
|
confirmationPolicy = (
|
|
292
350
|
await askWithDefault(rl, "CONFIRMATION_POLICY", "confirmed")
|
|
293
351
|
).toLowerCase();
|
|
294
352
|
}
|
|
295
353
|
|
|
354
|
+
let accessMode = await askWithDefault(
|
|
355
|
+
rl,
|
|
356
|
+
`ACCESS_MODE (${ACCESS_MODE_VALUES.join("|")})`,
|
|
357
|
+
existing.accessMode || DEFAULT_ACCESS_MODE,
|
|
358
|
+
);
|
|
359
|
+
accessMode = accessMode.toLowerCase();
|
|
360
|
+
while (!isValidAccessMode(accessMode)) {
|
|
361
|
+
logError(`Invalid ACCESS_MODE. Use one of: ${ACCESS_MODE_VALUES.join(", ")}`);
|
|
362
|
+
accessMode = (
|
|
363
|
+
await askWithDefault(
|
|
364
|
+
rl,
|
|
365
|
+
"ACCESS_MODE",
|
|
366
|
+
existing.accessMode || DEFAULT_ACCESS_MODE,
|
|
367
|
+
)
|
|
368
|
+
).toLowerCase();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (accessModeRequiresPayment(accessMode)) {
|
|
372
|
+
while (!sellerPayTo) {
|
|
373
|
+
logError("SELLER_PAY_TO is required for payment access modes.");
|
|
374
|
+
sellerPayTo = String(
|
|
375
|
+
await askWithDefault(rl, "SELLER_PAY_TO (seller payout address)", ""),
|
|
376
|
+
).trim();
|
|
377
|
+
}
|
|
378
|
+
while (!isAddress(sellerPayTo)) {
|
|
379
|
+
logError("Invalid SELLER_PAY_TO. Expected a valid Ethereum address (0x + 40 hex chars).");
|
|
380
|
+
sellerPayTo = String(
|
|
381
|
+
await askWithDefault(rl, "SELLER_PAY_TO (seller payout address)", sellerPayTo),
|
|
382
|
+
).trim();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const downloadCodeHash = await askDownloadCodeHash(
|
|
387
|
+
rl,
|
|
388
|
+
existing.downloadCodeHash || "",
|
|
389
|
+
accessMode,
|
|
390
|
+
);
|
|
391
|
+
|
|
296
392
|
const priceUsd = await askWithDefault(
|
|
297
393
|
rl,
|
|
298
394
|
"PRICE_USD",
|
|
@@ -311,7 +407,7 @@ async function runWizard({ writeEnv }) {
|
|
|
311
407
|
);
|
|
312
408
|
let port = parsePositiveInt(portRaw);
|
|
313
409
|
while (port === null) {
|
|
314
|
-
|
|
410
|
+
logError("PORT must be a positive integer");
|
|
315
411
|
portRaw = await askWithDefault(rl, "PORT", "4021");
|
|
316
412
|
port = parsePositiveInt(portRaw);
|
|
317
413
|
}
|
|
@@ -323,7 +419,7 @@ async function runWizard({ writeEnv }) {
|
|
|
323
419
|
);
|
|
324
420
|
let endedWindowSeconds = parseNonNegativeInt(endedWindowRaw);
|
|
325
421
|
while (endedWindowSeconds === null) {
|
|
326
|
-
|
|
422
|
+
logError("ENDED_WINDOW_SECONDS must be a non-negative integer");
|
|
327
423
|
endedWindowRaw = await askWithDefault(rl, "ENDED_WINDOW_SECONDS", "0");
|
|
328
424
|
endedWindowSeconds = parseNonNegativeInt(endedWindowRaw);
|
|
329
425
|
}
|
|
@@ -345,10 +441,13 @@ async function runWizard({ writeEnv }) {
|
|
|
345
441
|
endedWindowSeconds,
|
|
346
442
|
ogTitle,
|
|
347
443
|
ogDescription,
|
|
444
|
+
accessMode,
|
|
445
|
+
downloadCodeHash,
|
|
348
446
|
};
|
|
349
447
|
|
|
350
448
|
const written = writeConfig({ version: 1, defaults });
|
|
351
|
-
|
|
449
|
+
logOk(`Saved ${written.path}`);
|
|
450
|
+
console.log("");
|
|
352
451
|
console.log(JSON.stringify(redactConfig(written.config), null, 2));
|
|
353
452
|
|
|
354
453
|
if (writeEnv) {
|
|
@@ -376,6 +475,7 @@ async function main() {
|
|
|
376
475
|
}
|
|
377
476
|
|
|
378
477
|
main().catch((err) => {
|
|
379
|
-
|
|
478
|
+
const detail = err?.stack || err?.message || String(err);
|
|
479
|
+
logError(detail);
|
|
380
480
|
process.exit(1);
|
|
381
481
|
});
|
package/scripts/config_store.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
isValidAccessMode,
|
|
6
|
+
DEFAULT_ACCESS_MODE,
|
|
7
|
+
} from "../src/access_mode.js";
|
|
8
|
+
import { isValidDownloadCodeHash } from "../src/download_code.js";
|
|
4
9
|
|
|
5
10
|
export const CONFIG_VERSION = 1;
|
|
6
11
|
export const DEFAULT_TESTNET_FACILITATOR_URL = "https://x402.org/facilitator";
|
|
@@ -99,10 +104,24 @@ function normalizeDefaults(rawDefaults) {
|
|
|
99
104
|
const ogDescription = trimString(rawDefaults.ogDescription);
|
|
100
105
|
if (ogDescription) defaults.ogDescription = ogDescription;
|
|
101
106
|
|
|
107
|
+
const accessMode = trimString(rawDefaults.accessMode).toLowerCase();
|
|
108
|
+
if (isValidAccessMode(accessMode)) {
|
|
109
|
+
defaults.accessMode = accessMode;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const downloadCodeHash = trimString(rawDefaults.downloadCodeHash);
|
|
113
|
+
if (downloadCodeHash && isValidDownloadCodeHash(downloadCodeHash)) {
|
|
114
|
+
defaults.downloadCodeHash = downloadCodeHash;
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
if (defaults.facilitatorMode && !defaults.facilitatorUrl) {
|
|
103
118
|
defaults.facilitatorUrl = defaultFacilitatorUrlForMode(defaults.facilitatorMode);
|
|
104
119
|
}
|
|
105
120
|
|
|
121
|
+
if (!defaults.accessMode) {
|
|
122
|
+
defaults.accessMode = DEFAULT_ACCESS_MODE;
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
return defaults;
|
|
107
126
|
}
|
|
108
127
|
|
|
@@ -194,5 +213,9 @@ export function redactConfig(config) {
|
|
|
194
213
|
copy.defaults.cdpApiKeyId = redactSecret(copy.defaults.cdpApiKeyId);
|
|
195
214
|
}
|
|
196
215
|
|
|
216
|
+
if (copy.defaults.downloadCodeHash) {
|
|
217
|
+
copy.defaults.downloadCodeHash = redactSecret(copy.defaults.downloadCodeHash);
|
|
218
|
+
}
|
|
219
|
+
|
|
197
220
|
return copy;
|
|
198
221
|
}
|