@token2chat/t2c 0.2.0-beta.1 → 0.2.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/README.md +117 -144
- package/dist/cashu-store.d.ts +1 -1
- package/dist/cashu-store.js +4 -4
- package/dist/commands/audit.d.ts +65 -0
- package/dist/commands/audit.js +12 -12
- package/dist/commands/balance.js +2 -2
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/mint.js +14 -14
- package/dist/commands/monitor.d.ts +51 -0
- package/dist/commands/monitor.js +353 -0
- package/dist/commands/recover.js +4 -4
- package/dist/commands/setup.js +2 -2
- package/dist/commands/status.js +2 -3
- package/dist/config.d.ts +5 -0
- package/dist/config.js +17 -0
- package/dist/connectors/cursor.js +44 -15
- package/dist/connectors/openclaw.js +32 -24
- package/dist/index.js +8 -1
- package/dist/proxy/auth.d.ts +20 -0
- package/dist/proxy/auth.js +28 -0
- package/dist/proxy/errors.d.ts +58 -0
- package/dist/proxy/errors.js +95 -0
- package/dist/proxy/gate-client.d.ts +34 -0
- package/dist/proxy/gate-client.js +81 -0
- package/dist/proxy/index.d.ts +10 -0
- package/dist/proxy/index.js +17 -0
- package/dist/proxy/payment-service.d.ts +65 -0
- package/dist/proxy/payment-service.js +101 -0
- package/dist/proxy/pricing.d.ts +37 -0
- package/dist/proxy/pricing.js +90 -0
- package/dist/proxy/response.d.ts +24 -0
- package/dist/proxy/response.js +48 -0
- package/dist/proxy/sse-parser.d.ts +19 -0
- package/dist/proxy/sse-parser.js +80 -0
- package/dist/proxy/types.d.ts +113 -0
- package/dist/proxy/types.js +74 -0
- package/dist/proxy.d.ts +2 -9
- package/dist/proxy.js +74 -186
- package/package.json +5 -2
package/dist/proxy.js
CHANGED
|
@@ -3,62 +3,13 @@
|
|
|
3
3
|
* into ecash-paid requests to the token2chat Gate.
|
|
4
4
|
*/
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
|
-
import crypto from "node:crypto";
|
|
7
6
|
import { CashuStore } from "./cashu-store.js";
|
|
8
|
-
import { resolveHome,
|
|
7
|
+
import { resolveHome, appendFailedToken, appendTransaction, loadOrCreateProxySecret } from "./config.js";
|
|
9
8
|
import { GateRegistry } from "./gate-discovery.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Known provider prefixes for model ID transformation.
|
|
17
|
-
* We use `-` as separator in OpenClaw to avoid double-slash issue,
|
|
18
|
-
* but Gate/OpenRouter expects `/` as separator.
|
|
19
|
-
*/
|
|
20
|
-
const MODEL_PROVIDER_PREFIXES = [
|
|
21
|
-
"openai",
|
|
22
|
-
"anthropic",
|
|
23
|
-
"google",
|
|
24
|
-
"deepseek",
|
|
25
|
-
"qwen",
|
|
26
|
-
"moonshotai",
|
|
27
|
-
"mistralai",
|
|
28
|
-
"meta-llama",
|
|
29
|
-
"nvidia",
|
|
30
|
-
"cohere",
|
|
31
|
-
"perplexity",
|
|
32
|
-
];
|
|
33
|
-
/**
|
|
34
|
-
* Transform model ID from dash format to slash format.
|
|
35
|
-
* e.g., "anthropic-claude-sonnet-4.5" → "anthropic/claude-sonnet-4.5"
|
|
36
|
-
*/
|
|
37
|
-
function transformModelId(model) {
|
|
38
|
-
for (const prefix of MODEL_PROVIDER_PREFIXES) {
|
|
39
|
-
if (model.startsWith(`${prefix}-`)) {
|
|
40
|
-
return `${prefix}/${model.slice(prefix.length + 1)}`;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return model;
|
|
44
|
-
}
|
|
45
|
-
function sleep(ms) {
|
|
46
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
-
}
|
|
48
|
-
const MAX_RETRY_DELAY_MS = 30_000;
|
|
49
|
-
function parseRetryAfter(value) {
|
|
50
|
-
if (!value)
|
|
51
|
-
return null;
|
|
52
|
-
const seconds = parseFloat(value);
|
|
53
|
-
if (!isNaN(seconds) && isFinite(seconds)) {
|
|
54
|
-
return Math.max(0, Math.ceil(seconds * 1000));
|
|
55
|
-
}
|
|
56
|
-
const date = new Date(value);
|
|
57
|
-
if (!isNaN(date.getTime())) {
|
|
58
|
-
return Math.max(0, date.getTime() - Date.now());
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
9
|
+
import { defaultLogger, transformModelId, parseRetryAfter, MAX_BODY_SIZE, MAX_RETRY_DELAY_MS, DEFAULT_RETRY_CONFIG, PricingCache, GateClient, PaymentService, createAuthChecker, handleError, sendJsonResponse, } from "./proxy/index.js";
|
|
10
|
+
import { extractCashuChangeFromSSE } from "./proxy/sse-parser.js";
|
|
11
|
+
// Re-export for backwards compatibility
|
|
12
|
+
export { transformModelId, parseRetryAfter };
|
|
62
13
|
export async function startProxy(config, logger = defaultLogger) {
|
|
63
14
|
const { gateUrl, mintUrl, proxyPort: port, lowBalanceThreshold } = config;
|
|
64
15
|
const walletPath = resolveHome(config.walletPath);
|
|
@@ -71,56 +22,28 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
71
22
|
}
|
|
72
23
|
// Load proxy authentication secret
|
|
73
24
|
const proxySecret = await loadOrCreateProxySecret();
|
|
74
|
-
|
|
75
|
-
const auth = req.headers.authorization;
|
|
76
|
-
if (!auth)
|
|
77
|
-
return false;
|
|
78
|
-
const parts = auth.split(" ");
|
|
79
|
-
if (parts.length !== 2 || parts[0] !== "Bearer")
|
|
80
|
-
return false;
|
|
81
|
-
const provided = Buffer.from(parts[1]);
|
|
82
|
-
const expected = Buffer.from(proxySecret);
|
|
83
|
-
if (provided.length !== expected.length)
|
|
84
|
-
return false;
|
|
85
|
-
return crypto.timingSafeEqual(provided, expected);
|
|
86
|
-
}
|
|
25
|
+
const checkAuth = createAuthChecker(proxySecret);
|
|
87
26
|
// Load wallet synchronously before starting server (fixes race condition)
|
|
88
27
|
let wallet;
|
|
89
28
|
try {
|
|
90
29
|
wallet = await CashuStore.load(walletPath, mintUrl);
|
|
91
|
-
logger.info(`Wallet loaded:
|
|
30
|
+
logger.info(`Wallet loaded: balance=${wallet.balance} (${wallet.proofCount} proofs)`);
|
|
92
31
|
}
|
|
93
32
|
catch (e) {
|
|
94
33
|
logger.error("Failed to load wallet:", e);
|
|
95
34
|
throw new Error(`Cannot start proxy: wallet load failed - ${e instanceof Error ? e.message : e}`);
|
|
96
35
|
}
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
pricingCache = {};
|
|
109
|
-
for (const [model, rule] of Object.entries(data.models)) {
|
|
110
|
-
pricingCache[model] = rule.per_request;
|
|
111
|
-
}
|
|
112
|
-
pricingFetchedAt = now;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
logger.warn("Failed to fetch pricing:", e);
|
|
117
|
-
}
|
|
118
|
-
return pricingCache ?? {};
|
|
119
|
-
}
|
|
120
|
-
function getPrice(pricing, model) {
|
|
121
|
-
return pricing[model] ?? pricing["*"] ?? 500;
|
|
122
|
-
}
|
|
123
|
-
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
36
|
+
// Pricing cache
|
|
37
|
+
const pricingCache = new PricingCache(gateUrl);
|
|
38
|
+
// Gate client
|
|
39
|
+
const gateClient = new GateClient(gateUrl, { logger });
|
|
40
|
+
// Payment service
|
|
41
|
+
const paymentService = new PaymentService({
|
|
42
|
+
wallet,
|
|
43
|
+
logger,
|
|
44
|
+
appendFailedToken,
|
|
45
|
+
lowBalanceThreshold,
|
|
46
|
+
});
|
|
124
47
|
async function readBody(req) {
|
|
125
48
|
const chunks = [];
|
|
126
49
|
let size = 0;
|
|
@@ -137,14 +60,12 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
137
60
|
const server = createServer(async (req, res) => {
|
|
138
61
|
// Health check (unauthenticated — no sensitive data)
|
|
139
62
|
if (req.method === "GET" && req.url === "/health") {
|
|
140
|
-
res
|
|
141
|
-
res.end(JSON.stringify({ ok: true }));
|
|
63
|
+
sendJsonResponse(res, 200, { ok: true });
|
|
142
64
|
return;
|
|
143
65
|
}
|
|
144
66
|
// All endpoints below require authentication
|
|
145
67
|
if (!checkAuth(req)) {
|
|
146
|
-
res
|
|
147
|
-
res.end(JSON.stringify({ error: { message: "Unauthorized. Provide a valid Bearer token." } }));
|
|
68
|
+
sendJsonResponse(res, 401, { error: { message: "Unauthorized. Provide a valid Bearer token." } });
|
|
148
69
|
return;
|
|
149
70
|
}
|
|
150
71
|
// Pricing passthrough
|
|
@@ -155,28 +76,25 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
155
76
|
res.end(await upstream.text());
|
|
156
77
|
}
|
|
157
78
|
catch {
|
|
158
|
-
res
|
|
159
|
-
res.end(JSON.stringify({ error: "Gate unreachable" }));
|
|
79
|
+
sendJsonResponse(res, 502, { error: "Gate unreachable" });
|
|
160
80
|
}
|
|
161
81
|
return;
|
|
162
82
|
}
|
|
163
83
|
// Models endpoint
|
|
164
84
|
if (req.method === "GET" && req.url === "/v1/models") {
|
|
165
|
-
|
|
166
|
-
const models =
|
|
85
|
+
await pricingCache.get(); // Ensure cache is populated
|
|
86
|
+
const models = pricingCache.getModels().map((id) => ({
|
|
167
87
|
id,
|
|
168
88
|
object: "model",
|
|
169
89
|
created: Date.now(),
|
|
170
90
|
owned_by: "token2chat",
|
|
171
91
|
}));
|
|
172
|
-
res
|
|
173
|
-
res.end(JSON.stringify({ object: "list", data: models }));
|
|
92
|
+
sendJsonResponse(res, 200, { object: "list", data: models });
|
|
174
93
|
return;
|
|
175
94
|
}
|
|
176
95
|
// Only proxy POST /v1/chat/completions
|
|
177
96
|
if (req.method !== "POST" || !req.url?.startsWith("/v1/chat/completions")) {
|
|
178
|
-
res
|
|
179
|
-
res.end(JSON.stringify({ error: { message: "Not found" } }));
|
|
97
|
+
sendJsonResponse(res, 404, { error: { message: "Not found" } });
|
|
180
98
|
return;
|
|
181
99
|
}
|
|
182
100
|
try {
|
|
@@ -189,20 +107,15 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
189
107
|
const txId = `tx-${txStart}-${Math.random().toString(36).slice(2, 8)}`;
|
|
190
108
|
let txChangeSat = 0;
|
|
191
109
|
let txRefundSat = 0;
|
|
192
|
-
const balanceBefore =
|
|
193
|
-
|
|
194
|
-
const price = getPrice(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
code: "insufficient_balance",
|
|
202
|
-
message: `Wallet balance ${balance} sat < ${price} sat required. Run 't2c mint' to add funds.`,
|
|
203
|
-
type: "insufficient_funds",
|
|
204
|
-
},
|
|
205
|
-
}));
|
|
110
|
+
const balanceBefore = paymentService.getBalance();
|
|
111
|
+
await pricingCache.get(); // Ensure cache is populated
|
|
112
|
+
const price = pricingCache.getPrice(requestedModel);
|
|
113
|
+
// Check balance using PaymentService (throws InsufficientBalanceError)
|
|
114
|
+
try {
|
|
115
|
+
paymentService.checkBalance(price, requestedModel);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
handleError(res, e, logger);
|
|
206
119
|
return;
|
|
207
120
|
}
|
|
208
121
|
// Prepare modified body once (model transform doesn't change between retries)
|
|
@@ -212,51 +125,33 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
212
125
|
const gateUrls = gateRegistry
|
|
213
126
|
? await gateRegistry.selectGate(requestedModel)
|
|
214
127
|
: [gateUrl];
|
|
215
|
-
// Make request with retry logic
|
|
216
|
-
const maxRetries =
|
|
217
|
-
|
|
218
|
-
let lastResponse = null;
|
|
219
|
-
let lastResponseBody;
|
|
128
|
+
// Make request with retry logic (new token per attempt for ecash)
|
|
129
|
+
const { maxRetries, baseDelayMs } = DEFAULT_RETRY_CONFIG;
|
|
130
|
+
let lastGateResponse = null;
|
|
220
131
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
221
132
|
// Pick gate for this attempt (rotate through available gates)
|
|
222
133
|
const currentGateUrl = gateUrls[attempt % gateUrls.length];
|
|
223
|
-
const currentPrice = getPrice(
|
|
224
|
-
|
|
225
|
-
const
|
|
134
|
+
const currentPrice = pricingCache.getPrice(requestedModel);
|
|
135
|
+
// Select token using PaymentService
|
|
136
|
+
const { token, balanceAfter } = await paymentService.selectToken(currentPrice);
|
|
226
137
|
if (attempt === 0) {
|
|
227
|
-
logger.info(`Paying ${currentPrice}
|
|
138
|
+
logger.info(`Paying ${currentPrice} for ${requestedModel} → ${currentGateUrl} (balance: ${balanceAfter + currentPrice} → ~${balanceAfter})`);
|
|
228
139
|
}
|
|
229
140
|
else {
|
|
230
141
|
logger.info(`Retry ${attempt}/${maxRetries} for ${requestedModel} → ${currentGateUrl}`);
|
|
231
142
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"Content-Type": "application/json",
|
|
236
|
-
"X-Cashu": token,
|
|
237
|
-
},
|
|
143
|
+
// Use GateClient for the actual request
|
|
144
|
+
const gateRes = await gateClient.request({
|
|
145
|
+
path: "/v1/chat/completions",
|
|
238
146
|
body: modifiedBody,
|
|
147
|
+
token,
|
|
148
|
+
gateUrl: currentGateUrl,
|
|
149
|
+
stream: isStream,
|
|
239
150
|
});
|
|
240
|
-
// Handle change/refund tokens
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
continue;
|
|
245
|
-
try {
|
|
246
|
-
const amt = await wallet.receiveToken(tokenStr);
|
|
247
|
-
if (type === "change")
|
|
248
|
-
txChangeSat += amt;
|
|
249
|
-
else
|
|
250
|
-
txRefundSat += amt;
|
|
251
|
-
logger.info(`Received ${amt} sat ${type}`);
|
|
252
|
-
}
|
|
253
|
-
catch (e) {
|
|
254
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
255
|
-
logger.warn(`Failed to store ${type}: ${errMsg}`);
|
|
256
|
-
logger.warn(`Token saved to ${FAILED_TOKENS_PATH} - run 't2c recover' to retry`);
|
|
257
|
-
await appendFailedToken(tokenStr, type, errMsg);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
151
|
+
// Handle change/refund tokens using PaymentService
|
|
152
|
+
const tokens = await paymentService.processGateTokens(gateRes.changeToken, gateRes.refundToken);
|
|
153
|
+
txChangeSat += tokens.changeSat;
|
|
154
|
+
txRefundSat += tokens.refundSat;
|
|
260
155
|
// If not 429, we're done (and mark gate healthy for failover)
|
|
261
156
|
if (gateRes.status !== 429) {
|
|
262
157
|
if (gateRegistry) {
|
|
@@ -266,12 +161,13 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
266
161
|
gateRegistry.markSuccess(currentGateUrl);
|
|
267
162
|
}
|
|
268
163
|
const resHeaders = {};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
resHeaders["Content-Type"] = ct;
|
|
164
|
+
if (gateRes.contentType)
|
|
165
|
+
resHeaders["Content-Type"] = gateRes.contentType;
|
|
272
166
|
res.writeHead(gateRes.status, resHeaders);
|
|
273
|
-
if (isStream && gateRes.
|
|
274
|
-
|
|
167
|
+
if (isStream && gateRes.stream) {
|
|
168
|
+
// Filter out cashu-change SSE events from stream
|
|
169
|
+
const { filtered, changeToken: sseChangeToken } = extractCashuChangeFromSSE(gateRes.stream);
|
|
170
|
+
const reader = filtered.getReader();
|
|
275
171
|
try {
|
|
276
172
|
while (true) {
|
|
277
173
|
const { done, value } = await reader.read();
|
|
@@ -284,20 +180,23 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
284
180
|
reader.releaseLock();
|
|
285
181
|
}
|
|
286
182
|
res.end();
|
|
183
|
+
// Process SSE change token (if any)
|
|
184
|
+
const sseChange = sseChangeToken();
|
|
185
|
+
if (sseChange) {
|
|
186
|
+
const sseChangeResult = await paymentService.receiveChange(sseChange);
|
|
187
|
+
txChangeSat += sseChangeResult;
|
|
188
|
+
}
|
|
287
189
|
}
|
|
288
190
|
else {
|
|
289
|
-
res.end(
|
|
191
|
+
res.end(gateRes.body ?? "");
|
|
290
192
|
}
|
|
291
193
|
// Log balance warning
|
|
292
|
-
|
|
293
|
-
if (newBalance < lowBalanceThreshold) {
|
|
294
|
-
logger.warn(`⚠️ Low ecash balance: ${newBalance} sat (threshold: ${lowBalanceThreshold})`);
|
|
295
|
-
}
|
|
194
|
+
paymentService.checkLowBalance();
|
|
296
195
|
// Record transaction
|
|
297
196
|
appendTransaction({
|
|
298
197
|
id: txId, timestamp: txStart, model: requestedModel,
|
|
299
198
|
priceSat: currentPrice, changeSat: txChangeSat, refundSat: txRefundSat,
|
|
300
|
-
gateStatus: gateRes.status, balanceBefore, balanceAfter:
|
|
199
|
+
gateStatus: gateRes.status, balanceBefore, balanceAfter: paymentService.getBalance(),
|
|
301
200
|
durationMs: Date.now() - txStart,
|
|
302
201
|
}).catch(() => { });
|
|
303
202
|
return;
|
|
@@ -305,37 +204,26 @@ export async function startProxy(config, logger = defaultLogger) {
|
|
|
305
204
|
// Store last 429 response — mark gate for failover
|
|
306
205
|
if (gateRegistry)
|
|
307
206
|
gateRegistry.markFailed(currentGateUrl);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// On 429, calculate backoff delay
|
|
207
|
+
lastGateResponse = { status: gateRes.status, body: gateRes.body };
|
|
208
|
+
// On 429, calculate backoff delay (GateClient doesn't retry, we do it here for new tokens)
|
|
311
209
|
if (attempt < maxRetries) {
|
|
312
|
-
const
|
|
313
|
-
const backoffMs = Math.min(retryAfterMs ?? retryBaseDelayMs * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
|
|
210
|
+
const backoffMs = Math.min(baseDelayMs * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
|
|
314
211
|
logger.warn(`Rate limited (429), retrying in ${backoffMs}ms...`);
|
|
315
|
-
await
|
|
212
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
316
213
|
}
|
|
317
214
|
}
|
|
318
215
|
// All retries exhausted — record failed transaction
|
|
319
216
|
appendTransaction({
|
|
320
217
|
id: txId, timestamp: txStart, model: requestedModel,
|
|
321
218
|
priceSat: price, changeSat: txChangeSat, refundSat: txRefundSat,
|
|
322
|
-
gateStatus:
|
|
219
|
+
gateStatus: lastGateResponse.status, balanceBefore, balanceAfter: paymentService.getBalance(),
|
|
323
220
|
durationMs: Date.now() - txStart, error: "Rate limited after retries",
|
|
324
221
|
}).catch(() => { });
|
|
325
|
-
res.writeHead(
|
|
326
|
-
res.end(
|
|
222
|
+
res.writeHead(lastGateResponse.status, { "Content-Type": "application/json" });
|
|
223
|
+
res.end(lastGateResponse.body ?? "");
|
|
327
224
|
}
|
|
328
225
|
catch (e) {
|
|
329
|
-
|
|
330
|
-
logger.error("Proxy error:", e);
|
|
331
|
-
if (msg === "Request body too large") {
|
|
332
|
-
res.writeHead(413, { "Content-Type": "application/json" });
|
|
333
|
-
res.end(JSON.stringify({ error: { code: "payload_too_large", message: "Request body too large" } }));
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
337
|
-
res.end(JSON.stringify({ error: { code: "proxy_error", message: "Internal proxy error" } }));
|
|
338
|
-
}
|
|
226
|
+
handleError(res, e, logger);
|
|
339
227
|
}
|
|
340
228
|
});
|
|
341
229
|
server.listen(port, "127.0.0.1", () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@token2chat/t2c",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "t2c - Pay-per-request LLM access via Cashu ecash",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"cli"
|
|
28
28
|
],
|
|
29
29
|
"bin": {
|
|
30
|
-
"t2c": "
|
|
30
|
+
"t2c": "dist/index.js"
|
|
31
31
|
},
|
|
32
32
|
"main": "./dist/index.js",
|
|
33
33
|
"types": "./dist/index.d.ts",
|
|
@@ -67,9 +67,12 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cashu/cashu-ts": "^2.5.0",
|
|
69
69
|
"@cashu/crypto": "^0.3.4",
|
|
70
|
+
"blessed": "^0.1.81",
|
|
71
|
+
"blessed-contrib": "^4.11.0",
|
|
70
72
|
"commander": "^14.0.0"
|
|
71
73
|
},
|
|
72
74
|
"devDependencies": {
|
|
75
|
+
"@types/blessed": "^0.1.27",
|
|
73
76
|
"@types/node": "^25.1.0",
|
|
74
77
|
"@vitest/coverage-v8": "^3.2.4",
|
|
75
78
|
"typescript": "^5.9.3",
|