@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Mint /stats response shape */
|
|
2
|
+
export interface MintStats {
|
|
3
|
+
totalMintedSats: number;
|
|
4
|
+
totalMeltedSats: number;
|
|
5
|
+
mintCount: number;
|
|
6
|
+
meltCount: number;
|
|
7
|
+
}
|
|
8
|
+
/** Gate /stats response summary shape */
|
|
9
|
+
export interface GateStatsSummary {
|
|
10
|
+
total_requests: number;
|
|
11
|
+
success_count: number;
|
|
12
|
+
error_count: number;
|
|
13
|
+
ecash_received: number;
|
|
14
|
+
model_breakdown: Record<string, {
|
|
15
|
+
count: number;
|
|
16
|
+
ecash_in: number;
|
|
17
|
+
errors: number;
|
|
18
|
+
}>;
|
|
19
|
+
error_breakdown: Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
export interface GateStats {
|
|
22
|
+
generated_at: string;
|
|
23
|
+
today: GateStatsSummary;
|
|
24
|
+
last_7_days: GateStatsSummary;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fetch Gate statistics from the /stats endpoint.
|
|
28
|
+
* Returns null on error (network, parse, etc).
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchGateStats(gateUrl: string): Promise<GateStats | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Fetch Mint statistics from the /stats endpoint.
|
|
33
|
+
* Returns null on error (network, parse, etc).
|
|
34
|
+
*/
|
|
35
|
+
export declare function fetchMintStats(mintUrl: string): Promise<MintStats | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Format sats with thousands separators.
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatSats(sats: number): string;
|
|
40
|
+
/**
|
|
41
|
+
* Build proxy panel content from transaction history.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildProxyContent(maxLines: number, maxWidth: number): Promise<string>;
|
|
44
|
+
export interface MonitorOptions {
|
|
45
|
+
refresh?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create and run the TUI monitor dashboard.
|
|
49
|
+
* Layout: 2x2 grid with Gate, Mint, Proxy, Funds panels.
|
|
50
|
+
*/
|
|
51
|
+
export declare function monitorCommand(opts: MonitorOptions): Promise<void>;
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* t2c monitor ā Live TUI dashboard
|
|
3
|
+
*
|
|
4
|
+
* Real-time monitoring of Gate, Mint, Proxy, and Funds.
|
|
5
|
+
* Uses blessed-contrib for terminal UI.
|
|
6
|
+
*/
|
|
7
|
+
import blessed from "blessed";
|
|
8
|
+
import contrib from "blessed-contrib";
|
|
9
|
+
import { loadConfig, resolveHome, loadTransactions, formatUnits } from "../config.js";
|
|
10
|
+
import { CashuStore } from "../cashu-store.js";
|
|
11
|
+
/**
|
|
12
|
+
* Fetch Gate statistics from the /stats endpoint.
|
|
13
|
+
* Returns null on error (network, parse, etc).
|
|
14
|
+
*/
|
|
15
|
+
export async function fetchGateStats(gateUrl) {
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(`${gateUrl}/stats`, { signal: AbortSignal.timeout(5000) });
|
|
18
|
+
if (!res.ok)
|
|
19
|
+
return null;
|
|
20
|
+
const data = await res.json();
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetch Mint statistics from the /stats endpoint.
|
|
29
|
+
* Returns null on error (network, parse, etc).
|
|
30
|
+
*/
|
|
31
|
+
export async function fetchMintStats(mintUrl) {
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch(`${mintUrl}/stats`, { signal: AbortSignal.timeout(5000) });
|
|
34
|
+
if (!res.ok)
|
|
35
|
+
return null;
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Format sats with thousands separators.
|
|
45
|
+
*/
|
|
46
|
+
export function formatSats(sats) {
|
|
47
|
+
return sats.toLocaleString("en-US");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Format a transaction record for TUI display.
|
|
51
|
+
*/
|
|
52
|
+
function formatTransaction(tx, maxWidth) {
|
|
53
|
+
const time = new Date(tx.timestamp).toLocaleTimeString("en-US", {
|
|
54
|
+
hour: "2-digit",
|
|
55
|
+
minute: "2-digit",
|
|
56
|
+
second: "2-digit",
|
|
57
|
+
hour12: false,
|
|
58
|
+
});
|
|
59
|
+
// Status indicator with color
|
|
60
|
+
const statusIcon = tx.error
|
|
61
|
+
? "{red-fg}ā{/red-fg}"
|
|
62
|
+
: tx.gateStatus === 200
|
|
63
|
+
? "{green-fg}ā{/green-fg}"
|
|
64
|
+
: `{yellow-fg}${tx.gateStatus}{/yellow-fg}`;
|
|
65
|
+
// Cost display
|
|
66
|
+
const cost = formatUnits(tx.priceSat).padStart(8);
|
|
67
|
+
// Truncate model name to fit available space
|
|
68
|
+
const modelMaxLen = Math.max(10, maxWidth - 26);
|
|
69
|
+
const model = tx.model.length > modelMaxLen
|
|
70
|
+
? tx.model.slice(0, modelMaxLen - 2) + ".."
|
|
71
|
+
: tx.model;
|
|
72
|
+
return ` ${time} ${statusIcon} ${cost} ${model}`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build proxy panel content from transaction history.
|
|
76
|
+
*/
|
|
77
|
+
export async function buildProxyContent(maxLines, maxWidth) {
|
|
78
|
+
const transactions = await loadTransactions();
|
|
79
|
+
if (transactions.length === 0) {
|
|
80
|
+
return "{center}No transactions yet{/center}\n\n" +
|
|
81
|
+
" Run requests through the proxy\n" +
|
|
82
|
+
" to see activity here.";
|
|
83
|
+
}
|
|
84
|
+
// Show most recent first
|
|
85
|
+
const recent = transactions.slice().reverse();
|
|
86
|
+
// Calculate summary stats
|
|
87
|
+
const totalSpent = transactions.reduce((s, t) => s + t.priceSat, 0);
|
|
88
|
+
const totalChange = transactions.reduce((s, t) => s + t.changeSat, 0);
|
|
89
|
+
const errorCount = transactions.filter((t) => t.error).length;
|
|
90
|
+
const netCost = totalSpent - totalChange;
|
|
91
|
+
const lines = [
|
|
92
|
+
` Requests: ${transactions.length} | Spent: ${formatUnits(netCost)}`,
|
|
93
|
+
errorCount > 0
|
|
94
|
+
? ` {red-fg}Errors: ${errorCount}{/red-fg}`
|
|
95
|
+
: ` {green-fg}All OK{/green-fg}`,
|
|
96
|
+
" " + "ā".repeat(Math.min(36, maxWidth - 4)),
|
|
97
|
+
];
|
|
98
|
+
// Add recent transactions (leave room for header)
|
|
99
|
+
const displayCount = Math.min(recent.length, Math.max(1, maxLines - 5));
|
|
100
|
+
for (let i = 0; i < displayCount; i++) {
|
|
101
|
+
lines.push(formatTransaction(recent[i], maxWidth));
|
|
102
|
+
}
|
|
103
|
+
if (recent.length > displayCount) {
|
|
104
|
+
lines.push(` ... +${recent.length - displayCount} more`);
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
/** Low balance threshold for warning highlight */
|
|
109
|
+
const LOW_BALANCE_THRESHOLD = 500;
|
|
110
|
+
/** Default refresh interval in milliseconds */
|
|
111
|
+
const DEFAULT_REFRESH_MS = 5000;
|
|
112
|
+
/**
|
|
113
|
+
* Create and run the TUI monitor dashboard.
|
|
114
|
+
* Layout: 2x2 grid with Gate, Mint, Proxy, Funds panels.
|
|
115
|
+
*/
|
|
116
|
+
export async function monitorCommand(opts) {
|
|
117
|
+
// Parse refresh interval
|
|
118
|
+
const refreshMs = opts.refresh
|
|
119
|
+
? parseInt(opts.refresh, 10) * 1000
|
|
120
|
+
: DEFAULT_REFRESH_MS;
|
|
121
|
+
// Create the main screen
|
|
122
|
+
const screen = blessed.screen({
|
|
123
|
+
smartCSR: true,
|
|
124
|
+
title: "t2c monitor",
|
|
125
|
+
fullUnicode: true,
|
|
126
|
+
});
|
|
127
|
+
// Create a 2x2 grid layout
|
|
128
|
+
const grid = new contrib.grid({
|
|
129
|
+
rows: 2,
|
|
130
|
+
cols: 2,
|
|
131
|
+
screen: screen,
|
|
132
|
+
});
|
|
133
|
+
// āā Gate Panel (top-left) āā
|
|
134
|
+
const gateBox = grid.set(0, 0, 1, 1, blessed.box, {
|
|
135
|
+
label: " Gate ",
|
|
136
|
+
border: { type: "line" },
|
|
137
|
+
style: {
|
|
138
|
+
border: { fg: "cyan" },
|
|
139
|
+
label: { fg: "cyan", bold: true },
|
|
140
|
+
},
|
|
141
|
+
content: "{center}Loading...{/center}",
|
|
142
|
+
tags: true,
|
|
143
|
+
});
|
|
144
|
+
// āā Mint Panel (top-right) āā
|
|
145
|
+
const mintBox = grid.set(0, 1, 1, 1, blessed.box, {
|
|
146
|
+
label: " Mint ",
|
|
147
|
+
border: { type: "line" },
|
|
148
|
+
style: {
|
|
149
|
+
border: { fg: "green" },
|
|
150
|
+
label: { fg: "green", bold: true },
|
|
151
|
+
},
|
|
152
|
+
content: "{center}Loading...{/center}",
|
|
153
|
+
tags: true,
|
|
154
|
+
});
|
|
155
|
+
// āā Proxy Panel (bottom-left) āā
|
|
156
|
+
const proxyBox = grid.set(1, 0, 1, 1, blessed.box, {
|
|
157
|
+
label: " Proxy ",
|
|
158
|
+
border: { type: "line" },
|
|
159
|
+
style: {
|
|
160
|
+
border: { fg: "yellow" },
|
|
161
|
+
label: { fg: "yellow", bold: true },
|
|
162
|
+
},
|
|
163
|
+
content: "{center}Loading...{/center}",
|
|
164
|
+
tags: true,
|
|
165
|
+
});
|
|
166
|
+
// āā Funds Panel (bottom-right) āā
|
|
167
|
+
const fundsBox = grid.set(1, 1, 1, 1, blessed.box, {
|
|
168
|
+
label: " Funds ",
|
|
169
|
+
border: { type: "line" },
|
|
170
|
+
style: {
|
|
171
|
+
border: { fg: "magenta" },
|
|
172
|
+
label: { fg: "magenta", bold: true },
|
|
173
|
+
},
|
|
174
|
+
content: "{center}Loading...{/center}",
|
|
175
|
+
tags: true,
|
|
176
|
+
});
|
|
177
|
+
// Status bar at the bottom
|
|
178
|
+
const statusBar = blessed.box({
|
|
179
|
+
parent: screen,
|
|
180
|
+
bottom: 0,
|
|
181
|
+
left: 0,
|
|
182
|
+
width: "100%",
|
|
183
|
+
height: 1,
|
|
184
|
+
style: {
|
|
185
|
+
bg: "blue",
|
|
186
|
+
fg: "white",
|
|
187
|
+
},
|
|
188
|
+
content: ` t2c monitor | q: quit | r: refresh | interval: ${refreshMs / 1000}s `,
|
|
189
|
+
tags: true,
|
|
190
|
+
});
|
|
191
|
+
// Reference to refresh timer for cleanup
|
|
192
|
+
let refreshTimer = null;
|
|
193
|
+
// Key bindings for clean exit
|
|
194
|
+
screen.key(["escape", "q", "C-c"], () => {
|
|
195
|
+
cleanup();
|
|
196
|
+
});
|
|
197
|
+
// Refresh key
|
|
198
|
+
screen.key(["r"], () => {
|
|
199
|
+
updatePanels();
|
|
200
|
+
});
|
|
201
|
+
// Cleanup function
|
|
202
|
+
function cleanup() {
|
|
203
|
+
if (refreshTimer) {
|
|
204
|
+
clearInterval(refreshTimer);
|
|
205
|
+
refreshTimer = null;
|
|
206
|
+
}
|
|
207
|
+
screen.destroy();
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
// Handle process signals for clean exit
|
|
211
|
+
process.on("SIGINT", cleanup);
|
|
212
|
+
process.on("SIGTERM", cleanup);
|
|
213
|
+
// Panel update function
|
|
214
|
+
async function updatePanels() {
|
|
215
|
+
const now = new Date().toLocaleTimeString();
|
|
216
|
+
// Calculate available lines for proxy panel (rough estimate)
|
|
217
|
+
const panelHeight = Math.floor(screen.height / 2) - 3;
|
|
218
|
+
const panelWidth = Math.floor(screen.width / 2) - 4;
|
|
219
|
+
// Load config for panel updates
|
|
220
|
+
const config = await loadConfig();
|
|
221
|
+
// Update Gate panel with stats
|
|
222
|
+
await updateGatePanel(gateBox, config.gateUrl, now);
|
|
223
|
+
// Update Mint panel with stats
|
|
224
|
+
const mintStats = await fetchMintStats(config.mintUrl);
|
|
225
|
+
if (mintStats) {
|
|
226
|
+
const netFlow = mintStats.totalMintedSats - mintStats.totalMeltedSats;
|
|
227
|
+
const netFlowColor = netFlow >= 0 ? "green-fg" : "red-fg";
|
|
228
|
+
const netFlowSign = netFlow >= 0 ? "+" : "";
|
|
229
|
+
mintBox.setContent(`{center}Mint Statistics{/center}\n\n` +
|
|
230
|
+
` {bold}Minted:{/bold} ${formatUnits(mintStats.totalMintedSats)} (${mintStats.mintCount} ops)\n` +
|
|
231
|
+
` {bold}Melted:{/bold} ${formatUnits(mintStats.totalMeltedSats)} (${mintStats.meltCount} ops)\n` +
|
|
232
|
+
` {bold}Net:{/bold} {${netFlowColor}}${netFlowSign}${formatUnits(netFlow)}{/${netFlowColor}}\n\n` +
|
|
233
|
+
`{right}Updated: ${now}{/right}`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
mintBox.setContent(`{center}Mint Statistics{/center}\n\n` +
|
|
237
|
+
` {red-fg}ā{/red-fg} Unable to fetch stats\n` +
|
|
238
|
+
` URL: ${config.mintUrl}\n\n` +
|
|
239
|
+
`{right}Updated: ${now}{/right}`);
|
|
240
|
+
}
|
|
241
|
+
// Update Proxy panel with real transaction data
|
|
242
|
+
try {
|
|
243
|
+
const proxyContent = await buildProxyContent(panelHeight, panelWidth);
|
|
244
|
+
proxyBox.setContent(proxyContent + `\n\n{right}${now}{/right}`);
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
proxyBox.setContent(`{center}Proxy Events{/center}\n\n` +
|
|
248
|
+
` {red-fg}Error loading transactions{/red-fg}\n` +
|
|
249
|
+
` ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
250
|
+
`{right}${now}{/right}`);
|
|
251
|
+
}
|
|
252
|
+
// Update Funds panel with wallet balance and fund flow statistics
|
|
253
|
+
await updateFundsPanel(fundsBox, now);
|
|
254
|
+
screen.render();
|
|
255
|
+
}
|
|
256
|
+
/** Update the Funds panel with wallet balance and fund flow statistics */
|
|
257
|
+
async function updateFundsPanel(box, now) {
|
|
258
|
+
try {
|
|
259
|
+
const config = await loadConfig();
|
|
260
|
+
const walletPath = resolveHome(config.walletPath);
|
|
261
|
+
// Load wallet and transactions in parallel
|
|
262
|
+
const [store, transactions] = await Promise.all([
|
|
263
|
+
CashuStore.load(walletPath, config.mintUrl),
|
|
264
|
+
loadTransactions(),
|
|
265
|
+
]);
|
|
266
|
+
const balance = store.balance;
|
|
267
|
+
const proofCount = store.proofCount;
|
|
268
|
+
// Calculate fund flow statistics from transactions
|
|
269
|
+
const totalSpent = transactions.reduce((s, t) => s + t.priceSat, 0);
|
|
270
|
+
const totalChange = transactions.reduce((s, t) => s + t.changeSat, 0);
|
|
271
|
+
const totalRefund = transactions.reduce((s, t) => s + t.refundSat, 0);
|
|
272
|
+
const netCost = totalSpent - totalChange - totalRefund;
|
|
273
|
+
// Format balance with low balance warning
|
|
274
|
+
const balanceColor = balance < LOW_BALANCE_THRESHOLD ? "red" : "green";
|
|
275
|
+
const balanceWarning = balance < LOW_BALANCE_THRESHOLD ? " {red-fg}ā LOW{/red-fg}" : "";
|
|
276
|
+
const balanceStr = `{${balanceColor}-fg}${formatUnits(balance)}{/${balanceColor}-fg}${balanceWarning}`;
|
|
277
|
+
box.setContent(`{center}Wallet Funds{/center}\n\n` +
|
|
278
|
+
` Balance: ${balanceStr}\n` +
|
|
279
|
+
` Proofs: ${proofCount}\n\n` +
|
|
280
|
+
` {bold}Fund Flow{/bold}\n` +
|
|
281
|
+
` Spent: ${formatUnits(totalSpent)}\n` +
|
|
282
|
+
` Change: +${formatUnits(totalChange)}\n` +
|
|
283
|
+
` Refund: +${formatUnits(totalRefund)}\n` +
|
|
284
|
+
` Net: ${formatUnits(netCost)}\n\n` +
|
|
285
|
+
`{right}Updated: ${now}{/right}`);
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
box.setContent(`{center}Wallet Funds{/center}\n\n` +
|
|
289
|
+
` {red-fg}Error loading wallet{/red-fg}\n` +
|
|
290
|
+
` ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
291
|
+
`{right}Updated: ${now}{/right}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/** Update the Gate panel with stats from /stats endpoint */
|
|
295
|
+
async function updateGatePanel(box, gateUrl, now) {
|
|
296
|
+
const stats = await fetchGateStats(gateUrl);
|
|
297
|
+
if (!stats) {
|
|
298
|
+
box.setContent(`{center}Gate Statistics{/center}\n\n` +
|
|
299
|
+
` Status: {red-fg}ā{/red-fg} Unreachable\n` +
|
|
300
|
+
` URL: ${gateUrl}\n\n` +
|
|
301
|
+
`{right}Updated: ${now}{/right}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const today = stats.today;
|
|
305
|
+
const week = stats.last_7_days;
|
|
306
|
+
// Format error count with red highlight if > 0
|
|
307
|
+
const todayErrors = today.error_count > 0
|
|
308
|
+
? `{red-fg}${today.error_count}{/red-fg}`
|
|
309
|
+
: `${today.error_count}`;
|
|
310
|
+
const weekErrors = week.error_count > 0
|
|
311
|
+
? `{red-fg}${week.error_count}{/red-fg}`
|
|
312
|
+
: `${week.error_count}`;
|
|
313
|
+
// Calculate success rate
|
|
314
|
+
const todayRate = today.total_requests > 0
|
|
315
|
+
? ((today.success_count / today.total_requests) * 100).toFixed(1)
|
|
316
|
+
: "100.0";
|
|
317
|
+
const weekRate = week.total_requests > 0
|
|
318
|
+
? ((week.success_count / week.total_requests) * 100).toFixed(1)
|
|
319
|
+
: "100.0";
|
|
320
|
+
// Build content
|
|
321
|
+
let content = `{center}Gate Statistics{/center}\n\n`;
|
|
322
|
+
content += ` {bold}Today{/bold}\n`;
|
|
323
|
+
content += ` Total: ${formatSats(today.total_requests)} `;
|
|
324
|
+
content += `OK: ${formatSats(today.success_count)} `;
|
|
325
|
+
content += `Err: ${todayErrors}\n`;
|
|
326
|
+
content += ` Rate: ${todayRate}% `;
|
|
327
|
+
content += `Ecash: ${formatUnits(today.ecash_received)}\n\n`;
|
|
328
|
+
content += ` {bold}Last 7 Days{/bold}\n`;
|
|
329
|
+
content += ` Total: ${formatSats(week.total_requests)} `;
|
|
330
|
+
content += `OK: ${formatSats(week.success_count)} `;
|
|
331
|
+
content += `Err: ${weekErrors}\n`;
|
|
332
|
+
content += ` Rate: ${weekRate}% `;
|
|
333
|
+
content += `Ecash: ${formatUnits(week.ecash_received)}\n`;
|
|
334
|
+
// Show error breakdown if any errors today
|
|
335
|
+
if (today.error_count > 0 && Object.keys(today.error_breakdown).length > 0) {
|
|
336
|
+
content += `\n {bold}{red-fg}Errors Today{/red-fg}{/bold}\n`;
|
|
337
|
+
for (const [code, count] of Object.entries(today.error_breakdown)) {
|
|
338
|
+
content += ` {red-fg}⢠${code}: ${count}{/red-fg}\n`;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
content += `\n{right}Updated: ${now}{/right}`;
|
|
342
|
+
box.setContent(content);
|
|
343
|
+
}
|
|
344
|
+
// Initial render
|
|
345
|
+
await updatePanels();
|
|
346
|
+
screen.render();
|
|
347
|
+
// Set up auto-refresh
|
|
348
|
+
refreshTimer = setInterval(() => {
|
|
349
|
+
updatePanels().catch(() => {
|
|
350
|
+
// Ignore refresh errors, will retry next interval
|
|
351
|
+
});
|
|
352
|
+
}, refreshMs);
|
|
353
|
+
}
|
package/dist/commands/recover.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* t2c recover - Recover failed tokens
|
|
3
3
|
*/
|
|
4
|
-
import { loadConfig, resolveHome, FAILED_TOKENS_PATH, loadFailedTokens, saveFailedTokens, } from "../config.js";
|
|
4
|
+
import { loadConfig, resolveHome, FAILED_TOKENS_PATH, loadFailedTokens, saveFailedTokens, formatUnits, } from "../config.js";
|
|
5
5
|
import { CashuStore } from "../cashu-store.js";
|
|
6
6
|
export async function recoverCommand() {
|
|
7
7
|
const config = await loadConfig();
|
|
@@ -30,7 +30,7 @@ export async function recoverCommand() {
|
|
|
30
30
|
console.log(`Attempting to recover ${ft.type} token from ${date}...`);
|
|
31
31
|
try {
|
|
32
32
|
const amount = await wallet.receiveToken(ft.token);
|
|
33
|
-
console.log(` ā
Recovered ${amount}
|
|
33
|
+
console.log(` ā
Recovered ${formatUnits(amount)}\n`);
|
|
34
34
|
recoveredTotal += amount;
|
|
35
35
|
}
|
|
36
36
|
catch (e) {
|
|
@@ -47,8 +47,8 @@ export async function recoverCommand() {
|
|
|
47
47
|
await saveFailedTokens({ tokens: stillFailed });
|
|
48
48
|
console.log("ā".repeat(40));
|
|
49
49
|
if (recoveredTotal > 0) {
|
|
50
|
-
console.log(`\nš Recovered total: ${recoveredTotal
|
|
51
|
-
console.log(`New wallet balance: ${wallet.balance
|
|
50
|
+
console.log(`\nš Recovered total: ${formatUnits(recoveredTotal)}`);
|
|
51
|
+
console.log(`New wallet balance: ${formatUnits(wallet.balance)}\n`);
|
|
52
52
|
}
|
|
53
53
|
if (stillFailed.length > 0) {
|
|
54
54
|
console.log(`\nā ļø ${stillFailed.length} token(s) still failed.`);
|
package/dist/commands/setup.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* t2c setup - Interactive setup wizard
|
|
3
3
|
*/
|
|
4
4
|
import * as readline from "node:readline";
|
|
5
|
-
import { loadConfig, saveConfig, configExists, resolveHome, checkGateHealth, checkMintHealth, DEFAULT_CONFIG, } from "../config.js";
|
|
5
|
+
import { loadConfig, saveConfig, configExists, resolveHome, checkGateHealth, checkMintHealth, DEFAULT_CONFIG, formatUnits, } from "../config.js";
|
|
6
6
|
import { CashuStore } from "../cashu-store.js";
|
|
7
7
|
function createPrompt() {
|
|
8
8
|
return readline.createInterface({
|
|
@@ -106,7 +106,7 @@ export async function setupCommand() {
|
|
|
106
106
|
try {
|
|
107
107
|
const resolvedPath = resolveHome(walletPath);
|
|
108
108
|
const wallet = await CashuStore.load(resolvedPath, mintUrl);
|
|
109
|
-
console.log(`ā
Wallet initialized (balance: ${wallet.balance}
|
|
109
|
+
console.log(`ā
Wallet initialized (balance: ${formatUnits(wallet.balance)})`);
|
|
110
110
|
}
|
|
111
111
|
catch (e) {
|
|
112
112
|
console.log(`ā ļø Failed to initialize wallet: ${e}`);
|
package/dist/commands/status.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* t2c status - Show service status and wallet balance
|
|
3
3
|
*/
|
|
4
|
-
import { loadConfig, resolveHome, configExists, checkGateHealth } from "../config.js";
|
|
4
|
+
import { loadConfig, resolveHome, configExists, checkGateHealth, formatUnits } from "../config.js";
|
|
5
5
|
import { CashuStore } from "../cashu-store.js";
|
|
6
6
|
async function checkProxy(port) {
|
|
7
7
|
try {
|
|
@@ -70,9 +70,8 @@ export async function statusCommand(opts) {
|
|
|
70
70
|
// Wallet
|
|
71
71
|
console.log("");
|
|
72
72
|
if (walletInfo) {
|
|
73
|
-
const balanceStr = walletInfo.balance.toLocaleString();
|
|
74
73
|
const status = walletInfo.balance > 0 ? "ā
" : "ā ļø";
|
|
75
|
-
console.log(`Wallet: ${status} ${
|
|
74
|
+
console.log(`Wallet: ${status} ${formatUnits(walletInfo.balance)} (${walletInfo.proofs} proofs)`);
|
|
76
75
|
if (walletInfo.balance === 0) {
|
|
77
76
|
console.log(` Run 't2c mint' to add funds`);
|
|
78
77
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -30,6 +30,11 @@ export declare function configExists(): Promise<boolean>;
|
|
|
30
30
|
/**
|
|
31
31
|
* Custom error classes for better error handling
|
|
32
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* Format units (1 unit = $0.00001) as USD string.
|
|
35
|
+
* 100000 units = $1.00
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatUnits(units: number): string;
|
|
33
38
|
export declare class ConfigError extends Error {
|
|
34
39
|
readonly recoverable: boolean;
|
|
35
40
|
constructor(message: string, recoverable?: boolean);
|
package/dist/config.js
CHANGED
|
@@ -128,6 +128,23 @@ export async function configExists() {
|
|
|
128
128
|
/**
|
|
129
129
|
* Custom error classes for better error handling
|
|
130
130
|
*/
|
|
131
|
+
/**
|
|
132
|
+
* Format units (1 unit = $0.00001) as USD string.
|
|
133
|
+
* 100000 units = $1.00
|
|
134
|
+
*/
|
|
135
|
+
export function formatUnits(units) {
|
|
136
|
+
const dollars = units / 100000;
|
|
137
|
+
if (dollars >= 1 || dollars === 0) {
|
|
138
|
+
return "$" + dollars.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
139
|
+
}
|
|
140
|
+
// For sub-dollar: show enough decimal places to be meaningful
|
|
141
|
+
const str = dollars.toFixed(5).replace(/0+$/, "");
|
|
142
|
+
// Ensure at least 2 decimal places
|
|
143
|
+
const parts = str.split(".");
|
|
144
|
+
const decimals = parts[1] || "";
|
|
145
|
+
const padded = decimals.length < 2 ? decimals.padEnd(2, "0") : decimals;
|
|
146
|
+
return "$" + parts[0] + "." + padded;
|
|
147
|
+
}
|
|
131
148
|
export class ConfigError extends Error {
|
|
132
149
|
recoverable;
|
|
133
150
|
constructor(message, recoverable = false) {
|
|
@@ -1,28 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Connector
|
|
3
|
+
*
|
|
4
|
+
* Detects Cursor IDE installation and provides configuration instructions.
|
|
5
|
+
*/
|
|
6
|
+
import { accessSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { homedir, platform } from "node:os";
|
|
9
|
+
function detectCursorInstallation() {
|
|
10
|
+
const home = homedir();
|
|
11
|
+
const plat = platform();
|
|
12
|
+
const candidates = [];
|
|
13
|
+
if (plat === "darwin") {
|
|
14
|
+
candidates.push(join(home, "Library", "Application Support", "Cursor"));
|
|
15
|
+
}
|
|
16
|
+
else if (plat === "win32") {
|
|
17
|
+
const appData = process.env.APPDATA;
|
|
18
|
+
if (appData)
|
|
19
|
+
candidates.push(join(appData, "Cursor"));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// Linux and others
|
|
23
|
+
candidates.push(join(home, ".config", "Cursor"));
|
|
24
|
+
}
|
|
25
|
+
for (const p of candidates) {
|
|
26
|
+
try {
|
|
27
|
+
accessSync(p);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// not found
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
1
36
|
export const cursorConnector = {
|
|
2
37
|
id: "cursor",
|
|
3
38
|
name: "Cursor",
|
|
4
|
-
description: "Configure Cursor IDE
|
|
39
|
+
description: "Configure Cursor IDE for Token2Chat",
|
|
5
40
|
async detect() {
|
|
6
|
-
|
|
7
|
-
// Could check for:
|
|
8
|
-
// - ~/Library/Application Support/Cursor (macOS)
|
|
9
|
-
// - ~/.config/Cursor (Linux)
|
|
10
|
-
// - %APPDATA%\Cursor (Windows)
|
|
11
|
-
return false;
|
|
41
|
+
return detectCursorInstallation();
|
|
12
42
|
},
|
|
13
43
|
async connect(_config) {
|
|
14
|
-
console.log("\n
|
|
15
|
-
console.log(" Cursor
|
|
16
|
-
console.log(" In the meantime, you can configure Cursor manually:\n");
|
|
44
|
+
console.log("\n Cursor Integration\n");
|
|
45
|
+
console.log(" Configure Cursor manually:\n");
|
|
17
46
|
console.log(" 1. Open Cursor Settings (Cmd/Ctrl + ,)");
|
|
18
|
-
console.log(" 2.
|
|
47
|
+
console.log(" 2. Search for 'OpenAI Base URL' and set:");
|
|
19
48
|
console.log(` http://127.0.0.1:${_config.proxyPort}/v1\n`);
|
|
20
|
-
console.log(" 3. Set OpenAI API Key:");
|
|
21
|
-
console.log("
|
|
22
|
-
console.log(" 4. Use model
|
|
49
|
+
console.log(" 3. Set 'OpenAI API Key' to your proxy secret:");
|
|
50
|
+
console.log(" (find it with: t2c status --json)\n");
|
|
51
|
+
console.log(" 4. Use any model from the Gate, e.g.:");
|
|
23
52
|
console.log(" anthropic/claude-sonnet-4\n");
|
|
24
53
|
},
|
|
25
54
|
async verify() {
|
|
26
|
-
return
|
|
55
|
+
return detectCursorInstallation();
|
|
27
56
|
},
|
|
28
57
|
};
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import os from "node:os";
|
|
9
|
-
import { loadOrCreateProxySecret } from "../config.js";
|
|
9
|
+
import { loadOrCreateProxySecret, WALLET_PATH, formatUnits } from "../config.js";
|
|
10
|
+
import { CashuStore } from "../cashu-store.js";
|
|
10
11
|
/**
|
|
11
12
|
* Default models when Gate is unreachable.
|
|
12
13
|
* IDs use dash format (proxy transforms to slash for Gate/OpenRouter).
|
|
@@ -72,25 +73,6 @@ async function getConfigPath() {
|
|
|
72
73
|
*/
|
|
73
74
|
function mergeToken2ChatConfig(existingConfig, t2cConfig, gateModels, apiKey) {
|
|
74
75
|
const config = { ...existingConfig };
|
|
75
|
-
// Ensure plugins.entries exists
|
|
76
|
-
if (!config.plugins || typeof config.plugins !== "object") {
|
|
77
|
-
config.plugins = {};
|
|
78
|
-
}
|
|
79
|
-
const plugins = config.plugins;
|
|
80
|
-
if (!plugins.entries || typeof plugins.entries !== "object") {
|
|
81
|
-
plugins.entries = {};
|
|
82
|
-
}
|
|
83
|
-
const entries = plugins.entries;
|
|
84
|
-
// Set/overwrite token2chat plugin config
|
|
85
|
-
entries.token2chat = {
|
|
86
|
-
enabled: true,
|
|
87
|
-
config: {
|
|
88
|
-
gateUrl: t2cConfig.gateUrl,
|
|
89
|
-
mintUrl: t2cConfig.mintUrl,
|
|
90
|
-
proxyPort: t2cConfig.proxyPort,
|
|
91
|
-
walletPath: t2cConfig.walletPath,
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
76
|
// Ensure models.providers exists
|
|
95
77
|
if (!config.models || typeof config.models !== "object") {
|
|
96
78
|
config.models = {};
|
|
@@ -101,11 +83,29 @@ function mergeToken2ChatConfig(existingConfig, t2cConfig, gateModels, apiKey) {
|
|
|
101
83
|
}
|
|
102
84
|
const providers = models.providers;
|
|
103
85
|
// Set/overwrite token2chat provider config
|
|
86
|
+
const activeModels = gateModels.length > 0 ? gateModels : DEFAULT_MODELS;
|
|
104
87
|
providers.token2chat = {
|
|
105
88
|
baseUrl: `http://127.0.0.1:${t2cConfig.proxyPort}/v1`,
|
|
106
89
|
apiKey,
|
|
107
90
|
api: "openai-completions",
|
|
108
|
-
models:
|
|
91
|
+
models: activeModels,
|
|
92
|
+
};
|
|
93
|
+
// Set up agents.defaults with token2chat model allowlist and fallbacks
|
|
94
|
+
if (!config.agents || typeof config.agents !== "object") {
|
|
95
|
+
config.agents = {};
|
|
96
|
+
}
|
|
97
|
+
const agents = config.agents;
|
|
98
|
+
if (!agents.defaults || typeof agents.defaults !== "object") {
|
|
99
|
+
agents.defaults = {};
|
|
100
|
+
}
|
|
101
|
+
const defaults = agents.defaults;
|
|
102
|
+
const modelIds = activeModels.map((m) => m.id);
|
|
103
|
+
defaults.models = modelIds;
|
|
104
|
+
defaults.model = {
|
|
105
|
+
...(typeof defaults.model === "object" && defaults.model !== null
|
|
106
|
+
? defaults.model
|
|
107
|
+
: {}),
|
|
108
|
+
fallbacks: modelIds,
|
|
109
109
|
};
|
|
110
110
|
return config;
|
|
111
111
|
}
|
|
@@ -175,6 +175,16 @@ export const openclawConnector = {
|
|
|
175
175
|
if (existingContent) {
|
|
176
176
|
console.log(` Backup: ${backupPath}`);
|
|
177
177
|
}
|
|
178
|
+
// Show wallet balance for onboarding
|
|
179
|
+
try {
|
|
180
|
+
const wallet = await CashuStore.load(WALLET_PATH, config.mintUrl);
|
|
181
|
+
console.log(`\nš° Wallet balance: ${formatUnits(wallet.balance)}`);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
console.log("\nš° Wallet: not funded yet");
|
|
185
|
+
console.log(` Fund your wallet: t2c mint <amount>`);
|
|
186
|
+
console.log(` Mint URL: ${config.mintUrl}`);
|
|
187
|
+
}
|
|
178
188
|
console.log("\nš Next steps:\n");
|
|
179
189
|
console.log(" 1. Restart OpenClaw gateway:");
|
|
180
190
|
console.log(" openclaw gateway restart\n");
|
|
@@ -189,11 +199,9 @@ export const openclawConnector = {
|
|
|
189
199
|
const configPath = await getConfigPath();
|
|
190
200
|
const content = await fs.readFile(configPath, "utf-8");
|
|
191
201
|
const doc = JSON.parse(content);
|
|
192
|
-
const plugins = doc?.plugins;
|
|
193
|
-
const entries = plugins?.entries;
|
|
194
202
|
const models = doc?.models;
|
|
195
203
|
const providers = models?.providers;
|
|
196
|
-
return
|
|
204
|
+
return providers?.token2chat !== undefined;
|
|
197
205
|
}
|
|
198
206
|
catch {
|
|
199
207
|
return false;
|