@token2chat/t2c 0.2.0-beta.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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/adapters/aider.d.ts +5 -0
  4. package/dist/adapters/aider.js +29 -0
  5. package/dist/adapters/cline.d.ts +5 -0
  6. package/dist/adapters/cline.js +32 -0
  7. package/dist/adapters/continue.d.ts +5 -0
  8. package/dist/adapters/continue.js +45 -0
  9. package/dist/adapters/cursor.d.ts +5 -0
  10. package/dist/adapters/cursor.js +23 -0
  11. package/dist/adapters/env.d.ts +5 -0
  12. package/dist/adapters/env.js +25 -0
  13. package/dist/adapters/index.d.ts +6 -0
  14. package/dist/adapters/index.js +6 -0
  15. package/dist/adapters/openclaw.d.ts +2 -0
  16. package/dist/adapters/openclaw.js +167 -0
  17. package/dist/cashu-store.d.ts +52 -0
  18. package/dist/cashu-store.js +201 -0
  19. package/dist/commands/audit.d.ts +6 -0
  20. package/dist/commands/audit.js +340 -0
  21. package/dist/commands/balance.d.ts +5 -0
  22. package/dist/commands/balance.js +29 -0
  23. package/dist/commands/config.d.ts +5 -0
  24. package/dist/commands/config.js +62 -0
  25. package/dist/commands/connect.d.ts +1 -0
  26. package/dist/commands/connect.js +43 -0
  27. package/dist/commands/doctor.d.ts +1 -0
  28. package/dist/commands/doctor.js +178 -0
  29. package/dist/commands/init.d.ts +3 -0
  30. package/dist/commands/init.js +50 -0
  31. package/dist/commands/mint.d.ts +5 -0
  32. package/dist/commands/mint.js +168 -0
  33. package/dist/commands/recover.d.ts +1 -0
  34. package/dist/commands/recover.js +61 -0
  35. package/dist/commands/service.d.ts +7 -0
  36. package/dist/commands/service.js +378 -0
  37. package/dist/commands/setup.d.ts +1 -0
  38. package/dist/commands/setup.js +128 -0
  39. package/dist/commands/status.d.ts +5 -0
  40. package/dist/commands/status.js +87 -0
  41. package/dist/config.d.ts +83 -0
  42. package/dist/config.js +224 -0
  43. package/dist/connectors/cursor.d.ts +2 -0
  44. package/dist/connectors/cursor.js +28 -0
  45. package/dist/connectors/env.d.ts +2 -0
  46. package/dist/connectors/env.js +38 -0
  47. package/dist/connectors/index.d.ts +26 -0
  48. package/dist/connectors/index.js +30 -0
  49. package/dist/connectors/interface.d.ts +20 -0
  50. package/dist/connectors/interface.js +1 -0
  51. package/dist/connectors/openclaw.d.ts +2 -0
  52. package/dist/connectors/openclaw.js +202 -0
  53. package/dist/gate-discovery.d.ts +49 -0
  54. package/dist/gate-discovery.js +142 -0
  55. package/dist/index.d.ts +2 -0
  56. package/dist/index.js +177 -0
  57. package/dist/proxy.d.ts +11 -0
  58. package/dist/proxy.js +352 -0
  59. package/package.json +84 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * CashuStore — Cashu ecash proof management.
3
+ *
4
+ * Single source of truth for proof storage, selection, encoding,
5
+ * and mint interactions.
6
+ */
7
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
8
+ import { dirname } from "node:path";
9
+ import { CashuMint, CashuWallet, MintQuoteState, getEncodedTokenV4, } from "@cashu/cashu-ts";
10
+ /**
11
+ * Simple async mutex to prevent concurrent wallet operations.
12
+ * Protects against double-spend and data loss from overlapping read-modify-write cycles.
13
+ */
14
+ class Mutex {
15
+ _queue = [];
16
+ _locked = false;
17
+ async lock(fn) {
18
+ const release = await this._acquire();
19
+ try {
20
+ return await fn();
21
+ }
22
+ finally {
23
+ release();
24
+ }
25
+ }
26
+ _acquire() {
27
+ return new Promise((resolve) => {
28
+ const tryAcquire = () => {
29
+ if (!this._locked) {
30
+ this._locked = true;
31
+ resolve(() => {
32
+ this._locked = false;
33
+ const next = this._queue.shift();
34
+ if (next)
35
+ next();
36
+ });
37
+ }
38
+ else {
39
+ this._queue.push(tryAcquire);
40
+ }
41
+ };
42
+ tryAcquire();
43
+ });
44
+ }
45
+ }
46
+ export class CashuStore {
47
+ path;
48
+ data;
49
+ wallet = null;
50
+ mutex = new Mutex();
51
+ constructor(path, data) {
52
+ this.path = path;
53
+ this.data = data;
54
+ }
55
+ // ── Getters ────────────────────────────────────────────────
56
+ get mint() {
57
+ return this.data.mint;
58
+ }
59
+ get balance() {
60
+ return this.data.proofs.reduce((s, p) => s + p.amount, 0);
61
+ }
62
+ get proofCount() {
63
+ return this.data.proofs.length;
64
+ }
65
+ // ── Load / Save ────────────────────────────────────────────
66
+ /** Load from file, or create new empty store */
67
+ static async load(path, mint) {
68
+ try {
69
+ const raw = await readFile(path, "utf-8");
70
+ const data = JSON.parse(raw);
71
+ return new CashuStore(path, data);
72
+ }
73
+ catch {
74
+ const data = {
75
+ mint: mint ?? "https://mint.token2chat.com",
76
+ unit: "sat",
77
+ proofs: [],
78
+ };
79
+ const store = new CashuStore(path, data);
80
+ await store.save();
81
+ return store;
82
+ }
83
+ }
84
+ /** Persist to file */
85
+ async save() {
86
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
87
+ await writeFile(this.path, JSON.stringify(this.data, null, 2), { mode: 0o600 });
88
+ }
89
+ // ── Data export ────────────────────────────────────────────
90
+ /** Return a deep copy of the store data */
91
+ exportData() {
92
+ return {
93
+ mint: this.data.mint,
94
+ unit: this.data.unit,
95
+ proofs: this.data.proofs.map((p) => ({ ...p })),
96
+ };
97
+ }
98
+ // ── Funding check ──────────────────────────────────────────
99
+ /** Returns true if balance is below threshold */
100
+ needsFunding(threshold) {
101
+ return this.balance < threshold;
102
+ }
103
+ // ── Proof selection ────────────────────────────────────────
104
+ /**
105
+ * Select proofs totalling at least `amount` sat,
106
+ * encode as a Cashu V4 token, and remove them from the store.
107
+ */
108
+ async selectAndEncode(amount) {
109
+ return this.mutex.lock(async () => {
110
+ if (this.balance < amount) {
111
+ throw new Error(`Insufficient balance: need ${amount} sat, have ${this.balance} sat`);
112
+ }
113
+ const sorted = [...this.data.proofs].sort((a, b) => b.amount - a.amount);
114
+ const selected = [];
115
+ let total = 0;
116
+ for (const p of sorted) {
117
+ if (total >= amount)
118
+ break;
119
+ selected.push(p);
120
+ total += p.amount;
121
+ }
122
+ if (total < amount) {
123
+ throw new Error(`Could not select enough proofs for ${amount} sat`);
124
+ }
125
+ // Remove selected proofs
126
+ const selectedSecrets = new Set(selected.map((p) => typeof p.secret === "string" ? p.secret : JSON.stringify(p.secret)));
127
+ this.data.proofs = this.data.proofs.filter((p) => !selectedSecrets.has(typeof p.secret === "string" ? p.secret : JSON.stringify(p.secret)));
128
+ await this.save();
129
+ return getEncodedTokenV4({
130
+ mint: this.data.mint,
131
+ proofs: selected,
132
+ unit: this.data.unit,
133
+ });
134
+ });
135
+ }
136
+ // ── Proof import (no mint interaction) ─────────────────────
137
+ /**
138
+ * Import proofs directly into the store.
139
+ * No mint swap — caller is responsible for proof validity.
140
+ */
141
+ async importProofs(proofs) {
142
+ if (proofs.length === 0)
143
+ return 0;
144
+ return this.mutex.lock(async () => {
145
+ const amount = proofs.reduce((s, p) => s + p.amount, 0);
146
+ this.data.proofs.push(...proofs);
147
+ await this.save();
148
+ return amount;
149
+ });
150
+ }
151
+ // ── Mint-dependent operations ──────────────────────────────
152
+ /** Get or init CashuWallet (lazy, connects to mint) */
153
+ async getCashuWallet() {
154
+ if (!this.wallet) {
155
+ const m = new CashuMint(this.data.mint);
156
+ this.wallet = new CashuWallet(m);
157
+ await this.wallet.loadMint();
158
+ }
159
+ return this.wallet;
160
+ }
161
+ /**
162
+ * Receive an encoded Cashu token — swap at mint for fresh proofs.
163
+ * Used for change/refund tokens from the Gate.
164
+ */
165
+ async receiveToken(encodedToken) {
166
+ return this.mutex.lock(async () => {
167
+ const w = await this.getCashuWallet();
168
+ const proofs = await w.receive(encodedToken);
169
+ if (!proofs || proofs.length === 0)
170
+ return 0;
171
+ const amount = proofs.reduce((s, p) => s + p.amount, 0);
172
+ this.data.proofs.push(...proofs);
173
+ await this.save();
174
+ return amount;
175
+ });
176
+ }
177
+ /**
178
+ * Create a mint quote (Lightning invoice) for funding.
179
+ */
180
+ async createMintQuote(amount) {
181
+ const wallet = await this.getCashuWallet();
182
+ const quote = await wallet.createMintQuote(amount);
183
+ return { quote: quote.quote, request: quote.request };
184
+ }
185
+ /**
186
+ * Check and mint tokens from a paid quote.
187
+ */
188
+ async mintFromQuote(quoteId, amount) {
189
+ return this.mutex.lock(async () => {
190
+ const wallet = await this.getCashuWallet();
191
+ const check = await wallet.checkMintQuote(quoteId);
192
+ if (check.state !== MintQuoteState.PAID && check.state !== MintQuoteState.ISSUED) {
193
+ throw new Error(`Quote not paid yet (state: ${check.state})`);
194
+ }
195
+ const proofs = await wallet.mintProofs(amount, quoteId);
196
+ this.data.proofs.push(...proofs);
197
+ await this.save();
198
+ return proofs.reduce((s, p) => s + p.amount, 0);
199
+ });
200
+ }
201
+ }
@@ -0,0 +1,6 @@
1
+ interface AuditOptions {
2
+ json?: boolean;
3
+ lines?: string;
4
+ }
5
+ export declare function auditCommand(opts: AuditOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,340 @@
1
+ /**
2
+ * t2c audit — Full-chain fund visualization
3
+ *
4
+ * Unified view: local wallet + Mint + Gate + transaction log.
5
+ * Cross-references data and highlights anomalies.
6
+ */
7
+ import { loadConfig, resolveHome, loadFailedTokens, loadTransactions, } from "../config.js";
8
+ import { CashuStore } from "../cashu-store.js";
9
+ import { GateRegistry } from "../gate-discovery.js";
10
+ async function fetchMintInfo(mintUrl) {
11
+ try {
12
+ const [infoRes, keysetsRes] = await Promise.all([
13
+ fetch(`${mintUrl}/v1/info`, { signal: AbortSignal.timeout(8000) }),
14
+ fetch(`${mintUrl}/v1/keysets`, { signal: AbortSignal.timeout(8000) }),
15
+ ]);
16
+ const info = { reachable: infoRes.ok };
17
+ if (infoRes.ok) {
18
+ const data = (await infoRes.json());
19
+ info.name = data.name;
20
+ info.version = data.version;
21
+ info.nuts = data.nuts;
22
+ }
23
+ if (keysetsRes.ok) {
24
+ const data = (await keysetsRes.json());
25
+ info.keysetIds = data.keysets?.filter((k) => k.active).map((k) => k.id);
26
+ }
27
+ return info;
28
+ }
29
+ catch (e) {
30
+ return { reachable: false, error: e instanceof Error ? e.message : String(e) };
31
+ }
32
+ }
33
+ async function fetchGateInfo(gateUrl) {
34
+ try {
35
+ const [healthRes, pricingRes] = await Promise.all([
36
+ fetch(`${gateUrl}/health`, { signal: AbortSignal.timeout(8000) }),
37
+ fetch(`${gateUrl}/v1/pricing`, { signal: AbortSignal.timeout(8000) }),
38
+ ]);
39
+ const info = { reachable: healthRes.ok };
40
+ if (healthRes.ok) {
41
+ const data = (await healthRes.json());
42
+ info.mints = data.mints;
43
+ info.models = data.models;
44
+ }
45
+ if (pricingRes.ok) {
46
+ const data = (await pricingRes.json());
47
+ info.pricing = data.models;
48
+ }
49
+ return info;
50
+ }
51
+ catch (e) {
52
+ return { reachable: false, error: e instanceof Error ? e.message : String(e) };
53
+ }
54
+ }
55
+ function detectAnomalies(report) {
56
+ const anomalies = [];
57
+ // Wallet anomalies
58
+ if (report.wallet) {
59
+ if (report.wallet.balance === 0) {
60
+ anomalies.push({ severity: "error", message: "Wallet balance is 0 — cannot make requests" });
61
+ }
62
+ else if (report.wallet.balance < 500) {
63
+ anomalies.push({ severity: "warn", message: `Low wallet balance: ${report.wallet.balance} sat` });
64
+ }
65
+ if (report.wallet.proofCount > 100) {
66
+ anomalies.push({ severity: "warn", message: `High proof count (${report.wallet.proofCount}) — consider consolidating via swap` });
67
+ }
68
+ }
69
+ else {
70
+ anomalies.push({ severity: "error", message: "No wallet found — run 't2c init'" });
71
+ }
72
+ // Mint anomalies
73
+ if (!report.mint.reachable) {
74
+ anomalies.push({ severity: "error", message: `Mint unreachable: ${report.mint.error ?? "connection failed"}` });
75
+ }
76
+ if (report.mint.keysetIds && report.mint.keysetIds.length === 0) {
77
+ anomalies.push({ severity: "error", message: "Mint has no active keysets" });
78
+ }
79
+ // Gate anomalies
80
+ if (!report.gate.reachable) {
81
+ anomalies.push({ severity: "error", message: `Gate unreachable: ${report.gate.error ?? "connection failed"}` });
82
+ }
83
+ if (report.gate.reachable && report.gate.mints && report.wallet) {
84
+ if (!report.gate.mints.includes(report.wallet.mint)) {
85
+ anomalies.push({
86
+ severity: "error",
87
+ message: `Wallet mint (${report.wallet.mint}) not in Gate's trusted mints: ${report.gate.mints.join(", ")}`,
88
+ });
89
+ }
90
+ }
91
+ // Transaction anomalies
92
+ if (report.transactions.errorCount > 0) {
93
+ anomalies.push({
94
+ severity: "warn",
95
+ message: `${report.transactions.errorCount} failed transaction(s) in history`,
96
+ });
97
+ }
98
+ const lossRate = report.transactions.totalSpent > 0
99
+ ? ((report.transactions.totalSpent - report.transactions.totalChange - report.transactions.totalRefund) / report.transactions.totalSpent * 100)
100
+ : 0;
101
+ if (report.transactions.totalSpent > 0 && lossRate > 50) {
102
+ anomalies.push({
103
+ severity: "warn",
104
+ message: `High fund loss rate: ${lossRate.toFixed(1)}% of spent sats not returned as change/refund`,
105
+ });
106
+ }
107
+ // Failed token anomalies
108
+ if (report.failedTokens.length > 0) {
109
+ const totalLost = report.failedTokens.length;
110
+ anomalies.push({
111
+ severity: "error",
112
+ message: `${totalLost} failed token(s) pending recovery — run 't2c recover'`,
113
+ });
114
+ }
115
+ // Balance consistency check
116
+ if (report.wallet && report.transactions.recent.length > 0) {
117
+ const last = report.transactions.recent[report.transactions.recent.length - 1];
118
+ if (last.balanceAfter !== report.wallet.balance) {
119
+ anomalies.push({
120
+ severity: "info",
121
+ message: `Balance drift: last tx recorded ${last.balanceAfter} sat, wallet now ${report.wallet.balance} sat (may indicate manual changes)`,
122
+ });
123
+ }
124
+ }
125
+ return anomalies;
126
+ }
127
+ function formatTime(ts) {
128
+ return new Date(ts).toLocaleString();
129
+ }
130
+ function formatDuration(ms) {
131
+ if (ms < 1000)
132
+ return `${ms}ms`;
133
+ return `${(ms / 1000).toFixed(1)}s`;
134
+ }
135
+ function printReport(report) {
136
+ const config = report;
137
+ console.log("\n========================================");
138
+ console.log(" t2c Audit Report");
139
+ console.log(` ${formatTime(report.timestamp)}`);
140
+ console.log("========================================\n");
141
+ // ── Wallet ──
142
+ console.log("--- Wallet ---");
143
+ if (report.wallet) {
144
+ console.log(` Balance: ${report.wallet.balance.toLocaleString()} sat`);
145
+ console.log(` Proofs: ${report.wallet.proofCount}`);
146
+ console.log(` Mint: ${report.wallet.mint}`);
147
+ const denominations = Object.entries(report.wallet.proofBreakdown)
148
+ .sort(([a], [b]) => Number(b) - Number(a));
149
+ if (denominations.length > 0) {
150
+ console.log(" Breakdown:");
151
+ for (const [denom, count] of denominations) {
152
+ console.log(` ${denom} sat x ${count} = ${Number(denom) * count} sat`);
153
+ }
154
+ }
155
+ }
156
+ else {
157
+ console.log(" (no wallet found)");
158
+ }
159
+ // ── Mint ──
160
+ console.log("\n--- Mint ---");
161
+ console.log(` Status: ${report.mint.reachable ? "reachable" : "UNREACHABLE"}`);
162
+ if (report.mint.name)
163
+ console.log(` Name: ${report.mint.name}`);
164
+ if (report.mint.version)
165
+ console.log(` Version: ${report.mint.version}`);
166
+ if (report.mint.nuts) {
167
+ console.log(` NUTs: ${Object.keys(report.mint.nuts).sort().join(", ")}`);
168
+ }
169
+ if (report.mint.keysetIds) {
170
+ console.log(` Keysets: ${report.mint.keysetIds.join(", ") || "(none active)"}`);
171
+ }
172
+ // ── Gate ──
173
+ console.log("\n--- Gate ---");
174
+ console.log(` Status: ${report.gate.reachable ? "reachable" : "UNREACHABLE"}`);
175
+ if (report.gate.mints) {
176
+ console.log(` Mints: ${report.gate.mints.join(", ")}`);
177
+ }
178
+ if (report.gate.models) {
179
+ console.log(` Models: ${report.gate.models.join(", ")}`);
180
+ }
181
+ if (report.gate.pricing) {
182
+ console.log(" Pricing:");
183
+ for (const [model, rule] of Object.entries(report.gate.pricing)) {
184
+ const parts = [];
185
+ if (rule.input_per_million)
186
+ parts.push(`in:${rule.input_per_million}/M`);
187
+ if (rule.output_per_million)
188
+ parts.push(`out:${rule.output_per_million}/M`);
189
+ if (rule.per_request)
190
+ parts.push(`req:${rule.per_request}`);
191
+ console.log(` ${model}: ${parts.join(", ")} sat`);
192
+ }
193
+ }
194
+ // ── Discovered Gates ──
195
+ if (report.discoveredGates.length > 0) {
196
+ console.log("\n--- Discovered Gates ---");
197
+ for (const g of report.discoveredGates) {
198
+ const status = g.healthy ? "OK" : "DOWN";
199
+ console.log(` [${status}] ${g.name} — ${g.url}`);
200
+ if (g.mint)
201
+ console.log(` Mint: ${g.mint}`);
202
+ if (g.models.length > 0)
203
+ console.log(` Models: ${g.models.join(", ")}`);
204
+ }
205
+ }
206
+ // ── Transactions ──
207
+ console.log("\n--- Transactions ---");
208
+ if (report.transactions.total === 0) {
209
+ console.log(" (no transactions recorded yet)");
210
+ }
211
+ else {
212
+ console.log(` Total: ${report.transactions.total} requests`);
213
+ console.log(` Spent: ${report.transactions.totalSpent.toLocaleString()} sat`);
214
+ console.log(` Change: +${report.transactions.totalChange.toLocaleString()} sat`);
215
+ console.log(` Refund: +${report.transactions.totalRefund.toLocaleString()} sat`);
216
+ console.log(` Net cost: ${report.transactions.netCost.toLocaleString()} sat`);
217
+ console.log(` Errors: ${report.transactions.errorCount}`);
218
+ if (report.transactions.recent.length > 0) {
219
+ console.log("\n Recent transactions:");
220
+ console.log(" " + "-".repeat(90));
221
+ console.log(" " + padR("Time", 20) + padR("Model", 28) + padR("Paid", 8) + padR("Change", 8) + padR("Status", 8) + "Duration");
222
+ console.log(" " + "-".repeat(90));
223
+ for (const tx of report.transactions.recent) {
224
+ const time = new Date(tx.timestamp).toLocaleTimeString();
225
+ const model = tx.model.length > 26 ? tx.model.slice(0, 24) + ".." : tx.model;
226
+ const status = tx.error ? `ERR` : `${tx.gateStatus}`;
227
+ console.log(" " +
228
+ padR(time, 20) +
229
+ padR(model, 28) +
230
+ padR(`${tx.priceSat}`, 8) +
231
+ padR(`+${tx.changeSat}`, 8) +
232
+ padR(status, 8) +
233
+ formatDuration(tx.durationMs));
234
+ }
235
+ console.log(" " + "-".repeat(90));
236
+ }
237
+ }
238
+ // ── Failed Tokens ──
239
+ if (report.failedTokens.length > 0) {
240
+ console.log("\n--- Failed Tokens ---");
241
+ for (const ft of report.failedTokens) {
242
+ console.log(` [${ft.type}] ${formatTime(ft.timestamp)} — ${ft.error}`);
243
+ console.log(` Token: ${ft.token.slice(0, 40)}...`);
244
+ }
245
+ }
246
+ // ── Anomalies ──
247
+ console.log("\n--- Anomalies ---");
248
+ if (report.anomalies.length === 0) {
249
+ console.log(" No anomalies detected.");
250
+ }
251
+ else {
252
+ for (const a of report.anomalies) {
253
+ const icon = a.severity === "error" ? "!!" : a.severity === "warn" ? " !" : " i";
254
+ console.log(` [${icon}] ${a.message}`);
255
+ }
256
+ }
257
+ console.log("\n========================================\n");
258
+ }
259
+ function padR(s, n) {
260
+ return s.length >= n ? s.slice(0, n) : s + " ".repeat(n - s.length);
261
+ }
262
+ export async function auditCommand(opts) {
263
+ const config = await loadConfig();
264
+ const limit = parseInt(opts.lines || "20", 10);
265
+ // Gather data in parallel
266
+ const [mintInfo, gateInfo, transactions, failedTokensData, walletResult, discoveredGates] = await Promise.all([
267
+ fetchMintInfo(config.mintUrl),
268
+ fetchGateInfo(config.gateUrl),
269
+ loadTransactions(),
270
+ loadFailedTokens(),
271
+ (async () => {
272
+ try {
273
+ const walletPath = resolveHome(config.walletPath);
274
+ const store = await CashuStore.load(walletPath, config.mintUrl);
275
+ const data = store.exportData();
276
+ const breakdown = {};
277
+ for (const p of data.proofs) {
278
+ breakdown[p.amount] = (breakdown[p.amount] || 0) + 1;
279
+ }
280
+ return {
281
+ balance: store.balance,
282
+ proofCount: store.proofCount,
283
+ proofBreakdown: breakdown,
284
+ mint: data.mint,
285
+ };
286
+ }
287
+ catch {
288
+ return null;
289
+ }
290
+ })(),
291
+ (async () => {
292
+ try {
293
+ const registry = new GateRegistry(config.gateUrl, config.discoveryUrl);
294
+ await registry.discover();
295
+ return registry.getAll().map((g) => ({
296
+ name: g.name,
297
+ url: g.url,
298
+ mint: g.mint,
299
+ models: g.models,
300
+ healthy: g.healthy,
301
+ }));
302
+ }
303
+ catch {
304
+ return [];
305
+ }
306
+ })(),
307
+ ]);
308
+ // Compute transaction summaries
309
+ const totalSpent = transactions.reduce((s, t) => s + t.priceSat, 0);
310
+ const totalChange = transactions.reduce((s, t) => s + t.changeSat, 0);
311
+ const totalRefund = transactions.reduce((s, t) => s + t.refundSat, 0);
312
+ const errorCount = transactions.filter((t) => t.error).length;
313
+ const recent = transactions.slice(-limit);
314
+ const report = {
315
+ timestamp: Date.now(),
316
+ wallet: walletResult,
317
+ mint: mintInfo,
318
+ gate: gateInfo,
319
+ discoveredGates,
320
+ transactions: {
321
+ total: transactions.length,
322
+ shown: recent.length,
323
+ totalSpent,
324
+ totalChange,
325
+ totalRefund,
326
+ netCost: totalSpent - totalChange - totalRefund,
327
+ errorCount,
328
+ recent,
329
+ },
330
+ failedTokens: failedTokensData.tokens,
331
+ anomalies: [],
332
+ };
333
+ report.anomalies = detectAnomalies(report);
334
+ if (opts.json) {
335
+ console.log(JSON.stringify(report, null, 2));
336
+ }
337
+ else {
338
+ printReport(report);
339
+ }
340
+ }
@@ -0,0 +1,5 @@
1
+ interface BalanceOptions {
2
+ json?: boolean;
3
+ }
4
+ export declare function balanceCommand(opts: BalanceOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * t2c balance - Simple balance display
3
+ */
4
+ import { loadConfig, WALLET_PATH } from "../config.js";
5
+ import { CashuStore } from "../cashu-store.js";
6
+ export async function balanceCommand(opts) {
7
+ const config = await loadConfig();
8
+ try {
9
+ const wallet = await CashuStore.load(WALLET_PATH, config.mintUrl);
10
+ if (opts.json) {
11
+ console.log(JSON.stringify({
12
+ balance: wallet.balance,
13
+ proofs: wallet.proofCount,
14
+ }));
15
+ }
16
+ else {
17
+ console.log(`${wallet.balance} sat`);
18
+ }
19
+ }
20
+ catch (e) {
21
+ if (opts.json) {
22
+ console.log(JSON.stringify({ error: "Wallet not found" }));
23
+ }
24
+ else {
25
+ console.error("Wallet not found. Run 't2c setup' first.");
26
+ }
27
+ process.exit(1);
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * t2c config - Generate config for AI tools
3
+ */
4
+ import { type AdapterConfigOptions } from "../config.js";
5
+ export declare function configCommand(tool: string, opts: AdapterConfigOptions): Promise<void>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * t2c config - Generate config for AI tools
3
+ */
4
+ import { loadConfig, loadOrCreateProxySecret } from "../config.js";
5
+ import { openclawAdapter } from "../adapters/openclaw.js";
6
+ import { cursorAdapter } from "../adapters/cursor.js";
7
+ import { envAdapter } from "../adapters/env.js";
8
+ import { clineAdapter } from "../adapters/cline.js";
9
+ import { continueAdapter } from "../adapters/continue.js";
10
+ import { aiderAdapter } from "../adapters/aider.js";
11
+ const ADAPTERS = {
12
+ openclaw: {
13
+ name: "OpenClaw",
14
+ description: "Personal AI assistant with multi-channel support",
15
+ adapter: openclawAdapter,
16
+ },
17
+ cursor: {
18
+ name: "Cursor",
19
+ description: "AI-powered code editor",
20
+ adapter: cursorAdapter,
21
+ },
22
+ cline: {
23
+ name: "Cline",
24
+ description: "VS Code AI coding assistant extension",
25
+ adapter: clineAdapter,
26
+ },
27
+ continue: {
28
+ name: "Continue",
29
+ description: "Open-source AI code assistant for VS Code/JetBrains",
30
+ adapter: continueAdapter,
31
+ },
32
+ aider: {
33
+ name: "Aider",
34
+ description: "Terminal-based AI pair programming",
35
+ adapter: aiderAdapter,
36
+ },
37
+ env: {
38
+ name: "Environment Variables",
39
+ description: "Generic OpenAI-compatible configuration",
40
+ adapter: envAdapter,
41
+ },
42
+ };
43
+ export async function configCommand(tool, opts) {
44
+ if (tool === "list") {
45
+ console.log("\n🎟️ Supported AI Tools\n");
46
+ for (const [id, info] of Object.entries(ADAPTERS)) {
47
+ console.log(` ${id.padEnd(12)} ${info.name}`);
48
+ console.log(` ${"".padEnd(12)} ${info.description}\n`);
49
+ }
50
+ console.log("Usage: t2c config <tool>\n");
51
+ return;
52
+ }
53
+ const adapterInfo = ADAPTERS[tool];
54
+ if (!adapterInfo) {
55
+ console.error(`Unknown tool: ${tool}`);
56
+ console.error(`Run 't2c config list' to see supported tools.`);
57
+ process.exit(1);
58
+ }
59
+ const config = await loadConfig();
60
+ const proxySecret = await loadOrCreateProxySecret();
61
+ await adapterInfo.adapter(config, { ...opts, proxySecret });
62
+ }
@@ -0,0 +1 @@
1
+ export declare function connectCommand(app: string): Promise<void>;