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/bun.lock +198 -2
- package/dist/daemon/index.js +360 -231
- package/dist/index.js +57 -3
- package/package.json +2 -3
- package/src/cli.ts +79 -1
- package/src/daemon/http/index.ts +55 -3
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
|
|
36374
|
-
const logPath =
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
package/src/daemon/http/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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);
|