@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.
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/adapters/aider.d.ts +5 -0
- package/dist/adapters/aider.js +29 -0
- package/dist/adapters/cline.d.ts +5 -0
- package/dist/adapters/cline.js +32 -0
- package/dist/adapters/continue.d.ts +5 -0
- package/dist/adapters/continue.js +45 -0
- package/dist/adapters/cursor.d.ts +5 -0
- package/dist/adapters/cursor.js +23 -0
- package/dist/adapters/env.d.ts +5 -0
- package/dist/adapters/env.js +25 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/openclaw.d.ts +2 -0
- package/dist/adapters/openclaw.js +167 -0
- package/dist/cashu-store.d.ts +52 -0
- package/dist/cashu-store.js +201 -0
- package/dist/commands/audit.d.ts +6 -0
- package/dist/commands/audit.js +340 -0
- package/dist/commands/balance.d.ts +5 -0
- package/dist/commands/balance.js +29 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.js +62 -0
- package/dist/commands/connect.d.ts +1 -0
- package/dist/commands/connect.js +43 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +178 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +50 -0
- package/dist/commands/mint.d.ts +5 -0
- package/dist/commands/mint.js +168 -0
- package/dist/commands/recover.d.ts +1 -0
- package/dist/commands/recover.js +61 -0
- package/dist/commands/service.d.ts +7 -0
- package/dist/commands/service.js +378 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +128 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +87 -0
- package/dist/config.d.ts +83 -0
- package/dist/config.js +224 -0
- package/dist/connectors/cursor.d.ts +2 -0
- package/dist/connectors/cursor.js +28 -0
- package/dist/connectors/env.d.ts +2 -0
- package/dist/connectors/env.js +38 -0
- package/dist/connectors/index.d.ts +26 -0
- package/dist/connectors/index.js +30 -0
- package/dist/connectors/interface.d.ts +20 -0
- package/dist/connectors/interface.js +1 -0
- package/dist/connectors/openclaw.d.ts +2 -0
- package/dist/connectors/openclaw.js +202 -0
- package/dist/gate-discovery.d.ts +49 -0
- package/dist/gate-discovery.js +142 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +177 -0
- package/dist/proxy.d.ts +11 -0
- package/dist/proxy.js +352 -0
- package/package.json +84 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* t2c service - Manage the local proxy service
|
|
3
|
+
*/
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import { spawn, execSync } from "node:child_process";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { loadConfig, PID_PATH, LOG_PATH, ensureConfigDir, CONFIG_DIR, } from "../config.js";
|
|
9
|
+
import { startProxy } from "../proxy.js";
|
|
10
|
+
// Platform-specific paths
|
|
11
|
+
const LAUNCHD_PLIST_NAME = "com.token2chat.proxy.plist";
|
|
12
|
+
const LAUNCHD_PLIST_PATH = path.join(os.homedir(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
13
|
+
const SYSTEMD_UNIT_NAME = "t2c-proxy.service";
|
|
14
|
+
const SYSTEMD_UNIT_PATH = path.join(os.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
15
|
+
async function getPid() {
|
|
16
|
+
try {
|
|
17
|
+
const raw = await fs.readFile(PID_PATH, "utf-8");
|
|
18
|
+
const pid = parseInt(raw.trim(), 10);
|
|
19
|
+
if (isNaN(pid))
|
|
20
|
+
return null;
|
|
21
|
+
// Check if process exists
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return pid;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Process doesn't exist, clean up stale PID file
|
|
28
|
+
await fs.unlink(PID_PATH).catch(() => { });
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function writePid(pid) {
|
|
37
|
+
await ensureConfigDir();
|
|
38
|
+
await fs.writeFile(PID_PATH, String(pid), { mode: 0o600 });
|
|
39
|
+
}
|
|
40
|
+
async function removePid() {
|
|
41
|
+
await fs.unlink(PID_PATH).catch(() => { });
|
|
42
|
+
}
|
|
43
|
+
async function isProxyRunning(port) {
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
46
|
+
signal: AbortSignal.timeout(2000),
|
|
47
|
+
});
|
|
48
|
+
return res.ok;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function startForeground() {
|
|
55
|
+
const config = await loadConfig();
|
|
56
|
+
// Check if already running
|
|
57
|
+
if (await isProxyRunning(config.proxyPort)) {
|
|
58
|
+
console.log(`Proxy already running on port ${config.proxyPort}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.log("Starting proxy in foreground (Ctrl+C to stop)...\n");
|
|
62
|
+
const handle = await startProxy(config);
|
|
63
|
+
// Handle shutdown
|
|
64
|
+
process.on("SIGINT", () => {
|
|
65
|
+
console.log("\nShutting down...");
|
|
66
|
+
handle.stop();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
});
|
|
69
|
+
process.on("SIGTERM", () => {
|
|
70
|
+
handle.stop();
|
|
71
|
+
process.exit(0);
|
|
72
|
+
});
|
|
73
|
+
// Keep process alive
|
|
74
|
+
await new Promise(() => { });
|
|
75
|
+
}
|
|
76
|
+
async function startDaemon() {
|
|
77
|
+
const config = await loadConfig();
|
|
78
|
+
// Check if already running
|
|
79
|
+
const existingPid = await getPid();
|
|
80
|
+
if (existingPid) {
|
|
81
|
+
console.log(`Proxy already running (PID: ${existingPid})`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (await isProxyRunning(config.proxyPort)) {
|
|
85
|
+
console.log(`Proxy already running on port ${config.proxyPort}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await ensureConfigDir();
|
|
89
|
+
// Find t2c executable path
|
|
90
|
+
const t2cPath = process.argv[1];
|
|
91
|
+
// Spawn detached process
|
|
92
|
+
const logStream = await fs.open(LOG_PATH, "a");
|
|
93
|
+
const child = spawn(process.execPath, [t2cPath, "service", "start", "-f"], {
|
|
94
|
+
detached: true,
|
|
95
|
+
stdio: ["ignore", logStream.fd, logStream.fd],
|
|
96
|
+
env: { ...process.env, T2C_DAEMON: "1" },
|
|
97
|
+
});
|
|
98
|
+
child.unref();
|
|
99
|
+
await logStream.close();
|
|
100
|
+
if (child.pid) {
|
|
101
|
+
await writePid(child.pid);
|
|
102
|
+
console.log(`Proxy started (PID: ${child.pid})`);
|
|
103
|
+
console.log(`Logs: ${LOG_PATH}`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.error("Failed to start proxy");
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function stopService() {
|
|
111
|
+
const pid = await getPid();
|
|
112
|
+
if (!pid) {
|
|
113
|
+
console.log("Proxy not running");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
process.kill(pid, "SIGTERM");
|
|
118
|
+
console.log(`Stopping proxy (PID: ${pid})...`);
|
|
119
|
+
// Wait for process to exit
|
|
120
|
+
for (let i = 0; i < 30; i++) {
|
|
121
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
122
|
+
try {
|
|
123
|
+
process.kill(pid, 0);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Process exited
|
|
127
|
+
await removePid();
|
|
128
|
+
console.log("Proxy stopped");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Force kill
|
|
133
|
+
process.kill(pid, "SIGKILL");
|
|
134
|
+
await removePid();
|
|
135
|
+
console.log("Proxy killed");
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
console.error(`Failed to stop proxy: ${e}`);
|
|
139
|
+
await removePid();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function restartService() {
|
|
143
|
+
await stopService();
|
|
144
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
145
|
+
await startDaemon();
|
|
146
|
+
}
|
|
147
|
+
async function showLogs(opts) {
|
|
148
|
+
const lines = parseInt(opts.lines || "50", 10);
|
|
149
|
+
try {
|
|
150
|
+
const content = await fs.readFile(LOG_PATH, "utf-8");
|
|
151
|
+
const allLines = content.split("\n");
|
|
152
|
+
const lastLines = allLines.slice(-lines).join("\n");
|
|
153
|
+
console.log(lastLines);
|
|
154
|
+
if (opts.follow) {
|
|
155
|
+
console.log("\n--- Following logs (Ctrl+C to stop) ---\n");
|
|
156
|
+
const tail = spawn("tail", ["-f", LOG_PATH], { stdio: "inherit" });
|
|
157
|
+
process.on("SIGINT", () => {
|
|
158
|
+
tail.kill();
|
|
159
|
+
process.exit(0);
|
|
160
|
+
});
|
|
161
|
+
await new Promise(() => { });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
console.log("No logs found");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// āā Install/Uninstall for system service managers āāāāāāāāāāāāāāāāāā
|
|
169
|
+
function generateLaunchdPlist(config) {
|
|
170
|
+
// Find t2c executable
|
|
171
|
+
const t2cPath = getT2CExecutablePath();
|
|
172
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
173
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
174
|
+
<plist version="1.0">
|
|
175
|
+
<dict>
|
|
176
|
+
<key>Label</key>
|
|
177
|
+
<string>com.token2chat.proxy</string>
|
|
178
|
+
<key>ProgramArguments</key>
|
|
179
|
+
<array>
|
|
180
|
+
<string>${process.execPath}</string>
|
|
181
|
+
<string>${t2cPath}</string>
|
|
182
|
+
<string>service</string>
|
|
183
|
+
<string>start</string>
|
|
184
|
+
<string>-f</string>
|
|
185
|
+
</array>
|
|
186
|
+
<key>RunAtLoad</key>
|
|
187
|
+
<true/>
|
|
188
|
+
<key>KeepAlive</key>
|
|
189
|
+
<true/>
|
|
190
|
+
<key>StandardOutPath</key>
|
|
191
|
+
<string>${LOG_PATH}</string>
|
|
192
|
+
<key>StandardErrorPath</key>
|
|
193
|
+
<string>${LOG_PATH}</string>
|
|
194
|
+
<key>EnvironmentVariables</key>
|
|
195
|
+
<dict>
|
|
196
|
+
<key>PATH</key>
|
|
197
|
+
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
198
|
+
</dict>
|
|
199
|
+
<key>WorkingDirectory</key>
|
|
200
|
+
<string>${CONFIG_DIR}</string>
|
|
201
|
+
</dict>
|
|
202
|
+
</plist>`;
|
|
203
|
+
}
|
|
204
|
+
function generateSystemdUnit(config) {
|
|
205
|
+
const t2cPath = getT2CExecutablePath();
|
|
206
|
+
return `[Unit]
|
|
207
|
+
Description=Token2Chat Proxy Service
|
|
208
|
+
After=network.target
|
|
209
|
+
|
|
210
|
+
[Service]
|
|
211
|
+
Type=simple
|
|
212
|
+
ExecStart=${process.execPath} ${t2cPath} service start -f
|
|
213
|
+
Restart=on-failure
|
|
214
|
+
RestartSec=5
|
|
215
|
+
StandardOutput=append:${LOG_PATH}
|
|
216
|
+
StandardError=append:${LOG_PATH}
|
|
217
|
+
WorkingDirectory=${CONFIG_DIR}
|
|
218
|
+
|
|
219
|
+
[Install]
|
|
220
|
+
WantedBy=default.target
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
function getT2CExecutablePath() {
|
|
224
|
+
// If running from npm, use the bin path
|
|
225
|
+
// Otherwise use the current script path
|
|
226
|
+
return process.argv[1];
|
|
227
|
+
}
|
|
228
|
+
async function installService() {
|
|
229
|
+
const config = await loadConfig();
|
|
230
|
+
const platform = os.platform();
|
|
231
|
+
if (platform === "darwin") {
|
|
232
|
+
// macOS: launchd
|
|
233
|
+
const plistContent = generateLaunchdPlist(config);
|
|
234
|
+
const plistDir = path.dirname(LAUNCHD_PLIST_PATH);
|
|
235
|
+
await fs.mkdir(plistDir, { recursive: true });
|
|
236
|
+
await fs.writeFile(LAUNCHD_PLIST_PATH, plistContent);
|
|
237
|
+
console.log(`ā
Installed launchd service: ${LAUNCHD_PLIST_PATH}\n`);
|
|
238
|
+
console.log("To load and start the service:");
|
|
239
|
+
console.log(` launchctl load ${LAUNCHD_PLIST_PATH}\n`);
|
|
240
|
+
console.log("To check status:");
|
|
241
|
+
console.log(` launchctl list | grep com.token2chat\n`);
|
|
242
|
+
console.log("The service will auto-start on login.");
|
|
243
|
+
}
|
|
244
|
+
else if (platform === "linux") {
|
|
245
|
+
// Linux: systemd (user service)
|
|
246
|
+
const unitContent = generateSystemdUnit(config);
|
|
247
|
+
const unitDir = path.dirname(SYSTEMD_UNIT_PATH);
|
|
248
|
+
await fs.mkdir(unitDir, { recursive: true });
|
|
249
|
+
await fs.writeFile(SYSTEMD_UNIT_PATH, unitContent);
|
|
250
|
+
console.log(`ā
Installed systemd user service: ${SYSTEMD_UNIT_PATH}\n`);
|
|
251
|
+
console.log("To enable and start the service:");
|
|
252
|
+
console.log(" systemctl --user daemon-reload");
|
|
253
|
+
console.log(` systemctl --user enable ${SYSTEMD_UNIT_NAME}`);
|
|
254
|
+
console.log(` systemctl --user start ${SYSTEMD_UNIT_NAME}\n`);
|
|
255
|
+
console.log("To check status:");
|
|
256
|
+
console.log(` systemctl --user status ${SYSTEMD_UNIT_NAME}\n`);
|
|
257
|
+
console.log("The service will auto-start on login.");
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
console.error(`Unsupported platform for service installation: ${platform}`);
|
|
261
|
+
console.error("Use 't2c service start' to run manually.");
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function uninstallService() {
|
|
266
|
+
const platform = os.platform();
|
|
267
|
+
if (platform === "darwin") {
|
|
268
|
+
// macOS: launchd
|
|
269
|
+
try {
|
|
270
|
+
// Try to unload first
|
|
271
|
+
try {
|
|
272
|
+
execSync(`launchctl unload ${LAUNCHD_PLIST_PATH}`, { stdio: "ignore" });
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// May not be loaded
|
|
276
|
+
}
|
|
277
|
+
await fs.unlink(LAUNCHD_PLIST_PATH);
|
|
278
|
+
console.log(`ā
Uninstalled launchd service`);
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
if (e.code === "ENOENT") {
|
|
282
|
+
console.log("Service not installed");
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
throw e;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (platform === "linux") {
|
|
290
|
+
// Linux: systemd
|
|
291
|
+
try {
|
|
292
|
+
// Try to stop and disable first
|
|
293
|
+
try {
|
|
294
|
+
execSync(`systemctl --user stop ${SYSTEMD_UNIT_NAME}`, { stdio: "ignore" });
|
|
295
|
+
execSync(`systemctl --user disable ${SYSTEMD_UNIT_NAME}`, { stdio: "ignore" });
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// May not be running
|
|
299
|
+
}
|
|
300
|
+
await fs.unlink(SYSTEMD_UNIT_PATH);
|
|
301
|
+
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
302
|
+
console.log(`ā
Uninstalled systemd user service`);
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
if (e.code === "ENOENT") {
|
|
306
|
+
console.log("Service not installed");
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
throw e;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
console.error(`Unsupported platform: ${platform}`);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function showServiceStatus() {
|
|
319
|
+
const config = await loadConfig();
|
|
320
|
+
const platform = os.platform();
|
|
321
|
+
const running = await isProxyRunning(config.proxyPort);
|
|
322
|
+
const pid = await getPid();
|
|
323
|
+
console.log("\nšļø Service Status\n");
|
|
324
|
+
console.log(`Platform: ${platform}`);
|
|
325
|
+
console.log(`Proxy: ${running ? "ā
Running" : "ā Not running"}${pid ? ` (PID: ${pid})` : ""}`);
|
|
326
|
+
console.log(`Port: ${config.proxyPort}`);
|
|
327
|
+
// Check if installed as system service
|
|
328
|
+
if (platform === "darwin") {
|
|
329
|
+
try {
|
|
330
|
+
await fs.access(LAUNCHD_PLIST_PATH);
|
|
331
|
+
console.log(`launchd: ā
Installed (${LAUNCHD_PLIST_PATH})`);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
console.log(`launchd: ā Not installed (run 't2c service install')`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (platform === "linux") {
|
|
338
|
+
try {
|
|
339
|
+
await fs.access(SYSTEMD_UNIT_PATH);
|
|
340
|
+
console.log(`systemd: ā
Installed (${SYSTEMD_UNIT_PATH})`);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
console.log(`systemd: ā Not installed (run 't2c service install')`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
console.log(`\nLogs: ${LOG_PATH}`);
|
|
347
|
+
console.log("");
|
|
348
|
+
}
|
|
349
|
+
export async function serviceCommand(action, opts) {
|
|
350
|
+
switch (action) {
|
|
351
|
+
case "start":
|
|
352
|
+
if (opts.foreground || process.env.T2C_DAEMON === "1") {
|
|
353
|
+
await startForeground();
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
await startDaemon();
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
case "stop":
|
|
360
|
+
await stopService();
|
|
361
|
+
break;
|
|
362
|
+
case "restart":
|
|
363
|
+
await restartService();
|
|
364
|
+
break;
|
|
365
|
+
case "logs":
|
|
366
|
+
await showLogs(opts);
|
|
367
|
+
break;
|
|
368
|
+
case "install":
|
|
369
|
+
await installService();
|
|
370
|
+
break;
|
|
371
|
+
case "uninstall":
|
|
372
|
+
await uninstallService();
|
|
373
|
+
break;
|
|
374
|
+
case "status":
|
|
375
|
+
await showServiceStatus();
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setupCommand(): Promise<void>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* t2c setup - Interactive setup wizard
|
|
3
|
+
*/
|
|
4
|
+
import * as readline from "node:readline";
|
|
5
|
+
import { loadConfig, saveConfig, configExists, resolveHome, checkGateHealth, checkMintHealth, DEFAULT_CONFIG, } from "../config.js";
|
|
6
|
+
import { CashuStore } from "../cashu-store.js";
|
|
7
|
+
function createPrompt() {
|
|
8
|
+
return readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async function question(rl, prompt) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
rl.question(prompt, (answer) => resolve(answer.trim()));
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function confirm(rl, prompt, defaultYes = true) {
|
|
19
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
20
|
+
const answer = await question(rl, `${prompt} ${suffix} `);
|
|
21
|
+
if (answer === "")
|
|
22
|
+
return defaultYes;
|
|
23
|
+
return answer.toLowerCase().startsWith("y");
|
|
24
|
+
}
|
|
25
|
+
export async function setupCommand() {
|
|
26
|
+
console.log("\nšļø Token2Chat Setup\n");
|
|
27
|
+
console.log("Pay-per-request LLM access via Cashu ecash.\n");
|
|
28
|
+
const hasConfig = await configExists();
|
|
29
|
+
const existingConfig = await loadConfig();
|
|
30
|
+
const rl = createPrompt();
|
|
31
|
+
try {
|
|
32
|
+
// Check if already configured
|
|
33
|
+
if (hasConfig) {
|
|
34
|
+
const reconfigure = await confirm(rl, "Configuration already exists. Reconfigure?", false);
|
|
35
|
+
if (!reconfigure) {
|
|
36
|
+
console.log("\nSetup cancelled. Run 't2c status' to see current configuration.\n");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Step 1: Gate URL
|
|
41
|
+
console.log("Step 1/4: Gate URL");
|
|
42
|
+
console.log(` The Gate processes LLM requests and handles ecash payments.`);
|
|
43
|
+
const gateUrlAnswer = await question(rl, ` Gate URL [${existingConfig.gateUrl}]: `);
|
|
44
|
+
const gateUrl = gateUrlAnswer || existingConfig.gateUrl;
|
|
45
|
+
// Verify Gate
|
|
46
|
+
process.stdout.write(" Checking Gate... ");
|
|
47
|
+
const gateOk = await checkGateHealth(gateUrl);
|
|
48
|
+
if (gateOk) {
|
|
49
|
+
console.log("ā
Reachable");
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log("ā Unreachable");
|
|
53
|
+
const proceed = await confirm(rl, " Continue anyway?", false);
|
|
54
|
+
if (!proceed) {
|
|
55
|
+
console.log("\nSetup cancelled.\n");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Step 2: Mint URL
|
|
60
|
+
console.log("\nStep 2/4: Mint URL");
|
|
61
|
+
console.log(` The Mint issues ecash tokens for payments.`);
|
|
62
|
+
const mintUrlAnswer = await question(rl, ` Mint URL [${existingConfig.mintUrl}]: `);
|
|
63
|
+
const mintUrl = mintUrlAnswer || existingConfig.mintUrl;
|
|
64
|
+
// Verify Mint
|
|
65
|
+
process.stdout.write(" Checking Mint... ");
|
|
66
|
+
const mintOk = await checkMintHealth(mintUrl);
|
|
67
|
+
if (mintOk) {
|
|
68
|
+
console.log("ā
Reachable");
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log("ā Unreachable");
|
|
72
|
+
const proceed = await confirm(rl, " Continue anyway?", false);
|
|
73
|
+
if (!proceed) {
|
|
74
|
+
console.log("\nSetup cancelled.\n");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Step 3: Proxy port
|
|
79
|
+
console.log("\nStep 3/4: Proxy Port");
|
|
80
|
+
console.log(` The local proxy runs on this port for AI tools to connect.`);
|
|
81
|
+
const portAnswer = await question(rl, ` Port [${existingConfig.proxyPort}]: `);
|
|
82
|
+
const proxyPort = portAnswer ? parseInt(portAnswer, 10) : existingConfig.proxyPort;
|
|
83
|
+
if (isNaN(proxyPort) || proxyPort < 1 || proxyPort > 65535) {
|
|
84
|
+
console.log(" ā Invalid port number");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Step 4: Wallet path
|
|
88
|
+
console.log("\nStep 4/4: Wallet Path");
|
|
89
|
+
console.log(` Your ecash wallet will be stored here.`);
|
|
90
|
+
const walletPathAnswer = await question(rl, ` Wallet path [${existingConfig.walletPath}]: `);
|
|
91
|
+
const walletPath = walletPathAnswer || existingConfig.walletPath;
|
|
92
|
+
// Create config
|
|
93
|
+
const config = {
|
|
94
|
+
gateUrl,
|
|
95
|
+
mintUrl,
|
|
96
|
+
walletPath,
|
|
97
|
+
proxyPort,
|
|
98
|
+
lowBalanceThreshold: DEFAULT_CONFIG.lowBalanceThreshold,
|
|
99
|
+
autoDiscover: DEFAULT_CONFIG.autoDiscover,
|
|
100
|
+
discoveryUrl: DEFAULT_CONFIG.discoveryUrl,
|
|
101
|
+
};
|
|
102
|
+
// Save config
|
|
103
|
+
await saveConfig(config);
|
|
104
|
+
console.log("\nā
Configuration saved to ~/.t2c/config.json");
|
|
105
|
+
// Initialize wallet
|
|
106
|
+
try {
|
|
107
|
+
const resolvedPath = resolveHome(walletPath);
|
|
108
|
+
const wallet = await CashuStore.load(resolvedPath, mintUrl);
|
|
109
|
+
console.log(`ā
Wallet initialized (balance: ${wallet.balance} sat)`);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
console.log(`ā ļø Failed to initialize wallet: ${e}`);
|
|
113
|
+
}
|
|
114
|
+
// Next steps
|
|
115
|
+
console.log("\nš Next steps:\n");
|
|
116
|
+
console.log(" 1. Start the proxy service:");
|
|
117
|
+
console.log(" t2c service start\n");
|
|
118
|
+
console.log(" 2. Add funds to your wallet:");
|
|
119
|
+
console.log(" t2c mint\n");
|
|
120
|
+
console.log(" 3. Configure your AI tool:");
|
|
121
|
+
console.log(" t2c config openclaw # For OpenClaw");
|
|
122
|
+
console.log(" t2c config cursor # For Cursor");
|
|
123
|
+
console.log(" t2c config env # For other tools\n");
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
rl.close();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* t2c status - Show service status and wallet balance
|
|
3
|
+
*/
|
|
4
|
+
import { loadConfig, resolveHome, configExists, checkGateHealth } from "../config.js";
|
|
5
|
+
import { CashuStore } from "../cashu-store.js";
|
|
6
|
+
async function checkProxy(port) {
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
9
|
+
signal: AbortSignal.timeout(2000),
|
|
10
|
+
});
|
|
11
|
+
if (!res.ok)
|
|
12
|
+
return false;
|
|
13
|
+
const data = (await res.json());
|
|
14
|
+
return typeof data.ok === "boolean" && data.ok === true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function statusCommand(opts) {
|
|
21
|
+
const hasConfig = await configExists();
|
|
22
|
+
const config = await loadConfig();
|
|
23
|
+
const proxyRunning = await checkProxy(config.proxyPort);
|
|
24
|
+
const gateReachable = await checkGateHealth(config.gateUrl);
|
|
25
|
+
let walletInfo = null;
|
|
26
|
+
try {
|
|
27
|
+
const walletPath = resolveHome(config.walletPath);
|
|
28
|
+
const wallet = await CashuStore.load(walletPath, config.mintUrl);
|
|
29
|
+
walletInfo = {
|
|
30
|
+
path: config.walletPath,
|
|
31
|
+
balance: wallet.balance,
|
|
32
|
+
proofs: wallet.proofCount,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Wallet doesn't exist yet
|
|
37
|
+
}
|
|
38
|
+
const result = {
|
|
39
|
+
configured: hasConfig,
|
|
40
|
+
proxyRunning,
|
|
41
|
+
proxyUrl: proxyRunning ? `http://127.0.0.1:${config.proxyPort}` : null,
|
|
42
|
+
wallet: walletInfo,
|
|
43
|
+
gate: {
|
|
44
|
+
url: config.gateUrl,
|
|
45
|
+
reachable: gateReachable,
|
|
46
|
+
},
|
|
47
|
+
mint: {
|
|
48
|
+
url: config.mintUrl,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
console.log(JSON.stringify(result, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Pretty print
|
|
56
|
+
console.log("\nšļø Token2Chat Status\n");
|
|
57
|
+
// Config
|
|
58
|
+
console.log(`Config: ${hasConfig ? "ā
Configured" : "ā ļø Not configured (run 't2c setup')"}`);
|
|
59
|
+
// Proxy
|
|
60
|
+
if (proxyRunning) {
|
|
61
|
+
console.log(`Proxy: ā
Running on http://127.0.0.1:${config.proxyPort}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(`Proxy: ā Not running (run 't2c service start')`);
|
|
65
|
+
}
|
|
66
|
+
// Gate
|
|
67
|
+
console.log(`Gate: ${gateReachable ? "ā
" : "ā"} ${config.gateUrl}`);
|
|
68
|
+
// Mint
|
|
69
|
+
console.log(`Mint: ${config.mintUrl}`);
|
|
70
|
+
// Wallet
|
|
71
|
+
console.log("");
|
|
72
|
+
if (walletInfo) {
|
|
73
|
+
const balanceStr = walletInfo.balance.toLocaleString();
|
|
74
|
+
const status = walletInfo.balance > 0 ? "ā
" : "ā ļø";
|
|
75
|
+
console.log(`Wallet: ${status} ${balanceStr} sat (${walletInfo.proofs} proofs)`);
|
|
76
|
+
if (walletInfo.balance === 0) {
|
|
77
|
+
console.log(` Run 't2c mint' to add funds`);
|
|
78
|
+
}
|
|
79
|
+
else if (walletInfo.balance < config.lowBalanceThreshold) {
|
|
80
|
+
console.log(` ā ļø Low balance - consider adding funds`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(`Wallet: ā ļø No wallet found (run 't2c setup')`);
|
|
85
|
+
}
|
|
86
|
+
console.log("");
|
|
87
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface T2CConfig {
|
|
2
|
+
gateUrl: string;
|
|
3
|
+
mintUrl: string;
|
|
4
|
+
walletPath: string;
|
|
5
|
+
proxyPort: number;
|
|
6
|
+
lowBalanceThreshold: number;
|
|
7
|
+
autoDiscover: boolean;
|
|
8
|
+
discoveryUrl: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_CONFIG: T2CConfig;
|
|
11
|
+
export declare const CONFIG_DIR: string;
|
|
12
|
+
export declare const CONFIG_PATH: string;
|
|
13
|
+
export declare const WALLET_PATH: string;
|
|
14
|
+
export declare const PID_PATH: string;
|
|
15
|
+
export declare const LOG_PATH: string;
|
|
16
|
+
export declare const PROXY_SECRET_PATH: string;
|
|
17
|
+
export declare function resolveHome(p: string): string;
|
|
18
|
+
export declare function ensureConfigDir(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Load or create the proxy authentication secret.
|
|
21
|
+
* Used by the local proxy to authenticate requests.
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadOrCreateProxySecret(): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Load config with automatic recovery from corruption
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadConfig(): Promise<T2CConfig>;
|
|
28
|
+
export declare function saveConfig(config: T2CConfig): Promise<void>;
|
|
29
|
+
export declare function configExists(): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Custom error classes for better error handling
|
|
32
|
+
*/
|
|
33
|
+
export declare class ConfigError extends Error {
|
|
34
|
+
readonly recoverable: boolean;
|
|
35
|
+
constructor(message: string, recoverable?: boolean);
|
|
36
|
+
}
|
|
37
|
+
export declare class NetworkError extends Error {
|
|
38
|
+
readonly endpoint: string;
|
|
39
|
+
readonly cause?: Error | undefined;
|
|
40
|
+
constructor(message: string, endpoint: string, cause?: Error | undefined);
|
|
41
|
+
}
|
|
42
|
+
export declare class WalletError extends Error {
|
|
43
|
+
readonly code: "INSUFFICIENT_BALANCE" | "WALLET_NOT_FOUND" | "WALLET_CORRUPTED" | "PROOF_SELECTION_FAILED";
|
|
44
|
+
constructor(message: string, code: "INSUFFICIENT_BALANCE" | "WALLET_NOT_FOUND" | "WALLET_CORRUPTED" | "PROOF_SELECTION_FAILED");
|
|
45
|
+
}
|
|
46
|
+
/** Shared options for adapter config generation commands */
|
|
47
|
+
export interface AdapterConfigOptions {
|
|
48
|
+
apply?: boolean;
|
|
49
|
+
json?: boolean;
|
|
50
|
+
proxySecret?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare const FAILED_TOKENS_PATH: string;
|
|
53
|
+
export interface FailedToken {
|
|
54
|
+
token: string;
|
|
55
|
+
type: "change" | "refund";
|
|
56
|
+
timestamp: number;
|
|
57
|
+
error: string;
|
|
58
|
+
}
|
|
59
|
+
interface FailedTokensFile {
|
|
60
|
+
tokens: FailedToken[];
|
|
61
|
+
}
|
|
62
|
+
export declare function loadFailedTokens(): Promise<FailedTokensFile>;
|
|
63
|
+
export declare function saveFailedTokens(data: FailedTokensFile): Promise<void>;
|
|
64
|
+
export declare function appendFailedToken(token: string, type: "change" | "refund", error: string): Promise<void>;
|
|
65
|
+
export declare const TRANSACTIONS_LOG_PATH: string;
|
|
66
|
+
export interface TransactionRecord {
|
|
67
|
+
id: string;
|
|
68
|
+
timestamp: number;
|
|
69
|
+
model: string;
|
|
70
|
+
priceSat: number;
|
|
71
|
+
changeSat: number;
|
|
72
|
+
refundSat: number;
|
|
73
|
+
gateStatus: number;
|
|
74
|
+
balanceBefore: number;
|
|
75
|
+
balanceAfter: number;
|
|
76
|
+
durationMs: number;
|
|
77
|
+
error?: string;
|
|
78
|
+
}
|
|
79
|
+
export declare function appendTransaction(record: TransactionRecord): Promise<void>;
|
|
80
|
+
export declare function loadTransactions(limit?: number): Promise<TransactionRecord[]>;
|
|
81
|
+
export declare function checkGateHealth(url: string): Promise<boolean>;
|
|
82
|
+
export declare function checkMintHealth(url: string): Promise<boolean>;
|
|
83
|
+
export {};
|