satgate 1.5.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 +21 -0
- package/README.md +207 -0
- package/dist/bin/satgate.d.ts +3 -0
- package/dist/bin/satgate.d.ts.map +1 -0
- package/dist/bin/satgate.js +7 -0
- package/dist/bin/satgate.js.map +1 -0
- package/dist/src/auth/allowlist.d.ts +24 -0
- package/dist/src/auth/allowlist.d.ts.map +1 -0
- package/dist/src/auth/allowlist.js +130 -0
- package/dist/src/auth/allowlist.js.map +1 -0
- package/dist/src/auth/middleware.d.ts +15 -0
- package/dist/src/auth/middleware.d.ts.map +1 -0
- package/dist/src/auth/middleware.js +29 -0
- package/dist/src/auth/middleware.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +275 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +133 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +237 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/discovery/llms-txt.d.ts +10 -0
- package/dist/src/discovery/llms-txt.d.ts.map +1 -0
- package/dist/src/discovery/llms-txt.js +25 -0
- package/dist/src/discovery/llms-txt.js.map +1 -0
- package/dist/src/discovery/openapi.d.ts +8 -0
- package/dist/src/discovery/openapi.d.ts.map +1 -0
- package/dist/src/discovery/openapi.js +116 -0
- package/dist/src/discovery/openapi.js.map +1 -0
- package/dist/src/discovery/well-known.d.ts +22 -0
- package/dist/src/discovery/well-known.d.ts.map +1 -0
- package/dist/src/discovery/well-known.js +44 -0
- package/dist/src/discovery/well-known.js.map +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lightning.d.ts +12 -0
- package/dist/src/lightning.d.ts.map +1 -0
- package/dist/src/lightning.js +38 -0
- package/dist/src/lightning.js.map +1 -0
- package/dist/src/logger.d.ts +16 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +99 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/proxy/capacity.d.ts +15 -0
- package/dist/src/proxy/capacity.d.ts.map +1 -0
- package/dist/src/proxy/capacity.js +28 -0
- package/dist/src/proxy/capacity.js.map +1 -0
- package/dist/src/proxy/handler.d.ts +27 -0
- package/dist/src/proxy/handler.d.ts.map +1 -0
- package/dist/src/proxy/handler.js +165 -0
- package/dist/src/proxy/handler.js.map +1 -0
- package/dist/src/proxy/pricing.d.ts +17 -0
- package/dist/src/proxy/pricing.d.ts.map +1 -0
- package/dist/src/proxy/pricing.js +42 -0
- package/dist/src/proxy/pricing.js.map +1 -0
- package/dist/src/proxy/streaming.d.ts +12 -0
- package/dist/src/proxy/streaming.d.ts.map +1 -0
- package/dist/src/proxy/streaming.js +68 -0
- package/dist/src/proxy/streaming.js.map +1 -0
- package/dist/src/proxy/token-counter.d.ts +22 -0
- package/dist/src/proxy/token-counter.d.ts.map +1 -0
- package/dist/src/proxy/token-counter.js +66 -0
- package/dist/src/proxy/token-counter.js.map +1 -0
- package/dist/src/server.d.ts +9 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +239 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/tunnel.d.ts +26 -0
- package/dist/src/tunnel.d.ts.map +1 -0
- package/dist/src/tunnel.js +78 -0
- package/dist/src/tunnel.js.map +1 -0
- package/dist/src/x402/facilitator.d.ts +7 -0
- package/dist/src/x402/facilitator.d.ts.map +1 -0
- package/dist/src/x402/facilitator.js +33 -0
- package/dist/src/x402/facilitator.js.map +1 -0
- package/package.json +70 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import { serve } from '@hono/node-server';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
import { createTokenTollServer } from './server.js';
|
|
6
|
+
import { createLightningBackend } from './lightning.js';
|
|
7
|
+
import { startTunnel, stopTunnel } from './tunnel.js';
|
|
8
|
+
import { createLogger } from './logger.js';
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const args = {};
|
|
11
|
+
for (let i = 2; i < argv.length; i++) {
|
|
12
|
+
switch (argv[i]) {
|
|
13
|
+
case '--upstream':
|
|
14
|
+
args.upstream = argv[++i];
|
|
15
|
+
break;
|
|
16
|
+
case '--port':
|
|
17
|
+
args.port = parseInt(argv[++i], 10);
|
|
18
|
+
break;
|
|
19
|
+
case '--config':
|
|
20
|
+
args.config = argv[++i];
|
|
21
|
+
break;
|
|
22
|
+
case '--price':
|
|
23
|
+
args.price = parseInt(argv[++i], 10);
|
|
24
|
+
break;
|
|
25
|
+
case '--max-concurrent':
|
|
26
|
+
args.maxConcurrent = parseInt(argv[++i], 10);
|
|
27
|
+
break;
|
|
28
|
+
case '--storage':
|
|
29
|
+
args.storage = argv[++i];
|
|
30
|
+
break;
|
|
31
|
+
case '--db-path':
|
|
32
|
+
args.dbPath = argv[++i];
|
|
33
|
+
break;
|
|
34
|
+
case '--free-tier':
|
|
35
|
+
args.freeTier = parseInt(argv[++i], 10);
|
|
36
|
+
break;
|
|
37
|
+
case '--trust-proxy':
|
|
38
|
+
args.trustProxy = true;
|
|
39
|
+
break;
|
|
40
|
+
case '--lightning':
|
|
41
|
+
args.lightning = argv[++i];
|
|
42
|
+
break;
|
|
43
|
+
case '--lightning-url':
|
|
44
|
+
args.lightningUrl = argv[++i];
|
|
45
|
+
break;
|
|
46
|
+
case '--lightning-key':
|
|
47
|
+
args.lightningKey = argv[++i];
|
|
48
|
+
break;
|
|
49
|
+
case '--auth':
|
|
50
|
+
args.authMode = argv[++i];
|
|
51
|
+
break;
|
|
52
|
+
case '--allowlist':
|
|
53
|
+
args.allowlist = argv[++i].split(',');
|
|
54
|
+
break;
|
|
55
|
+
case '--allowlist-file':
|
|
56
|
+
args.allowlistFile = argv[++i];
|
|
57
|
+
break;
|
|
58
|
+
case '--no-tunnel':
|
|
59
|
+
args.noTunnel = true;
|
|
60
|
+
break;
|
|
61
|
+
case '--root-key':
|
|
62
|
+
args.rootKey = argv[++i];
|
|
63
|
+
break;
|
|
64
|
+
case '--verbose':
|
|
65
|
+
args.verbose = true;
|
|
66
|
+
break;
|
|
67
|
+
case '--log-format':
|
|
68
|
+
args.logFormat = argv[++i];
|
|
69
|
+
break;
|
|
70
|
+
case '--token-price':
|
|
71
|
+
args.tokenPrice = parseInt(argv[++i], 10);
|
|
72
|
+
break;
|
|
73
|
+
case '--model-price':
|
|
74
|
+
args.modelPrice = [...(args.modelPrice ?? []), argv[++i]];
|
|
75
|
+
break;
|
|
76
|
+
case '-h':
|
|
77
|
+
case '--help':
|
|
78
|
+
printHelp();
|
|
79
|
+
process.exit(0);
|
|
80
|
+
case '-v':
|
|
81
|
+
case '--version':
|
|
82
|
+
printVersion();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
default:
|
|
85
|
+
console.error(`Unknown option: ${argv[i]}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return args;
|
|
90
|
+
}
|
|
91
|
+
function loadFileConfig(path) {
|
|
92
|
+
const configPath = path
|
|
93
|
+
?? (existsSync('satgate.json') ? 'satgate.json' : undefined)
|
|
94
|
+
?? (existsSync('satgate.yaml') ? 'satgate.yaml' : undefined);
|
|
95
|
+
if (!configPath)
|
|
96
|
+
return {};
|
|
97
|
+
try {
|
|
98
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
99
|
+
if (configPath.endsWith('.yaml') || configPath.endsWith('.yml')) {
|
|
100
|
+
return yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
101
|
+
}
|
|
102
|
+
return JSON.parse(content);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
console.warn(`[satgate] Could not read config file: ${configPath}`);
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function printHelp() {
|
|
110
|
+
console.log(`
|
|
111
|
+
satgate - Lightning-paid AI inference
|
|
112
|
+
|
|
113
|
+
Usage: satgate [options]
|
|
114
|
+
|
|
115
|
+
Upstream:
|
|
116
|
+
--upstream <url> Upstream API URL (default: auto-detect Ollama on :11434)
|
|
117
|
+
|
|
118
|
+
Lightning:
|
|
119
|
+
--lightning <backend> phoenixd | lnbits | lnd | cln
|
|
120
|
+
--lightning-url <url> Backend URL (defaults per backend)
|
|
121
|
+
--lightning-key <secret> Password / API key / macaroon / rune
|
|
122
|
+
|
|
123
|
+
Auth:
|
|
124
|
+
--auth <mode> open | lightning | allowlist (inferred from context)
|
|
125
|
+
--allowlist <keys> Comma-separated npubs or shared secrets
|
|
126
|
+
--allowlist-file <path> File with one key per line
|
|
127
|
+
|
|
128
|
+
Pricing:
|
|
129
|
+
--price <sats> Sats per request (flat pricing)
|
|
130
|
+
--token-price <sats> Sats per 1k tokens (per-token pricing)
|
|
131
|
+
--model-price <model:sats> Per-model token price (repeatable)
|
|
132
|
+
|
|
133
|
+
Server:
|
|
134
|
+
--port <number> Listen port (default: 3000)
|
|
135
|
+
--no-tunnel Skip Cloudflare Tunnel
|
|
136
|
+
|
|
137
|
+
Storage:
|
|
138
|
+
--storage <type> memory | sqlite (default: memory)
|
|
139
|
+
--db-path <path> SQLite path (default: ./satgate.db)
|
|
140
|
+
|
|
141
|
+
Other:
|
|
142
|
+
--config <path> Config file (JSON or YAML)
|
|
143
|
+
--max-concurrent <n> Max concurrent inference requests
|
|
144
|
+
--free-tier <n> Free credits (sats) per IP per day (default: 0)
|
|
145
|
+
--trust-proxy Trust X-Forwarded-For headers
|
|
146
|
+
--root-key <key> Root key for macaroon minting
|
|
147
|
+
--verbose Show extra fields in log output
|
|
148
|
+
--log-format <format> pretty | json (default: pretty)
|
|
149
|
+
-h, --help Show help
|
|
150
|
+
-v, --version Show version
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
153
|
+
function printVersion() {
|
|
154
|
+
try {
|
|
155
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
156
|
+
console.log(`satgate v${pkg.version}`);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
console.log('satgate (unknown version)');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export async function main(argv = process.argv) {
|
|
163
|
+
const args = parseArgs(argv);
|
|
164
|
+
const fileConfig = loadFileConfig(args.config);
|
|
165
|
+
// Auto-detect Ollama if no upstream specified
|
|
166
|
+
let ollamaAutoDetected = false;
|
|
167
|
+
if (!args.upstream && !process.env.UPSTREAM_URL && !fileConfig?.upstream) {
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch('http://localhost:11434/v1/models', {
|
|
170
|
+
signal: AbortSignal.timeout(2000),
|
|
171
|
+
});
|
|
172
|
+
if (res.ok) {
|
|
173
|
+
args.upstream = 'http://localhost:11434';
|
|
174
|
+
ollamaAutoDetected = true;
|
|
175
|
+
console.log('[satgate] Ollama detected on :11434');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Ollama not found
|
|
180
|
+
}
|
|
181
|
+
if (!args.upstream && !process.env.UPSTREAM_URL && !fileConfig?.upstream) {
|
|
182
|
+
console.error('[satgate] No upstream detected. Ollama not found on :11434.');
|
|
183
|
+
console.error('[satgate] Either start Ollama or pass --upstream <url>');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Load allowlist file before config validation so --allowlist-file works standalone
|
|
188
|
+
if (args.allowlistFile) {
|
|
189
|
+
const content = readFileSync(args.allowlistFile, 'utf-8');
|
|
190
|
+
const entries = content.split('\n').map(l => l.trim()).filter(Boolean);
|
|
191
|
+
args.allowlist = [...(args.allowlist ?? []), ...entries];
|
|
192
|
+
}
|
|
193
|
+
const config = loadConfig(args, process.env, fileConfig);
|
|
194
|
+
const logger = createLogger({ format: config.logFormat, verbose: config.verbose });
|
|
195
|
+
// Auto-detect models from upstream (retry to handle startup races with Ollama)
|
|
196
|
+
let models = [];
|
|
197
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
198
|
+
try {
|
|
199
|
+
const res = await fetch(`${config.upstream}/v1/models`, {
|
|
200
|
+
signal: AbortSignal.timeout(5000),
|
|
201
|
+
});
|
|
202
|
+
const body = await res.json();
|
|
203
|
+
models = body.data?.map(m => m.id) ?? [];
|
|
204
|
+
if (models.length > 0)
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Fall through to retry
|
|
209
|
+
}
|
|
210
|
+
if (attempt < 3) {
|
|
211
|
+
console.warn(`[satgate] Upstream not ready, retrying model detection (${attempt}/3)...`);
|
|
212
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (models.length === 0) {
|
|
216
|
+
console.warn('[satgate] Could not auto-detect models from upstream');
|
|
217
|
+
}
|
|
218
|
+
const backend = createLightningBackend(config);
|
|
219
|
+
const { app } = createTokenTollServer({ ...config, models, backend, logger });
|
|
220
|
+
let version = '0.1.0';
|
|
221
|
+
try {
|
|
222
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
223
|
+
version = pkg.version;
|
|
224
|
+
}
|
|
225
|
+
catch { /* ignore */ }
|
|
226
|
+
let tunnelResult;
|
|
227
|
+
const server = serve({ fetch: app.fetch, port: config.port }, async () => {
|
|
228
|
+
const lightningLabel = config.lightning
|
|
229
|
+
? `${config.lightning} (${config.lightningUrl})`
|
|
230
|
+
: 'none (free mode)';
|
|
231
|
+
const authLabel = config.authMode === 'lightning'
|
|
232
|
+
? 'lightning (pay-per-request)'
|
|
233
|
+
: config.authMode === 'allowlist'
|
|
234
|
+
? `allowlist (${config.allowlist.length} identities)`
|
|
235
|
+
: 'open';
|
|
236
|
+
const priceLabel = config.flatPricing
|
|
237
|
+
? `${config.price} sat/request`
|
|
238
|
+
: `${config.pricing.default} sat/1k tokens`;
|
|
239
|
+
logger.info(`satgate v${version}`);
|
|
240
|
+
logger.info(`Upstream: ${config.upstream}${ollamaAutoDetected ? ' (auto-detected)' : ''}`);
|
|
241
|
+
logger.info(`Models: ${models.length > 0 ? models.join(', ') : '(none detected)'}`);
|
|
242
|
+
logger.info(`Lightning: ${lightningLabel}`);
|
|
243
|
+
logger.info(`Auth: ${authLabel}`);
|
|
244
|
+
logger.info(`Price: ${priceLabel}`);
|
|
245
|
+
logger.info(`Storage: ${config.storage}${config.storage === 'memory' ? ' (ephemeral)' : ''}`);
|
|
246
|
+
logger.info(`Local: http://localhost:${config.port}`);
|
|
247
|
+
if (config.rootKeyGenerated) {
|
|
248
|
+
logger.warn('Using auto-generated root key (not persisted across restarts)');
|
|
249
|
+
logger.warn('Set ROOT_KEY env var for production use');
|
|
250
|
+
}
|
|
251
|
+
logger.info('/.well-known/l402 | /llms.txt | /health');
|
|
252
|
+
// Start tunnel if enabled
|
|
253
|
+
if (config.tunnel) {
|
|
254
|
+
tunnelResult = await startTunnel(config.port);
|
|
255
|
+
if (tunnelResult.url) {
|
|
256
|
+
logger.info(`Public: ${tunnelResult.url}`);
|
|
257
|
+
}
|
|
258
|
+
else if (tunnelResult.error) {
|
|
259
|
+
logger.warn(`Tunnel: ${tunnelResult.error}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
logger.info('Tunnel: disabled');
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
const shutdown = () => {
|
|
267
|
+
if (tunnelResult?.process)
|
|
268
|
+
stopTunnel(tunnelResult.process);
|
|
269
|
+
server.close();
|
|
270
|
+
process.exit(0);
|
|
271
|
+
};
|
|
272
|
+
process.on('SIGINT', shutdown);
|
|
273
|
+
process.on('SIGTERM', shutdown);
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { LightningBackend } from '@thecryptodonkey/toll-booth';
|
|
2
|
+
import type { Logger } from './logger.js';
|
|
3
|
+
export interface ModelPricing {
|
|
4
|
+
/** Sats per 1k tokens for each model. */
|
|
5
|
+
models: Record<string, number>;
|
|
6
|
+
/** Default sats per 1k tokens for unlisted models. */
|
|
7
|
+
default: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TokenTollConfig {
|
|
10
|
+
upstream: string;
|
|
11
|
+
port: number;
|
|
12
|
+
rootKey: string;
|
|
13
|
+
rootKeyGenerated: boolean;
|
|
14
|
+
storage: 'memory' | 'sqlite';
|
|
15
|
+
dbPath: string;
|
|
16
|
+
pricing: ModelPricing;
|
|
17
|
+
freeTier: {
|
|
18
|
+
creditsPerDay: number;
|
|
19
|
+
};
|
|
20
|
+
capacity: {
|
|
21
|
+
maxConcurrent: number;
|
|
22
|
+
};
|
|
23
|
+
tiers: Array<{
|
|
24
|
+
amountSats: number;
|
|
25
|
+
creditSats: number;
|
|
26
|
+
label: string;
|
|
27
|
+
}>;
|
|
28
|
+
trustProxy: boolean;
|
|
29
|
+
/** Estimated cost in sats to hold per request (deducted upfront, reconciled after). */
|
|
30
|
+
estimatedCostSats: number;
|
|
31
|
+
/** Maximum request body size in bytes. */
|
|
32
|
+
maxBodySize: number;
|
|
33
|
+
/** Auto-detected model IDs from upstream. */
|
|
34
|
+
models?: string[];
|
|
35
|
+
lightning?: 'phoenixd' | 'lnbits' | 'lnd' | 'cln';
|
|
36
|
+
lightningUrl?: string;
|
|
37
|
+
lightningKey?: string;
|
|
38
|
+
authMode: 'open' | 'lightning' | 'allowlist';
|
|
39
|
+
allowlist: string[];
|
|
40
|
+
flatPricing: boolean;
|
|
41
|
+
/** Flat per-request price in sats (only used when flatPricing is true). */
|
|
42
|
+
price: number;
|
|
43
|
+
tunnel: boolean;
|
|
44
|
+
/** Lightning backend instance (created externally, threaded to server). */
|
|
45
|
+
backend?: LightningBackend;
|
|
46
|
+
x402?: {
|
|
47
|
+
receiverAddress: string;
|
|
48
|
+
network: string;
|
|
49
|
+
facilitatorUrl?: string;
|
|
50
|
+
facilitatorKey?: string;
|
|
51
|
+
asset?: string;
|
|
52
|
+
creditMode?: boolean;
|
|
53
|
+
};
|
|
54
|
+
defaultPriceUsd?: number;
|
|
55
|
+
verbose: boolean;
|
|
56
|
+
logFormat: 'pretty' | 'json';
|
|
57
|
+
logger?: Logger;
|
|
58
|
+
/** Human-readable service name for Lightning invoice descriptions. Defaults to 'toll-booth'. */
|
|
59
|
+
serviceName?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface CliArgs {
|
|
62
|
+
upstream?: string;
|
|
63
|
+
port?: number;
|
|
64
|
+
config?: string;
|
|
65
|
+
price?: number;
|
|
66
|
+
maxConcurrent?: number;
|
|
67
|
+
storage?: string;
|
|
68
|
+
dbPath?: string;
|
|
69
|
+
freeTier?: number;
|
|
70
|
+
trustProxy?: boolean;
|
|
71
|
+
rootKey?: string;
|
|
72
|
+
lightning?: string;
|
|
73
|
+
lightningUrl?: string;
|
|
74
|
+
lightningKey?: string;
|
|
75
|
+
authMode?: string;
|
|
76
|
+
allowlist?: string[];
|
|
77
|
+
allowlistFile?: string;
|
|
78
|
+
noTunnel?: boolean;
|
|
79
|
+
verbose?: boolean;
|
|
80
|
+
logFormat?: string;
|
|
81
|
+
tokenPrice?: number;
|
|
82
|
+
modelPrice?: string[];
|
|
83
|
+
}
|
|
84
|
+
export interface FileConfig {
|
|
85
|
+
upstream?: string;
|
|
86
|
+
port?: number;
|
|
87
|
+
rootKey?: string;
|
|
88
|
+
storage?: string;
|
|
89
|
+
dbPath?: string;
|
|
90
|
+
pricing?: {
|
|
91
|
+
default?: number;
|
|
92
|
+
models?: Record<string, number>;
|
|
93
|
+
};
|
|
94
|
+
freeTier?: {
|
|
95
|
+
creditsPerDay?: number;
|
|
96
|
+
requestsPerDay?: number;
|
|
97
|
+
};
|
|
98
|
+
capacity?: {
|
|
99
|
+
maxConcurrent?: number;
|
|
100
|
+
};
|
|
101
|
+
tiers?: Array<{
|
|
102
|
+
amountSats: number;
|
|
103
|
+
creditSats: number;
|
|
104
|
+
label: string;
|
|
105
|
+
}>;
|
|
106
|
+
trustProxy?: boolean;
|
|
107
|
+
estimatedCostSats?: number;
|
|
108
|
+
maxBodySize?: number;
|
|
109
|
+
lightning?: string;
|
|
110
|
+
lightningUrl?: string;
|
|
111
|
+
lightningKey?: string;
|
|
112
|
+
auth?: string;
|
|
113
|
+
allowlist?: string[];
|
|
114
|
+
price?: number;
|
|
115
|
+
tunnel?: boolean;
|
|
116
|
+
x402?: {
|
|
117
|
+
receiverAddress?: string;
|
|
118
|
+
network?: string;
|
|
119
|
+
facilitatorUrl?: string;
|
|
120
|
+
facilitatorKey?: string;
|
|
121
|
+
asset?: string;
|
|
122
|
+
creditMode?: boolean;
|
|
123
|
+
};
|
|
124
|
+
defaultPriceUsd?: number;
|
|
125
|
+
verbose?: boolean;
|
|
126
|
+
logFormat?: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Loads and validates configuration.
|
|
130
|
+
* Precedence: CLI args > env vars > config file > defaults.
|
|
131
|
+
*/
|
|
132
|
+
export declare function loadConfig(args: CliArgs, env?: Record<string, string | undefined>, file?: FileConfig): TokenTollConfig;
|
|
133
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,OAAO,CAAA;IACzB,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,YAAY,CAAA;IACrB,QAAQ,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAA;IACnC,QAAQ,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAA;IACnC,KAAK,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvE,UAAU,EAAE,OAAO,CAAA;IACnB,uFAAuF;IACvF,iBAAiB,EAAE,MAAM,CAAA;IACzB,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAA;IACnB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IAEjB,SAAS,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAA;IACjD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,CAAA;IAC5C,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,WAAW,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;IACf,2EAA2E;IAC3E,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,IAAI,CAAC,EAAE;QACL,eAAe,EAAE,MAAM,CAAA;QACvB,OAAO,EAAE,MAAM,CAAA;QACf,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAA;IACD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IAC/D,QAAQ,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,QAAQ,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE;QACL,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAA;IACD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AASD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,OAAO,EACb,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,EAC5C,IAAI,GAAE,UAAe,GACpB,eAAe,CAsPjB"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { resolve, relative } from 'node:path';
|
|
3
|
+
const LIGHTNING_URL_DEFAULTS = {
|
|
4
|
+
phoenixd: 'http://localhost:9740',
|
|
5
|
+
lnbits: 'https://legend.lnbits.com',
|
|
6
|
+
lnd: 'https://localhost:8080',
|
|
7
|
+
cln: 'http://localhost:3010',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Loads and validates configuration.
|
|
11
|
+
* Precedence: CLI args > env vars > config file > defaults.
|
|
12
|
+
*/
|
|
13
|
+
export function loadConfig(args, env = {}, file = {}) {
|
|
14
|
+
const upstream = args.upstream ?? env.UPSTREAM_URL ?? file.upstream;
|
|
15
|
+
if (!upstream) {
|
|
16
|
+
throw new Error('upstream URL is required (--upstream or UPSTREAM_URL)');
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(upstream);
|
|
20
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
21
|
+
throw new Error('upstream URL must use http or https');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
if (e instanceof Error && e.message.includes('http or https'))
|
|
26
|
+
throw e;
|
|
27
|
+
throw new Error(`upstream URL is not a valid URL: ${upstream}`);
|
|
28
|
+
}
|
|
29
|
+
const portRaw = args.port ?? (env.PORT ? parseInt(env.PORT, 10) : undefined) ?? file.port ?? 3000;
|
|
30
|
+
if (!Number.isFinite(portRaw) || portRaw < 0 || portRaw > 65535) {
|
|
31
|
+
throw new Error(`Invalid port: ${portRaw} (must be 0–65535)`);
|
|
32
|
+
}
|
|
33
|
+
const port = portRaw;
|
|
34
|
+
const rootKeyRaw = args.rootKey ?? env.ROOT_KEY ?? file.rootKey;
|
|
35
|
+
const rootKeyGenerated = !rootKeyRaw;
|
|
36
|
+
const rootKey = rootKeyRaw ?? randomBytes(32).toString('hex');
|
|
37
|
+
const storageRaw = args.storage ?? env.STORAGE ?? file.storage ?? 'memory';
|
|
38
|
+
if (storageRaw !== 'memory' && storageRaw !== 'sqlite') {
|
|
39
|
+
throw new Error(`Invalid storage type: ${storageRaw} (must be 'memory' or 'sqlite')`);
|
|
40
|
+
}
|
|
41
|
+
const storage = storageRaw;
|
|
42
|
+
const dbPathRaw = args.dbPath ?? env.SATGATE_DB_PATH ?? file.dbPath ?? './satgate.db';
|
|
43
|
+
const resolvedDbPath = resolve(dbPathRaw);
|
|
44
|
+
const relFromCwd = relative(process.cwd(), resolvedDbPath);
|
|
45
|
+
if (relFromCwd.startsWith('..')) {
|
|
46
|
+
throw new Error(`dbPath must be within the working directory (got: ${dbPathRaw})`);
|
|
47
|
+
}
|
|
48
|
+
const dbPath = dbPathRaw;
|
|
49
|
+
// Pricing: three sources
|
|
50
|
+
// 1. File: pricing.default / pricing.models (per-token via config file)
|
|
51
|
+
// 2. CLI flat: --price / file.price (flat per-request)
|
|
52
|
+
// 3. CLI per-token: --token-price / --model-price (per-token via CLI)
|
|
53
|
+
const pricingDefault = (env.DEFAULT_PRICE ? parseInt(env.DEFAULT_PRICE, 10) : undefined)
|
|
54
|
+
?? file.pricing?.default
|
|
55
|
+
?? 1;
|
|
56
|
+
const pricing = {
|
|
57
|
+
default: pricingDefault,
|
|
58
|
+
models: { ...(file.pricing?.models ?? {}) },
|
|
59
|
+
};
|
|
60
|
+
// Flat pricing
|
|
61
|
+
const flatPrice = args.price ?? file.price;
|
|
62
|
+
if (flatPrice !== undefined && (!Number.isFinite(flatPrice) || flatPrice < 0)) {
|
|
63
|
+
throw new Error(`Invalid price: ${flatPrice} (must be a non-negative number)`);
|
|
64
|
+
}
|
|
65
|
+
// Per-token CLI pricing
|
|
66
|
+
const tokenPrice = args.tokenPrice
|
|
67
|
+
?? (env.SATGATE_TOKEN_PRICE ? parseInt(env.SATGATE_TOKEN_PRICE, 10) : undefined);
|
|
68
|
+
if (tokenPrice !== undefined && (!Number.isFinite(tokenPrice) || tokenPrice <= 0)) {
|
|
69
|
+
throw new Error(`Invalid --token-price: ${tokenPrice} (must be a positive integer)`);
|
|
70
|
+
}
|
|
71
|
+
// Model-price entries from CLI and env (CLI last = CLI wins on conflict)
|
|
72
|
+
const cliModelEntries = args.modelPrice ?? [];
|
|
73
|
+
const envModelEntries = env.SATGATE_MODEL_PRICE?.split(',').filter(Boolean) ?? [];
|
|
74
|
+
const allModelEntries = [...envModelEntries, ...cliModelEntries];
|
|
75
|
+
// Parse model-price entries (split on last colon to allow model IDs with colons)
|
|
76
|
+
const parsedModelPrices = {};
|
|
77
|
+
for (const raw of allModelEntries) {
|
|
78
|
+
const lastColonIdx = raw.lastIndexOf(':');
|
|
79
|
+
if (lastColonIdx === -1) {
|
|
80
|
+
throw new Error(`Invalid --model-price value: "${raw}" (expected <model>:<sats>)`);
|
|
81
|
+
}
|
|
82
|
+
const modelPart = raw.slice(0, lastColonIdx);
|
|
83
|
+
const ratePart = raw.slice(lastColonIdx + 1);
|
|
84
|
+
const rate = parseInt(ratePart, 10);
|
|
85
|
+
if (!Number.isFinite(rate) || rate <= 0) {
|
|
86
|
+
throw new Error(`Invalid --model-price value: "${raw}" (expected <model>:<sats>)`);
|
|
87
|
+
}
|
|
88
|
+
parsedModelPrices[modelPart] = rate;
|
|
89
|
+
}
|
|
90
|
+
const hasCliTokenPricing = tokenPrice !== undefined || allModelEntries.length > 0;
|
|
91
|
+
// Mutual exclusion: flat pricing vs per-token pricing
|
|
92
|
+
if (flatPrice !== undefined && hasCliTokenPricing) {
|
|
93
|
+
throw new Error('Cannot use --price (flat) and --token-price (per-token) together');
|
|
94
|
+
}
|
|
95
|
+
// Apply CLI per-token pricing overrides
|
|
96
|
+
if (hasCliTokenPricing) {
|
|
97
|
+
if (tokenPrice !== undefined) {
|
|
98
|
+
pricing.default = tokenPrice;
|
|
99
|
+
}
|
|
100
|
+
Object.assign(pricing.models, parsedModelPrices);
|
|
101
|
+
}
|
|
102
|
+
const hasPricingConfig = file.pricing !== undefined || hasCliTokenPricing;
|
|
103
|
+
const flatPricing = flatPrice !== undefined || !hasPricingConfig;
|
|
104
|
+
const price = flatPrice ?? 1;
|
|
105
|
+
if (env.FREE_TIER_REQUESTS && !env.FREE_TIER_CREDITS) {
|
|
106
|
+
console.warn('[satgate] WARNING: FREE_TIER_REQUESTS is deprecated, use FREE_TIER_CREDITS instead');
|
|
107
|
+
}
|
|
108
|
+
const freeTierCredits = args.freeTier
|
|
109
|
+
?? (env.FREE_TIER_CREDITS ? parseInt(env.FREE_TIER_CREDITS, 10) : undefined)
|
|
110
|
+
?? (env.FREE_TIER_REQUESTS ? parseInt(env.FREE_TIER_REQUESTS, 10) : undefined)
|
|
111
|
+
?? file.freeTier?.creditsPerDay
|
|
112
|
+
?? file.freeTier?.requestsPerDay
|
|
113
|
+
?? 0;
|
|
114
|
+
if (!Number.isFinite(freeTierCredits) || freeTierCredits < 0) {
|
|
115
|
+
throw new Error(`Invalid free tier value: ${freeTierCredits} (must be a non-negative integer)`);
|
|
116
|
+
}
|
|
117
|
+
const maxConcurrent = args.maxConcurrent
|
|
118
|
+
?? (env.MAX_CONCURRENT ? parseInt(env.MAX_CONCURRENT, 10) : undefined)
|
|
119
|
+
?? file.capacity?.maxConcurrent
|
|
120
|
+
?? 0;
|
|
121
|
+
if (!Number.isFinite(maxConcurrent) || maxConcurrent < 0) {
|
|
122
|
+
throw new Error(`Invalid max concurrent value: ${maxConcurrent} (must be a non-negative integer)`);
|
|
123
|
+
}
|
|
124
|
+
const trustProxy = args.trustProxy !== undefined
|
|
125
|
+
? args.trustProxy
|
|
126
|
+
: env.TRUST_PROXY !== undefined
|
|
127
|
+
? env.TRUST_PROXY === 'true'
|
|
128
|
+
: file.trustProxy ?? false;
|
|
129
|
+
const tiers = file.tiers ?? [];
|
|
130
|
+
const estimatedCostSats = env.SATGATE_ESTIMATED_COST
|
|
131
|
+
? parseInt(env.SATGATE_ESTIMATED_COST, 10)
|
|
132
|
+
: file.estimatedCostSats ?? Math.max(pricing.default * 2, 5);
|
|
133
|
+
const maxBodySize = file.maxBodySize ?? 10 * 1024 * 1024; // 10 MiB
|
|
134
|
+
// Lightning backend config
|
|
135
|
+
const VALID_BACKENDS = ['phoenixd', 'lnbits', 'lnd', 'cln'];
|
|
136
|
+
const lightningRaw = args.lightning ?? env.LIGHTNING_BACKEND ?? file.lightning;
|
|
137
|
+
if (lightningRaw && !VALID_BACKENDS.includes(lightningRaw)) {
|
|
138
|
+
throw new Error(`Invalid lightning backend: ${lightningRaw} (must be one of: ${VALID_BACKENDS.join(', ')})`);
|
|
139
|
+
}
|
|
140
|
+
const lightning = lightningRaw;
|
|
141
|
+
const lightningUrl = args.lightningUrl ?? env.LIGHTNING_URL ?? file.lightningUrl
|
|
142
|
+
?? (lightning ? LIGHTNING_URL_DEFAULTS[lightning] : undefined);
|
|
143
|
+
if (lightningUrl) {
|
|
144
|
+
try {
|
|
145
|
+
const parsed = new URL(lightningUrl);
|
|
146
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
147
|
+
throw new Error('lightning URL must use http or https');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
if (e instanceof Error && e.message.includes('http or https'))
|
|
152
|
+
throw e;
|
|
153
|
+
throw new Error(`lightning URL is not a valid URL: ${lightningUrl}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const lightningKey = args.lightningKey ?? env.LIGHTNING_KEY ?? file.lightningKey;
|
|
157
|
+
// Auth mode inference
|
|
158
|
+
const VALID_AUTH_MODES = ['open', 'lightning', 'allowlist'];
|
|
159
|
+
const explicitAuth = args.authMode ?? env.AUTH_MODE ?? file.auth;
|
|
160
|
+
if (explicitAuth && !VALID_AUTH_MODES.includes(explicitAuth)) {
|
|
161
|
+
throw new Error(`Invalid auth mode: ${explicitAuth} (must be one of: ${VALID_AUTH_MODES.join(', ')})`);
|
|
162
|
+
}
|
|
163
|
+
let authMode;
|
|
164
|
+
if (explicitAuth) {
|
|
165
|
+
authMode = explicitAuth;
|
|
166
|
+
if (authMode === 'lightning' && !lightning) {
|
|
167
|
+
throw new Error("auth mode 'lightning' requires --lightning <backend>");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
authMode = lightning ? 'lightning' : 'open';
|
|
172
|
+
}
|
|
173
|
+
// Allowlist
|
|
174
|
+
const allowlist = args.allowlist ?? file.allowlist ?? [];
|
|
175
|
+
if (authMode === 'allowlist' && allowlist.length === 0) {
|
|
176
|
+
throw new Error("auth mode 'allowlist' requires --allowlist <keys> or --allowlist-file <path>");
|
|
177
|
+
}
|
|
178
|
+
// Tunnel
|
|
179
|
+
const tunnelEnv = env.TUNNEL !== undefined ? env.TUNNEL !== 'false' : undefined;
|
|
180
|
+
const tunnel = args.noTunnel === true ? false : (tunnelEnv ?? file.tunnel ?? true);
|
|
181
|
+
// x402 stablecoin config
|
|
182
|
+
const x402Receiver = env.X402_RECEIVER ?? file.x402?.receiverAddress;
|
|
183
|
+
const x402Network = env.X402_NETWORK ?? file.x402?.network;
|
|
184
|
+
const x402 = x402Receiver && x402Network
|
|
185
|
+
? {
|
|
186
|
+
receiverAddress: x402Receiver,
|
|
187
|
+
network: x402Network,
|
|
188
|
+
facilitatorUrl: env.X402_FACILITATOR_URL ?? file.x402?.facilitatorUrl,
|
|
189
|
+
facilitatorKey: env.X402_FACILITATOR_KEY ?? file.x402?.facilitatorKey,
|
|
190
|
+
asset: env.X402_ASSET ?? file.x402?.asset,
|
|
191
|
+
creditMode: file.x402?.creditMode,
|
|
192
|
+
}
|
|
193
|
+
: undefined;
|
|
194
|
+
const defaultPriceUsd = env.DEFAULT_PRICE_USD
|
|
195
|
+
? parseInt(env.DEFAULT_PRICE_USD, 10)
|
|
196
|
+
: file.defaultPriceUsd;
|
|
197
|
+
// Logging
|
|
198
|
+
const verbose = args.verbose
|
|
199
|
+
?? (env.SATGATE_VERBOSE !== undefined ? env.SATGATE_VERBOSE === 'true' : undefined)
|
|
200
|
+
?? file.verbose
|
|
201
|
+
?? false;
|
|
202
|
+
const logFormatRaw = args.logFormat ?? env.SATGATE_LOG_FORMAT ?? file.logFormat ?? 'pretty';
|
|
203
|
+
if (logFormatRaw !== 'pretty' && logFormatRaw !== 'json') {
|
|
204
|
+
throw new Error(`Invalid log format: ${logFormatRaw} (must be 'pretty' or 'json')`);
|
|
205
|
+
}
|
|
206
|
+
const logFormat = logFormatRaw;
|
|
207
|
+
const serviceName = env.SATGATE_SERVICE_NAME ?? 'satgate';
|
|
208
|
+
return {
|
|
209
|
+
upstream: upstream.replace(/\/+$/, ''),
|
|
210
|
+
port,
|
|
211
|
+
rootKey,
|
|
212
|
+
rootKeyGenerated,
|
|
213
|
+
storage,
|
|
214
|
+
dbPath,
|
|
215
|
+
pricing,
|
|
216
|
+
freeTier: { creditsPerDay: freeTierCredits },
|
|
217
|
+
capacity: { maxConcurrent },
|
|
218
|
+
tiers,
|
|
219
|
+
trustProxy,
|
|
220
|
+
estimatedCostSats,
|
|
221
|
+
maxBodySize,
|
|
222
|
+
lightning,
|
|
223
|
+
lightningUrl,
|
|
224
|
+
lightningKey,
|
|
225
|
+
authMode,
|
|
226
|
+
allowlist,
|
|
227
|
+
flatPricing,
|
|
228
|
+
price,
|
|
229
|
+
tunnel,
|
|
230
|
+
x402,
|
|
231
|
+
defaultPriceUsd,
|
|
232
|
+
verbose,
|
|
233
|
+
logFormat,
|
|
234
|
+
serviceName,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=config.js.map
|