routstrd 0.2.0 → 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/dist/index.js CHANGED
@@ -36370,8 +36370,8 @@ var AuditLogger = class _AuditLogger {
36370
36370
  if (typeof window === "undefined") {
36371
36371
  try {
36372
36372
  const fs2 = await import("fs");
36373
- const path2 = await import("path");
36374
- const logPath = path2.join(process.cwd(), "audit.log");
36373
+ const path = await import("path");
36374
+ const logPath = path.join(process.cwd(), "audit.log");
36375
36375
  fs2.appendFileSync(logPath, logLine);
36376
36376
  } catch (error) {
36377
36377
  console.error("[AuditLogger] Failed to write to file:", error);
@@ -36914,6 +36914,9 @@ function parsePositiveIntOrExit(value, fieldName) {
36914
36914
  }
36915
36915
  return parsed;
36916
36916
  }
36917
+ function isPositiveIntegerString(value) {
36918
+ return /^[1-9]\d*$/.test(value.trim());
36919
+ }
36917
36920
  async function printLightningInvoice(invoice) {
36918
36921
  const paymentUri = `lightning:${invoice}`;
36919
36922
  const qr = await $toString(paymentUri, {
@@ -37017,7 +37020,9 @@ ${initStderr}`.toLowerCase();
37017
37020
  logger.log(`
37018
37021
  Initialization complete!`);
37019
37022
  logger.log(`
37020
- use 'routstrd wallet receive cashu <token>' or 'routstrd wallet receive bolt11 2100' to top up your local wallet!`);
37023
+ use 'routstrd receive <cashu-token>' or 'routstrd receive 2100' to top up your local wallet using Lightning!`);
37024
+ logger.log(`
37025
+ full wallet commands still work too, e.g. 'routstrd wallet receive cashu <token>' and 'routstrd wallet receive bolt11 2100'.`);
37021
37026
  logger.log(`
37022
37027
  To ensure routstrd persists across system restarts, run: 'routstrd service install'`);
37023
37028
  }
@@ -37330,6 +37335,55 @@ program.command("top").description("Open interactive TUI for usage monitoring (a
37330
37335
  const { runUsageTui: runUsageTui2 } = await Promise.resolve().then(() => (init_usage(), exports_usage));
37331
37336
  await runUsageTui2();
37332
37337
  });
37338
+ program.command("send <target>").description("Shortcut: numbers send Cashu, non-numbers pay a Lightning invoice").option("--mint-url <url>", "Mint URL to use").action(async (target, options) => {
37339
+ if (isPositiveIntegerString(target)) {
37340
+ await handleDaemonCommand("/wallet/send/cashu", {
37341
+ method: "POST",
37342
+ body: {
37343
+ amount: parsePositiveIntOrExit(target, "amount"),
37344
+ mintUrl: options.mintUrl
37345
+ }
37346
+ });
37347
+ return;
37348
+ }
37349
+ await handleDaemonCommand("/wallet/send/bolt11", {
37350
+ method: "POST",
37351
+ body: {
37352
+ invoice: target,
37353
+ mintUrl: options.mintUrl
37354
+ }
37355
+ });
37356
+ });
37357
+ program.command("receive <value>").description("Shortcut: numbers create a Lightning invoice, non-numbers receive a Cashu token").option("--mint-url <url>", "Mint URL to use for bolt11 receive").action(async (value, options) => {
37358
+ if (isPositiveIntegerString(value)) {
37359
+ try {
37360
+ await ensureDaemonRunning();
37361
+ const result = await callDaemon("/wallet/receive/bolt11", {
37362
+ method: "POST",
37363
+ body: {
37364
+ amount: parsePositiveIntOrExit(value, "amount"),
37365
+ mintUrl: options.mintUrl
37366
+ }
37367
+ });
37368
+ const output4 = result.output;
37369
+ if (typeof output4?.invoice === "string" && output4.invoice) {
37370
+ await printLightningInvoice(output4.invoice);
37371
+ return;
37372
+ }
37373
+ if (result.output !== undefined) {
37374
+ console.log(JSON.stringify(result.output, null, 2));
37375
+ }
37376
+ } catch (error) {
37377
+ console.error(error.message);
37378
+ process.exit(1);
37379
+ }
37380
+ return;
37381
+ }
37382
+ await handleDaemonCommand("/wallet/receive/cashu", {
37383
+ method: "POST",
37384
+ body: { token: value }
37385
+ });
37386
+ });
37333
37387
  var walletCmd = program.command("wallet").description("Wallet operations");
37334
37388
  walletCmd.command("status").description("Check wallet status").action(async () => {
37335
37389
  await handleDaemonCommand("/wallet/status");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routstrd",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -13,7 +13,6 @@
13
13
  "lint": "tsc --noEmit",
14
14
  "test": "bun test",
15
15
  "build": "mkdir -p dist/daemon && bun build src/index.ts --target=bun --outfile=dist/index.js && bun build src/daemon/index.ts --target=bun --outfile=dist/daemon/index.js",
16
- "build:dev": "sed -i '' 's/\"@routstr\\/sdk\": \"\\^0.2.11\"/\"@routstr\\/sdk\": \"file:..\\/routstr-chat\\/sdk\"/' package.json && bun install && bun run build && sed -i '' 's/\"@routstr\\/sdk\": \"file:..\\/routstr-chat\\/sdk\"/\"@routstr\\/sdk\": \"^0.2.11\"/' package.json && bun install",
17
16
  "prepublishOnly": "bun run build"
18
17
  },
19
18
  "devDependencies": {
@@ -25,7 +24,7 @@
25
24
  },
26
25
  "dependencies": {
27
26
  "@cashu/cashu-ts": "^3.1.1",
28
- "@routstr/sdk": "^0.2.12",
27
+ "@routstr/sdk": "^0.3.0",
29
28
  "applesauce-core": "^5.1.0",
30
29
  "applesauce-relay": "^5.1.0",
31
30
  "commander": "^14.0.2",
package/src/cli.ts CHANGED
@@ -59,6 +59,10 @@ function parsePositiveIntOrExit(value: string, fieldName: string): number {
59
59
  return parsed;
60
60
  }
61
61
 
62
+ function isPositiveIntegerString(value: string): boolean {
63
+ return /^[1-9]\d*$/.test(value.trim());
64
+ }
65
+
62
66
  async function printLightningInvoice(invoice: string): Promise<void> {
63
67
  const paymentUri = `lightning:${invoice}`;
64
68
  const qr = await QRCode.toString(paymentUri, {
@@ -202,7 +206,10 @@ async function initDaemon(): Promise<void> {
202
206
 
203
207
  logger.log("\nInitialization complete!");
204
208
  logger.log(
205
- "\n use 'routstrd wallet receive cashu <token>' or 'routstrd wallet receive bolt11 2100' to top up your local wallet!",
209
+ "\n use 'routstrd receive <cashu-token>' or 'routstrd receive 2100' to top up your local wallet using Lightning!",
210
+ );
211
+ logger.log(
212
+ "\n full wallet commands still work too, e.g. 'routstrd wallet receive cashu <token>' and 'routstrd wallet receive bolt11 2100'.",
206
213
  );
207
214
  logger.log(
208
215
  "\nTo ensure routstrd persists across system restarts, run: 'routstrd service install'",
@@ -733,6 +740,77 @@ program
733
740
  await runUsageTui();
734
741
  });
735
742
 
743
+ program
744
+ .command("send <target>")
745
+ .description(
746
+ "Shortcut: numbers send Cashu, non-numbers pay a Lightning invoice",
747
+ )
748
+ .option("--mint-url <url>", "Mint URL to use")
749
+ .action(async (target: string, options: { mintUrl?: string }) => {
750
+ if (isPositiveIntegerString(target)) {
751
+ await handleDaemonCommand("/wallet/send/cashu", {
752
+ method: "POST",
753
+ body: {
754
+ amount: parsePositiveIntOrExit(target, "amount"),
755
+ mintUrl: options.mintUrl,
756
+ },
757
+ });
758
+ return;
759
+ }
760
+
761
+ await handleDaemonCommand("/wallet/send/bolt11", {
762
+ method: "POST",
763
+ body: {
764
+ invoice: target,
765
+ mintUrl: options.mintUrl,
766
+ },
767
+ });
768
+ });
769
+
770
+ program
771
+ .command("receive <value>")
772
+ .description(
773
+ "Shortcut: numbers create a Lightning invoice, non-numbers receive a Cashu token",
774
+ )
775
+ .option("--mint-url <url>", "Mint URL to use for bolt11 receive")
776
+ .action(async (value: string, options: { mintUrl?: string }) => {
777
+ if (isPositiveIntegerString(value)) {
778
+ try {
779
+ await ensureDaemonRunning();
780
+
781
+ const result = await callDaemon("/wallet/receive/bolt11", {
782
+ method: "POST",
783
+ body: {
784
+ amount: parsePositiveIntOrExit(value, "amount"),
785
+ mintUrl: options.mintUrl,
786
+ },
787
+ });
788
+
789
+ const output = result.output as
790
+ | { invoice?: string; amount?: number; mintUrl?: string }
791
+ | undefined;
792
+
793
+ if (typeof output?.invoice === "string" && output.invoice) {
794
+ await printLightningInvoice(output.invoice);
795
+ return;
796
+ }
797
+
798
+ if (result.output !== undefined) {
799
+ console.log(JSON.stringify(result.output, null, 2));
800
+ }
801
+ } catch (error) {
802
+ console.error((error as Error).message);
803
+ process.exit(1);
804
+ }
805
+ return;
806
+ }
807
+
808
+ await handleDaemonCommand("/wallet/receive/cashu", {
809
+ method: "POST",
810
+ body: { token: value },
811
+ });
812
+ });
813
+
736
814
  const walletCmd = program.command("wallet").description("Wallet operations");
737
815
 
738
816
  walletCmd
@@ -1,7 +1,8 @@
1
1
  import { randomBytes } from "crypto";
2
2
  import { type IncomingMessage, type ServerResponse } from "http";
3
+ import { Readable } from "stream";
3
4
  import {
4
- routeRequestsToNodeResponse,
5
+ routeRequests,
5
6
  InsufficientBalanceError,
6
7
  ProviderManager,
7
8
  } from "@routstr/sdk";
@@ -968,7 +969,8 @@ export function createDaemonRequestHandler(deps: {
968
969
  try {
969
970
  await deps.ensureProvidersBootstrapped();
970
971
  logger.log("Routing request with path: ", url.pathname);
971
- await routeRequestsToNodeResponse({
972
+
973
+ const response = await routeRequests({
972
974
  modelId,
973
975
  requestBody,
974
976
  path: url.pathname,
@@ -984,8 +986,58 @@ export function createDaemonRequestHandler(deps: {
984
986
  usageTrackingDriver: deps.usageTrackingDriver,
985
987
  sdkStore: deps.store,
986
988
  providerManager: deps.providerManager,
987
- res,
988
989
  });
990
+
991
+ // Bridge the Web `Response` to the Node `ServerResponse` with no
992
+ // transforms: status + headers + pipe(body → res).
993
+ res.statusCode = response.status;
994
+ response.headers.forEach((value, key) => {
995
+ res.setHeader(key, value);
996
+ });
997
+
998
+ const finalize = (response as any).finalize as
999
+ | (() => Promise<number>)
1000
+ | undefined;
1001
+
1002
+ if (!response.body) {
1003
+ res.end();
1004
+ if (finalize) {
1005
+ try {
1006
+ await finalize();
1007
+ } catch (err) {
1008
+ logger.error(`[daemon] finalize error: ${toErrorMessage(err)}`);
1009
+ }
1010
+ }
1011
+ return;
1012
+ }
1013
+
1014
+ const nodeReadable = Readable.fromWeb(response.body as any);
1015
+ await new Promise<void>((resolve, reject) => {
1016
+ let settled = false;
1017
+ const finish = () => {
1018
+ if (settled) return;
1019
+ settled = true;
1020
+ resolve();
1021
+ };
1022
+ const fail = (err: unknown) => {
1023
+ if (settled) return;
1024
+ settled = true;
1025
+ reject(err);
1026
+ };
1027
+ res.once("finish", finish);
1028
+ res.once("close", finish);
1029
+ res.once("error", fail);
1030
+ nodeReadable.once("error", fail);
1031
+ nodeReadable.pipe(res);
1032
+ });
1033
+
1034
+ if (finalize) {
1035
+ try {
1036
+ await finalize();
1037
+ } catch (err) {
1038
+ logger.error(`[daemon] finalize error: ${toErrorMessage(err)}`);
1039
+ }
1040
+ }
989
1041
  return;
990
1042
  } catch (error) {
991
1043
  const message = error instanceof Error ? error.message : String(error);