routstrd 0.1.1 → 0.1.3

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/src/cli.ts CHANGED
@@ -13,13 +13,18 @@ import {
13
13
  DB_PATH,
14
14
  CONFIG_FILE,
15
15
  DEFAULT_CONFIG,
16
- LOG_FILE,
16
+ LOGS_DIR,
17
17
  type RoutstrdConfig,
18
18
  } from "./utils/config";
19
19
  import { logger } from "./utils/logger";
20
20
  import { setupIntegration } from "./integrations";
21
21
  import { createSdkStore } from "@routstr/sdk";
22
22
  import { createBunSqliteDriver } from "@routstr/sdk/storage";
23
+ import * as QRCode from "qrcode";
24
+ import {
25
+ isCocodInstalled,
26
+ resolveCocodExecutable,
27
+ } from "./daemon/wallet/cocod-client";
23
28
 
24
29
  type RoutstrModel = {
25
30
  id: string;
@@ -42,7 +47,26 @@ type UsageEntry = {
42
47
  client?: string;
43
48
  };
44
49
 
45
- const cliVersion = "0.1.0";
50
+ const cliVersion = "0.1.1";
51
+
52
+ function parsePositiveIntOrExit(value: string, fieldName: string): number {
53
+ const parsed = Number.parseInt(value, 10);
54
+ if (!Number.isFinite(parsed) || parsed <= 0) {
55
+ console.error(`Invalid ${fieldName}: ${value}`);
56
+ process.exit(1);
57
+ }
58
+ return parsed;
59
+ }
60
+
61
+ async function printLightningInvoice(invoice: string): Promise<void> {
62
+ const paymentUri = `lightning:${invoice}`;
63
+ const qr = await QRCode.toString(paymentUri, {
64
+ type: "terminal",
65
+ small: true,
66
+ });
67
+
68
+ console.log(`${qr}\nInvoice:\n${invoice}`);
69
+ }
46
70
 
47
71
  async function initDaemon(): Promise<void> {
48
72
  logger.log("Initializing routstrd...");
@@ -57,7 +81,9 @@ async function initDaemon(): Promise<void> {
57
81
 
58
82
  const installCode = await installProc.exited;
59
83
  if (installCode !== 0 || !(await checkCocodInstalled())) {
60
- logger.error("Failed to install cocod. Please run 'bun install --global cocod' manually.");
84
+ logger.error(
85
+ "Failed to install cocod. Please run 'bun install --global cocod' manually.",
86
+ );
61
87
  return;
62
88
  }
63
89
 
@@ -80,10 +106,39 @@ async function initDaemon(): Promise<void> {
80
106
  logger.log(`Created config file: ${CONFIG_FILE}`);
81
107
  }
82
108
 
109
+ const config = await loadConfig();
110
+ const cocodExecutable = resolveCocodExecutable(config.cocodPath);
111
+
112
+ if (!(await isCocodInstalled(config.cocodPath))) {
113
+ if (config.cocodPath) {
114
+ logger.error(
115
+ `Configured cocod executable was not found: ${config.cocodPath}`,
116
+ );
117
+ return;
118
+ }
119
+
120
+ logger.log("cocod not found. Installing globally with bun...");
121
+
122
+ const installProc = Bun.spawn(["bun", "install", "--global", "cocod"], {
123
+ stdout: "inherit",
124
+ stderr: "inherit",
125
+ });
126
+
127
+ const installCode = await installProc.exited;
128
+ if (installCode !== 0 || !(await isCocodInstalled(config.cocodPath))) {
129
+ logger.error(
130
+ "Failed to install cocod. Please run 'bun install --global cocod' manually.",
131
+ );
132
+ return;
133
+ }
134
+
135
+ logger.log("cocod installed successfully.");
136
+ }
137
+
83
138
  console.log(`Database will be stored at: ${DB_PATH}`);
84
139
  console.log("\nInitializing cocod...");
85
140
 
86
- const initProc = Bun.spawn(["cocod", "init"], {
141
+ const initProc = Bun.spawn([cocodExecutable, "init"], {
87
142
  stdout: "pipe",
88
143
  stderr: "pipe",
89
144
  });
@@ -115,12 +170,18 @@ async function initDaemon(): Promise<void> {
115
170
  )
116
171
  : Promise.resolve();
117
172
 
118
- const [initCode] = await Promise.all([initProc.exited, stdoutDone, stderrDone]);
173
+ const [initCode] = await Promise.all([
174
+ initProc.exited,
175
+ stdoutDone,
176
+ stderrDone,
177
+ ]);
119
178
  const combinedOutput = `${initStdout}\n${initStderr}`.toLowerCase();
120
179
  const alreadyInitialized = combinedOutput.includes("already initialized");
121
180
 
122
181
  if (initCode !== 0 && !alreadyInitialized) {
123
- logger.error("Failed to initialize cocod. Please run 'cocod init' manually.");
182
+ logger.error(
183
+ "Failed to initialize cocod. Please run 'cocod init' manually.",
184
+ );
124
185
  return;
125
186
  }
126
187
 
@@ -130,7 +191,6 @@ async function initDaemon(): Promise<void> {
130
191
  logger.log("cocod initialized successfully.");
131
192
  }
132
193
 
133
- const config = await loadConfig();
134
194
  await startDaemon({ port: String(config.port || 8008) });
135
195
 
136
196
  // Create SDK store for integrations
@@ -140,7 +200,9 @@ async function initDaemon(): Promise<void> {
140
200
  await setupIntegration(config, store);
141
201
 
142
202
  logger.log("\nInitialization complete!");
143
- logger.log("\n use 'cocod receive cashu' or 'cocod receive bolt11 2100' to top up your local wallet!");
203
+ logger.log(
204
+ "\n use 'routstrd wallet receive cashu <token>' or 'routstrd wallet receive bolt11 2100' to top up your local wallet!",
205
+ );
144
206
  }
145
207
 
146
208
  async function checkCocodInstalled(): Promise<boolean> {
@@ -164,7 +226,9 @@ program
164
226
  // Onboard - initialize the daemon
165
227
  program
166
228
  .command("onboard")
167
- .description("Initialize routstrd (creates config directory and initializes cocod)")
229
+ .description(
230
+ "Initialize routstrd (creates config directory and initializes cocod)",
231
+ )
168
232
  .action(async () => {
169
233
  await initDaemon();
170
234
  });
@@ -176,11 +240,14 @@ program
176
240
  .option("--port <port>", "Port to listen on")
177
241
  .option("-p, --provider <provider>", "Default provider to use")
178
242
  .action(async (options: { port?: string; provider?: string }) => {
179
- if (!(await checkCocodInstalled())) {
180
- logger.error("cocod is not installed. Run 'routstrd onboard' first to install cocod.");
243
+ const config = await loadConfig();
244
+ if (!(await isCocodInstalled(config.cocodPath))) {
245
+ const installHint = config.cocodPath
246
+ ? `Configured cocod executable was not found: ${config.cocodPath}`
247
+ : "cocod is not installed. Run 'routstrd onboard' first to install cocod.";
248
+ logger.error(installHint);
181
249
  process.exit(1);
182
250
  }
183
- const config = await loadConfig();
184
251
  await startDaemon({
185
252
  port: options.port || String(config.port || 8008),
186
253
  provider: options.provider,
@@ -224,7 +291,7 @@ program
224
291
  .description("Get wallet and API key balances")
225
292
  .action(async () => {
226
293
  await ensureDaemonRunning();
227
-
294
+
228
295
  const [walletResult, keysResult] = await Promise.all([
229
296
  callDaemon("/balance"),
230
297
  callDaemon("/keys/balance"),
@@ -234,8 +301,14 @@ program
234
301
 
235
302
  console.log("=== Wallet Balance ===");
236
303
  let totalWallet = 0;
237
- if (walletResult.output && typeof walletResult.output === "object" && "balances" in walletResult.output) {
238
- const balances = (walletResult.output as { balances: Record<string, number> }).balances;
304
+ if (
305
+ walletResult.output &&
306
+ typeof walletResult.output === "object" &&
307
+ "balances" in walletResult.output
308
+ ) {
309
+ const balances = (
310
+ walletResult.output as { balances: Record<string, number> }
311
+ ).balances;
239
312
  for (const [mintUrl, balance] of Object.entries(balances)) {
240
313
  console.log(` ${mintUrl}: ${balance} sats`);
241
314
  totalWallet += balance;
@@ -247,9 +320,17 @@ program
247
320
 
248
321
  console.log("\n=== API Keys ===");
249
322
  let totalApiKeys = 0;
250
- if (keysResult.output && typeof keysResult.output === "object" && "keys" in keysResult.output) {
251
- const keys = (keysResult.output as { keys: Array<{ id: string; name: string; balance: number }> }).keys;
252
- const apiKeyEntries = keys.filter(k => k.id.startsWith("apikey:"));
323
+ if (
324
+ keysResult.output &&
325
+ typeof keysResult.output === "object" &&
326
+ "keys" in keysResult.output
327
+ ) {
328
+ const keys = (
329
+ keysResult.output as {
330
+ keys: Array<{ id: string; name: string; balance: number }>;
331
+ }
332
+ ).keys;
333
+ const apiKeyEntries = keys.filter((k) => k.id.startsWith("apikey:"));
253
334
  for (const key of apiKeyEntries) {
254
335
  const name = key.name.replace("API Key: ", "");
255
336
  console.log(` ${name}: ${key.balance} sats`);
@@ -265,7 +346,9 @@ program
265
346
  }
266
347
 
267
348
  console.log("\n=== Summary ===");
268
- console.log(` Wallet: ${totalWallet} sats | API Keys: ${totalApiKeys} sats`);
349
+ console.log(
350
+ ` Wallet: ${totalWallet} sats | API Keys: ${totalApiKeys} sats`,
351
+ );
269
352
  console.log(` Grand Total: ${totalWallet + totalApiKeys} sats`);
270
353
  });
271
354
 
@@ -284,7 +367,7 @@ program
284
367
  .option("-r, --refresh", "Force refresh routstr21 models from Nostr", false)
285
368
  .action(async (options: { refresh: boolean }) => {
286
369
  await ensureDaemonRunning();
287
-
370
+
288
371
  const result = await callDaemon(
289
372
  options.refresh ? "/models?refresh=true" : "/models",
290
373
  );
@@ -293,7 +376,11 @@ program
293
376
  process.exit(1);
294
377
  }
295
378
 
296
- if (result.output && typeof result.output === "object" && "models" in result.output) {
379
+ if (
380
+ result.output &&
381
+ typeof result.output === "object" &&
382
+ "models" in result.output
383
+ ) {
297
384
  const models = (result.output as { models: RoutstrModel[] }).models;
298
385
  if (models.length === 0) {
299
386
  console.log("No routstr21 models found");
@@ -303,7 +390,9 @@ program
303
390
  const details = [
304
391
  model.name && model.name !== model.id ? model.name : null,
305
392
  model.context_length ? `${model.context_length} ctx` : null,
306
- ].filter(Boolean).join(" - ");
393
+ ]
394
+ .filter(Boolean)
395
+ .join(" - ");
307
396
  console.log(`${i + 1}. ${model.id}${details ? ` (${details})` : ""}`);
308
397
  });
309
398
  }
@@ -319,7 +408,9 @@ program
319
408
 
320
409
  const requested = Number.parseInt(options.limit, 10);
321
410
  const limit =
322
- Number.isFinite(requested) && requested > 0 ? Math.min(requested, 1000) : 10;
411
+ Number.isFinite(requested) && requested > 0
412
+ ? Math.min(requested, 1000)
413
+ : 10;
323
414
 
324
415
  const result = await callDaemon(`/usage?limit=${limit}`);
325
416
  if (result.error) {
@@ -327,20 +418,16 @@ program
327
418
  process.exit(1);
328
419
  }
329
420
 
330
- const output = result.output as
331
- | {
332
- entries?: UsageEntry[];
333
- totalEntries?: number;
334
- totalSatsCost?: number;
335
- recentSatsCost?: number;
336
- limit?: number;
337
- }
338
- | undefined;
421
+ // The daemon returns { output: UsageEntry[] } where output is the array directly
422
+ const entries = (result.output as UsageEntry[] | undefined) || [];
339
423
 
340
- const entries = output?.entries || [];
341
- const totalEntries = output?.totalEntries || 0;
342
- const totalSatsCost = output?.totalSatsCost || 0;
343
- const recentSatsCost = output?.recentSatsCost || 0;
424
+ // Calculate totals from entries
425
+ const totalEntries = entries.length;
426
+ const totalSatsCost = entries.reduce(
427
+ (sum, e) => sum + (e.satsCost || 0),
428
+ 0,
429
+ );
430
+ const recentSatsCost = totalSatsCost; // For now, recent = total since we don't have time window
344
431
 
345
432
  console.log(`Usage entries: showing ${entries.length} of ${totalEntries}`);
346
433
  console.log(`Total sats cost (all time): ${totalSatsCost.toFixed(3)} sats`);
@@ -383,18 +470,26 @@ providersCmd
383
470
  process.exit(1);
384
471
  }
385
472
 
386
- const output = result.output as {
387
- providers: Array<{ index: number; baseUrl: string; disabled: boolean }>;
388
- disabledCount: number;
389
- totalCount: number;
390
- } | undefined;
473
+ const output = result.output as
474
+ | {
475
+ providers: Array<{
476
+ index: number;
477
+ baseUrl: string;
478
+ disabled: boolean;
479
+ }>;
480
+ disabledCount: number;
481
+ totalCount: number;
482
+ }
483
+ | undefined;
391
484
 
392
485
  if (!output?.providers) {
393
486
  console.log("No providers found.");
394
487
  return;
395
488
  }
396
489
 
397
- console.log(`Providers (${output.totalCount} total, ${output.disabledCount} disabled):\n`);
490
+ console.log(
491
+ `Providers (${output.totalCount} total, ${output.disabledCount} disabled):\n`,
492
+ );
398
493
  for (const provider of output.providers) {
399
494
  const status = provider.disabled ? "DISABLED" : "enabled ";
400
495
  console.log(` [${provider.index}] ${status} ${provider.baseUrl}`);
@@ -403,11 +498,15 @@ providersCmd
403
498
 
404
499
  providersCmd
405
500
  .command("disable <indices...>")
406
- .description("Disable providers by their indices (e.g., routstrd providers disable 0 2 5)")
501
+ .description(
502
+ "Disable providers by their indices (e.g., routstrd providers disable 0 2 5)",
503
+ )
407
504
  .action(async (indices: string[]) => {
408
505
  await ensureDaemonRunning();
409
506
 
410
- const indexNums = indices.map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n));
507
+ const indexNums = indices
508
+ .map((s) => parseInt(s, 10))
509
+ .filter((n) => Number.isFinite(n));
411
510
  if (indexNums.length === 0) {
412
511
  console.log("No valid indices provided.");
413
512
  process.exit(1);
@@ -423,7 +522,9 @@ providersCmd
423
522
  process.exit(1);
424
523
  }
425
524
 
426
- const output = result.output as { message: string; disabled: string[] } | undefined;
525
+ const output = result.output as
526
+ | { message: string; disabled: string[] }
527
+ | undefined;
427
528
  if (output) {
428
529
  console.log(output.message);
429
530
  for (const url of output.disabled) {
@@ -434,11 +535,15 @@ providersCmd
434
535
 
435
536
  providersCmd
436
537
  .command("enable <indices...>")
437
- .description("Enable providers by their indices (e.g., routstrd providers enable 0 2 5)")
538
+ .description(
539
+ "Enable providers by their indices (e.g., routstrd providers enable 0 2 5)",
540
+ )
438
541
  .action(async (indices: string[]) => {
439
542
  await ensureDaemonRunning();
440
543
 
441
- const indexNums = indices.map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n));
544
+ const indexNums = indices
545
+ .map((s) => parseInt(s, 10))
546
+ .filter((n) => Number.isFinite(n));
442
547
  if (indexNums.length === 0) {
443
548
  console.log("No valid indices provided.");
444
549
  process.exit(1);
@@ -454,7 +559,9 @@ providersCmd
454
559
  process.exit(1);
455
560
  }
456
561
 
457
- const output = result.output as { message: string; enabled: string[] } | undefined;
562
+ const output = result.output as
563
+ | { message: string; enabled: string[] }
564
+ | undefined;
458
565
  if (output) {
459
566
  console.log(output.message);
460
567
  for (const url of output.enabled) {
@@ -463,6 +570,96 @@ providersCmd
463
570
  }
464
571
  });
465
572
 
573
+ // Clients - list and manage clients
574
+ const clientsCmd = program
575
+ .command("clients")
576
+ .description("List and manage clients");
577
+
578
+ clientsCmd
579
+ .command("list")
580
+ .description("List all clients")
581
+ .action(async () => {
582
+ await ensureDaemonRunning();
583
+
584
+ const result = await callDaemon("/clients");
585
+ if (result.error) {
586
+ console.log(result.error);
587
+ process.exit(1);
588
+ }
589
+
590
+ const output = result.output as
591
+ | {
592
+ clients: Array<{
593
+ id: string;
594
+ name: string;
595
+ apiKey: string;
596
+ createdAt: number;
597
+ lastUsed?: number | null;
598
+ }>;
599
+ totalCount: number;
600
+ }
601
+ | undefined;
602
+
603
+ if (!output?.clients || output.clients.length === 0) {
604
+ console.log("No clients found.");
605
+ return;
606
+ }
607
+
608
+ console.log(`Clients (${output.totalCount} total):\n`);
609
+ for (const client of output.clients) {
610
+ const createdAt = new Date(client.createdAt).toISOString();
611
+ const lastUsed = client.lastUsed
612
+ ? new Date(client.lastUsed).toISOString()
613
+ : "never";
614
+ console.log(` ${client.id}`);
615
+ console.log(` Name: ${client.name}`);
616
+ console.log(` API Key: ${client.apiKey}`);
617
+ console.log(` Created: ${createdAt}`);
618
+ console.log("");
619
+ }
620
+ });
621
+
622
+ clientsCmd
623
+ .command("add")
624
+ .description("Add a new client")
625
+ .requiredOption("-n, --name <name>", "Client name")
626
+ .action(async (options: { name: string }) => {
627
+ await ensureDaemonRunning();
628
+ const config = await loadConfig();
629
+
630
+ const result = await callDaemon("/clients/add", {
631
+ method: "POST",
632
+ body: { name: options.name },
633
+ });
634
+
635
+ if (result.error) {
636
+ console.log(result.error);
637
+ process.exit(1);
638
+ }
639
+
640
+ const output = result.output as
641
+ | {
642
+ message: string;
643
+ client: {
644
+ id: string;
645
+ name: string;
646
+ apiKey: string;
647
+ createdAt: number;
648
+ };
649
+ }
650
+ | undefined;
651
+
652
+ if (output) {
653
+ console.log(output.message);
654
+ console.log(`\n ID: ${output.client.id}`);
655
+ console.log(` Name: ${output.client.name}`);
656
+ console.log(` API Key: ${output.client.apiKey}`);
657
+ console.log(
658
+ `\n Access Routstr at: http://localhost:${config.port || 8008}`,
659
+ );
660
+ }
661
+ });
662
+
466
663
  // Monitor - interactive TUI
467
664
  program
468
665
  .command("monitor")
@@ -472,6 +669,143 @@ program
472
669
  await runUsageTui();
473
670
  });
474
671
 
672
+ const walletCmd = program.command("wallet").description("Wallet operations");
673
+
674
+ walletCmd
675
+ .command("status")
676
+ .description("Check wallet status")
677
+ .action(async () => {
678
+ await handleDaemonCommand("/wallet/status");
679
+ });
680
+
681
+ walletCmd
682
+ .command("unlock <passphrase>")
683
+ .description("Unlock the wallet")
684
+ .action(async (passphrase: string) => {
685
+ await handleDaemonCommand("/wallet/unlock", {
686
+ method: "POST",
687
+ body: { passphrase },
688
+ });
689
+ });
690
+
691
+ walletCmd
692
+ .command("balance")
693
+ .description("Get wallet balance")
694
+ .action(async () => {
695
+ await handleDaemonCommand("/wallet/balance");
696
+ });
697
+
698
+ const walletReceiveCmd = walletCmd
699
+ .command("receive")
700
+ .description("Wallet receive operations");
701
+
702
+ walletReceiveCmd
703
+ .command("cashu <token>")
704
+ .description("Receive a Cashu token")
705
+ .action(async (token: string) => {
706
+ await handleDaemonCommand("/wallet/receive/cashu", {
707
+ method: "POST",
708
+ body: { token },
709
+ });
710
+ });
711
+
712
+ walletReceiveCmd
713
+ .command("bolt11 <amount>")
714
+ .description("Create a Lightning invoice")
715
+ .option("--mint-url <url>", "Mint URL to use")
716
+ .action(async (amount: string, options: { mintUrl?: string }) => {
717
+ try {
718
+ await ensureDaemonRunning();
719
+
720
+ const result = await callDaemon("/wallet/receive/bolt11", {
721
+ method: "POST",
722
+ body: {
723
+ amount: parsePositiveIntOrExit(amount, "amount"),
724
+ mintUrl: options.mintUrl,
725
+ },
726
+ });
727
+
728
+ const output = result.output as
729
+ | { invoice?: string; amount?: number; mintUrl?: string }
730
+ | undefined;
731
+
732
+ if (typeof output?.invoice === "string" && output.invoice) {
733
+ await printLightningInvoice(output.invoice);
734
+ return;
735
+ }
736
+
737
+ if (result.output !== undefined) {
738
+ console.log(JSON.stringify(result.output, null, 2));
739
+ }
740
+ } catch (error) {
741
+ console.error((error as Error).message);
742
+ process.exit(1);
743
+ }
744
+ });
745
+
746
+ const walletSendCmd = walletCmd
747
+ .command("send")
748
+ .description("Wallet send operations");
749
+
750
+ walletSendCmd
751
+ .command("cashu <amount>")
752
+ .description("Create a Cashu token to send")
753
+ .option("--mint-url <url>", "Mint URL to use")
754
+ .action(async (amount: string, options: { mintUrl?: string }) => {
755
+ await handleDaemonCommand("/wallet/send/cashu", {
756
+ method: "POST",
757
+ body: {
758
+ amount: parsePositiveIntOrExit(amount, "amount"),
759
+ mintUrl: options.mintUrl,
760
+ },
761
+ });
762
+ });
763
+
764
+ walletSendCmd
765
+ .command("bolt11 <invoice>")
766
+ .description("Pay a Lightning invoice")
767
+ .option("--mint-url <url>", "Mint URL to use")
768
+ .action(async (invoice: string, options: { mintUrl?: string }) => {
769
+ await handleDaemonCommand("/wallet/send/bolt11", {
770
+ method: "POST",
771
+ body: {
772
+ invoice,
773
+ mintUrl: options.mintUrl,
774
+ },
775
+ });
776
+ });
777
+
778
+ const walletMintsCmd = walletCmd
779
+ .command("mints")
780
+ .description("Wallet mint operations");
781
+
782
+ walletMintsCmd
783
+ .command("list")
784
+ .description("List configured wallet mints")
785
+ .action(async () => {
786
+ await handleDaemonCommand("/wallet/mints");
787
+ });
788
+
789
+ walletMintsCmd
790
+ .command("add <url>")
791
+ .description("Add a wallet mint")
792
+ .action(async (url: string) => {
793
+ await handleDaemonCommand("/wallet/mints", {
794
+ method: "POST",
795
+ body: { url },
796
+ });
797
+ });
798
+
799
+ walletMintsCmd
800
+ .command("info <url>")
801
+ .description("Get wallet mint info")
802
+ .action(async (url: string) => {
803
+ await handleDaemonCommand("/wallet/mints/info", {
804
+ method: "POST",
805
+ body: { url },
806
+ });
807
+ });
808
+
475
809
  // Stop
476
810
  program
477
811
  .command("stop")
@@ -493,7 +827,7 @@ program
493
827
  if (wasRunning) {
494
828
  console.log("Stopping daemon...");
495
829
  await callDaemon("/stop", { method: "POST" });
496
-
830
+
497
831
  // Wait for daemon to fully stop
498
832
  for (let i = 0; i < 50; i++) {
499
833
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -522,32 +856,43 @@ program
522
856
  // Mode
523
857
  program
524
858
  .command("mode")
525
- .description("Set the client mode (xcashu or apikeys)")
859
+ .description("Set the client mode (lazyrefund/apikeys or xcashu)")
526
860
  .action(async () => {
527
861
  const config = await loadConfig();
528
862
  const currentMode = config.mode || "apikeys";
529
-
863
+
530
864
  console.log("Select client mode:");
531
- console.log(" 1) apikeys - Pseudonymous accounts are kept with the Routstr nodes for easy topup and refunds.");
532
- console.log(" 2) xcashu - Balances are never kept with the nodes, all balances are refunded in response.");
865
+ console.log(
866
+ " 1) lazyrefund/apikeys - Pseudonymous accounts are kept with the Routstr nodes and are refunded after 5 mins if not used.",
867
+ );
868
+ console.log(
869
+ " 2) xcashu (coming soon) - Balances are never kept with the nodes, all balances are refunded in response.",
870
+ );
533
871
  console.log(`\nCurrent mode: ${currentMode}`);
534
872
 
535
873
  const modes: Array<"apikeys" | "xcashu"> = ["apikeys", "xcashu"];
536
-
874
+
537
875
  const selectedIndex = await new Promise<number>((resolve) => {
538
876
  const rl = require("readline").createInterface({
539
877
  input: process.stdin,
540
878
  output: process.stdout,
541
879
  });
542
- rl.question("\nEnter choice (1-3): ", (answer: string) => {
880
+ rl.question("\nEnter choice (1-2): ", (answer: string) => {
543
881
  rl.close();
544
882
  const num = parseInt(answer, 10);
545
- resolve(Number.isFinite(num) && num >= 1 && num <= 3 ? num - 1 : 0);
883
+ resolve(Number.isFinite(num) && num >= 1 && num <= 2 ? num - 1 : 0);
546
884
  });
547
885
  });
548
886
 
549
887
  const selectedMode = modes[selectedIndex];
550
-
888
+
889
+ if (selectedMode === "xcashu") {
890
+ console.log(
891
+ "\nxcashu mode is coming soon! Only lazyrefund/apikeys is available at this time.",
892
+ );
893
+ return;
894
+ }
895
+
551
896
  if (selectedMode === currentMode) {
552
897
  console.log(`Mode is already set to '${selectedMode}'. No changes made.`);
553
898
  return;
@@ -566,7 +911,7 @@ program
566
911
  if (wasRunning) {
567
912
  console.log("Stopping daemon...");
568
913
  await callDaemon("/stop", { method: "POST" });
569
-
914
+
570
915
  for (let i = 0; i < 50; i++) {
571
916
  await new Promise((resolve) => setTimeout(resolve, 100));
572
917
  if (!(await isDaemonRunning())) {
@@ -590,22 +935,47 @@ program
590
935
  });
591
936
 
592
937
  // Logs
938
+ function getLogFileForDate(date: Date = new Date()): string {
939
+ const year = date.getFullYear();
940
+ const month = String(date.getMonth() + 1).padStart(2, "0");
941
+ const day = String(date.getDate()).padStart(2, "0");
942
+ return `${LOGS_DIR}/${year}-${month}-${day}.log`;
943
+ }
944
+
593
945
  program
594
946
  .command("logs")
595
947
  .description("View daemon logs")
596
948
  .option("-f, --follow", "Follow log output", false)
597
949
  .option("-n, --lines <number>", "Number of lines to show", "50")
598
950
  .action(async (options: { follow: boolean; lines: string }) => {
599
- if (!existsSync(LOG_FILE)) {
600
- console.log("No log file found. Daemon may not have started yet.");
951
+ const todayFile = getLogFileForDate();
952
+ const yesterday = new Date();
953
+ yesterday.setDate(yesterday.getDate() - 1);
954
+ const yesterdayFile = getLogFileForDate(yesterday);
955
+
956
+ if (!existsSync(todayFile) && !existsSync(yesterdayFile)) {
957
+ console.log("No log files found. Daemon may not have started yet.");
958
+ console.log(`Logs directory: ${LOGS_DIR}`);
601
959
  process.exit(1);
602
960
  }
603
961
 
604
962
  const lines = parseInt(options.lines, 10);
605
963
 
606
964
  const readLastLines = async (): Promise<string[]> => {
607
- const content = await Bun.file(LOG_FILE).text();
608
- const allLines = content.split("\n").filter(Boolean);
965
+ let allLines: string[] = [];
966
+
967
+ // Read yesterday's log first if it exists
968
+ if (existsSync(yesterdayFile)) {
969
+ const yesterdayContent = await Bun.file(yesterdayFile).text();
970
+ allLines = yesterdayContent.split("\n").filter(Boolean);
971
+ }
972
+
973
+ // Then read today's log
974
+ if (existsSync(todayFile)) {
975
+ const todayContent = await Bun.file(todayFile).text();
976
+ allLines = allLines.concat(todayContent.split("\n").filter(Boolean));
977
+ }
978
+
609
979
  return allLines.slice(-lines);
610
980
  };
611
981
 
@@ -617,22 +987,40 @@ program
617
987
  };
618
988
 
619
989
  if (options.follow) {
620
- const logFile = Bun.file(LOG_FILE);
621
- const initialContent = await logFile.text();
622
- let lastSize = initialContent.length;
623
-
990
+ let currentLogFile = todayFile;
991
+ let lastSize = 0;
992
+
993
+ if (existsSync(currentLogFile)) {
994
+ lastSize = (await Bun.file(currentLogFile).text()).length;
995
+ }
996
+
624
997
  await printLines();
625
998
 
626
999
  const interval = setInterval(async () => {
627
- const content = await Bun.file(LOG_FILE).text();
628
- const currentSize = content.length;
629
- if (currentSize > lastSize) {
630
- const allLines = content.split("\n").filter(Boolean);
631
- const newLines = allLines.slice(Math.floor(lastSize === 0 ? 0 : -1), -1);
632
- for (const line of newLines) {
633
- console.log(line);
1000
+ // Check if we need to switch to a new date file
1001
+ const newLogFile = getLogFileForDate();
1002
+ if (newLogFile !== currentLogFile) {
1003
+ console.log(`\n--- Switched to ${newLogFile} ---\n`);
1004
+ currentLogFile = newLogFile;
1005
+ lastSize = existsSync(currentLogFile)
1006
+ ? (await Bun.file(currentLogFile).text()).length
1007
+ : 0;
1008
+ }
1009
+
1010
+ if (existsSync(currentLogFile)) {
1011
+ const content = await Bun.file(currentLogFile).text();
1012
+ const currentSize = content.length;
1013
+ if (currentSize > lastSize) {
1014
+ const allLines = content.split("\n").filter(Boolean);
1015
+ const newLines = allLines.slice(
1016
+ Math.floor(lastSize === 0 ? 0 : -1),
1017
+ -1,
1018
+ );
1019
+ for (const line of newLines) {
1020
+ console.log(line);
1021
+ }
1022
+ lastSize = currentSize;
634
1023
  }
635
- lastSize = currentSize;
636
1024
  }
637
1025
  }, 1000);
638
1026