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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +207 -0
  3. package/dist/bin/satgate.d.ts +3 -0
  4. package/dist/bin/satgate.d.ts.map +1 -0
  5. package/dist/bin/satgate.js +7 -0
  6. package/dist/bin/satgate.js.map +1 -0
  7. package/dist/src/auth/allowlist.d.ts +24 -0
  8. package/dist/src/auth/allowlist.d.ts.map +1 -0
  9. package/dist/src/auth/allowlist.js +130 -0
  10. package/dist/src/auth/allowlist.js.map +1 -0
  11. package/dist/src/auth/middleware.d.ts +15 -0
  12. package/dist/src/auth/middleware.d.ts.map +1 -0
  13. package/dist/src/auth/middleware.js +29 -0
  14. package/dist/src/auth/middleware.js.map +1 -0
  15. package/dist/src/cli.d.ts +2 -0
  16. package/dist/src/cli.d.ts.map +1 -0
  17. package/dist/src/cli.js +275 -0
  18. package/dist/src/cli.js.map +1 -0
  19. package/dist/src/config.d.ts +133 -0
  20. package/dist/src/config.d.ts.map +1 -0
  21. package/dist/src/config.js +237 -0
  22. package/dist/src/config.js.map +1 -0
  23. package/dist/src/discovery/llms-txt.d.ts +10 -0
  24. package/dist/src/discovery/llms-txt.d.ts.map +1 -0
  25. package/dist/src/discovery/llms-txt.js +25 -0
  26. package/dist/src/discovery/llms-txt.js.map +1 -0
  27. package/dist/src/discovery/openapi.d.ts +8 -0
  28. package/dist/src/discovery/openapi.d.ts.map +1 -0
  29. package/dist/src/discovery/openapi.js +116 -0
  30. package/dist/src/discovery/openapi.js.map +1 -0
  31. package/dist/src/discovery/well-known.d.ts +22 -0
  32. package/dist/src/discovery/well-known.d.ts.map +1 -0
  33. package/dist/src/discovery/well-known.js +44 -0
  34. package/dist/src/discovery/well-known.js.map +1 -0
  35. package/dist/src/index.d.ts +15 -0
  36. package/dist/src/index.d.ts.map +1 -0
  37. package/dist/src/index.js +15 -0
  38. package/dist/src/index.js.map +1 -0
  39. package/dist/src/lightning.d.ts +12 -0
  40. package/dist/src/lightning.d.ts.map +1 -0
  41. package/dist/src/lightning.js +38 -0
  42. package/dist/src/lightning.js.map +1 -0
  43. package/dist/src/logger.d.ts +16 -0
  44. package/dist/src/logger.d.ts.map +1 -0
  45. package/dist/src/logger.js +99 -0
  46. package/dist/src/logger.js.map +1 -0
  47. package/dist/src/proxy/capacity.d.ts +15 -0
  48. package/dist/src/proxy/capacity.d.ts.map +1 -0
  49. package/dist/src/proxy/capacity.js +28 -0
  50. package/dist/src/proxy/capacity.js.map +1 -0
  51. package/dist/src/proxy/handler.d.ts +27 -0
  52. package/dist/src/proxy/handler.d.ts.map +1 -0
  53. package/dist/src/proxy/handler.js +165 -0
  54. package/dist/src/proxy/handler.js.map +1 -0
  55. package/dist/src/proxy/pricing.d.ts +17 -0
  56. package/dist/src/proxy/pricing.d.ts.map +1 -0
  57. package/dist/src/proxy/pricing.js +42 -0
  58. package/dist/src/proxy/pricing.js.map +1 -0
  59. package/dist/src/proxy/streaming.d.ts +12 -0
  60. package/dist/src/proxy/streaming.d.ts.map +1 -0
  61. package/dist/src/proxy/streaming.js +68 -0
  62. package/dist/src/proxy/streaming.js.map +1 -0
  63. package/dist/src/proxy/token-counter.d.ts +22 -0
  64. package/dist/src/proxy/token-counter.d.ts.map +1 -0
  65. package/dist/src/proxy/token-counter.js +66 -0
  66. package/dist/src/proxy/token-counter.js.map +1 -0
  67. package/dist/src/server.d.ts +9 -0
  68. package/dist/src/server.d.ts.map +1 -0
  69. package/dist/src/server.js +239 -0
  70. package/dist/src/server.js.map +1 -0
  71. package/dist/src/tunnel.d.ts +26 -0
  72. package/dist/src/tunnel.d.ts.map +1 -0
  73. package/dist/src/tunnel.js +78 -0
  74. package/dist/src/tunnel.js.map +1 -0
  75. package/dist/src/x402/facilitator.d.ts +7 -0
  76. package/dist/src/x402/facilitator.d.ts.map +1 -0
  77. package/dist/src/x402/facilitator.js +33 -0
  78. package/dist/src/x402/facilitator.js.map +1 -0
  79. package/package.json +70 -0
@@ -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