routstrd 0.1.7 → 0.1.9
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/.claude/settings.local.json +4 -1
- package/bun.lock +2 -2
- package/dist/daemon/index.js +298 -302
- package/dist/index.js +161 -72
- package/package.json +3 -2
- package/src/cli.ts +113 -65
- package/src/daemon/http/index.ts +0 -236
- package/src/daemon/wallet/index.ts +1 -1
- package/src/integrations/claudecode.ts +77 -0
- package/src/integrations/index.ts +9 -2
- package/src/integrations/registry.ts +7 -0
package/dist/index.js
CHANGED
|
@@ -28789,12 +28789,13 @@ async function startDaemon(options = {}) {
|
|
|
28789
28789
|
// src/cli.ts
|
|
28790
28790
|
init_cli_shared();
|
|
28791
28791
|
init_config();
|
|
28792
|
-
import { existsSync as
|
|
28792
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
28793
|
+
import { execSync } from "child_process";
|
|
28793
28794
|
|
|
28794
28795
|
// src/integrations/opencode.ts
|
|
28795
|
-
import { existsSync as
|
|
28796
|
-
import { readFile as
|
|
28797
|
-
import { dirname as
|
|
28796
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
28797
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
28798
|
+
import { dirname as dirname4 } from "path";
|
|
28798
28799
|
|
|
28799
28800
|
// src/integrations/registry.ts
|
|
28800
28801
|
import { randomBytes } from "crypto";
|
|
@@ -28959,6 +28960,60 @@ Installing routstr models in openclaw.json...`);
|
|
|
28959
28960
|
}
|
|
28960
28961
|
}
|
|
28961
28962
|
|
|
28963
|
+
// src/integrations/claudecode.ts
|
|
28964
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
28965
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
28966
|
+
import { dirname as dirname3 } from "path";
|
|
28967
|
+
async function installClaudeCodeIntegration(config, store, integrationConfig) {
|
|
28968
|
+
const { clientId, name, configPath } = integrationConfig;
|
|
28969
|
+
logger.log(`
|
|
28970
|
+
Installing routstr configuration in ${configPath}...`);
|
|
28971
|
+
const port = config.port || 8008;
|
|
28972
|
+
const state = store.getState();
|
|
28973
|
+
const existingClient = (state.clientIds || []).find((c) => c.clientId === clientId);
|
|
28974
|
+
let apiKey;
|
|
28975
|
+
if (existingClient) {
|
|
28976
|
+
apiKey = existingClient.apiKey;
|
|
28977
|
+
logger.log(`Using existing API key for ${name}`);
|
|
28978
|
+
} else {
|
|
28979
|
+
apiKey = generateApiKey();
|
|
28980
|
+
store.getState().setClientIds((prev) => [
|
|
28981
|
+
...prev || [],
|
|
28982
|
+
{
|
|
28983
|
+
clientId,
|
|
28984
|
+
name,
|
|
28985
|
+
apiKey,
|
|
28986
|
+
createdAt: Date.now()
|
|
28987
|
+
}
|
|
28988
|
+
]);
|
|
28989
|
+
logger.log(`Created new API key for ${name}`);
|
|
28990
|
+
}
|
|
28991
|
+
let settings = {};
|
|
28992
|
+
try {
|
|
28993
|
+
if (existsSync6(configPath)) {
|
|
28994
|
+
const content = await readFile3(configPath, "utf-8");
|
|
28995
|
+
settings = JSON.parse(content);
|
|
28996
|
+
}
|
|
28997
|
+
} catch (error) {
|
|
28998
|
+
logger.error(`Error reading ${configPath}, creating new one.`);
|
|
28999
|
+
}
|
|
29000
|
+
if (!settings.env) {
|
|
29001
|
+
settings.env = {};
|
|
29002
|
+
}
|
|
29003
|
+
settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
29004
|
+
settings.env["ANTHROPIC_BASE_URL"] = `http://localhost:${port}`;
|
|
29005
|
+
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = "gpt-5.4";
|
|
29006
|
+
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = "claude-opus-4.7";
|
|
29007
|
+
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "minimax-m2.7";
|
|
29008
|
+
try {
|
|
29009
|
+
mkdirSync3(dirname3(configPath), { recursive: true });
|
|
29010
|
+
await writeFile3(configPath, JSON.stringify(settings, null, 2));
|
|
29011
|
+
logger.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
29012
|
+
} catch (error) {
|
|
29013
|
+
logger.error(`Failed to write to ${configPath}:`, error);
|
|
29014
|
+
}
|
|
29015
|
+
}
|
|
29016
|
+
|
|
28962
29017
|
// src/integrations/registry.ts
|
|
28963
29018
|
function generateApiKey() {
|
|
28964
29019
|
const bytes = randomBytes(24);
|
|
@@ -28979,6 +29034,11 @@ var CLIENT_CONFIGS = {
|
|
|
28979
29034
|
clientId: "openclaw",
|
|
28980
29035
|
name: "OpenClaw",
|
|
28981
29036
|
configPath: join2(process.env.HOME || "", ".openclaw/openclaw.json")
|
|
29037
|
+
},
|
|
29038
|
+
"claude-code": {
|
|
29039
|
+
clientId: "claude-code",
|
|
29040
|
+
name: "Claude Code",
|
|
29041
|
+
configPath: join2(process.env.HOME || "", ".claude/settings.json")
|
|
28982
29042
|
}
|
|
28983
29043
|
};
|
|
28984
29044
|
|
|
@@ -29010,8 +29070,8 @@ Installing routstr models in opencode.json...`);
|
|
|
29010
29070
|
}
|
|
29011
29071
|
let opencodeConfig;
|
|
29012
29072
|
try {
|
|
29013
|
-
if (
|
|
29014
|
-
const content = await
|
|
29073
|
+
if (existsSync7(configPath)) {
|
|
29074
|
+
const content = await readFile4(configPath, "utf-8");
|
|
29015
29075
|
opencodeConfig = JSON.parse(content);
|
|
29016
29076
|
} else {
|
|
29017
29077
|
opencodeConfig = { provider: {} };
|
|
@@ -29023,7 +29083,7 @@ Installing routstr models in opencode.json...`);
|
|
|
29023
29083
|
opencodeConfig.provider = {};
|
|
29024
29084
|
}
|
|
29025
29085
|
try {
|
|
29026
|
-
|
|
29086
|
+
mkdirSync4(dirname4(configPath), { recursive: true });
|
|
29027
29087
|
const response = await fetch(`http://localhost:${port}/models`);
|
|
29028
29088
|
const data = await response.json();
|
|
29029
29089
|
const models = data.output?.models || [];
|
|
@@ -29046,7 +29106,7 @@ Installing routstr models in opencode.json...`);
|
|
|
29046
29106
|
models: modelsObj
|
|
29047
29107
|
};
|
|
29048
29108
|
opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
|
|
29049
|
-
await
|
|
29109
|
+
await writeFile4(configPath, JSON.stringify(opencodeConfig, null, 2));
|
|
29050
29110
|
logger.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
|
|
29051
29111
|
} catch (error) {
|
|
29052
29112
|
logger.error("Failed to install models in opencode.json:", error);
|
|
@@ -29073,7 +29133,7 @@ function parseChoice(input) {
|
|
|
29073
29133
|
return 1;
|
|
29074
29134
|
}
|
|
29075
29135
|
const parsed = Number.parseInt(input, 10);
|
|
29076
|
-
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <=
|
|
29136
|
+
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 5) {
|
|
29077
29137
|
return parsed;
|
|
29078
29138
|
}
|
|
29079
29139
|
return 1;
|
|
@@ -29084,7 +29144,8 @@ Choose an integration to set up:`);
|
|
|
29084
29144
|
logger.log("1. OpenCode (default)");
|
|
29085
29145
|
logger.log("2. OpenClaw");
|
|
29086
29146
|
logger.log("3. Pi");
|
|
29087
|
-
logger.log("4.
|
|
29147
|
+
logger.log("4. Claude Code");
|
|
29148
|
+
logger.log("5. Skip for now");
|
|
29088
29149
|
const answer = await ask("Select integration [1]: ");
|
|
29089
29150
|
const choice = parseChoice(answer);
|
|
29090
29151
|
if (choice === 1) {
|
|
@@ -29099,6 +29160,10 @@ Choose an integration to set up:`);
|
|
|
29099
29160
|
await installPiIntegration(config, store, CLIENT_CONFIGS["pi-agent"]);
|
|
29100
29161
|
return;
|
|
29101
29162
|
}
|
|
29163
|
+
if (choice === 4) {
|
|
29164
|
+
await installClaudeCodeIntegration(config, store, CLIENT_CONFIGS["claude-code"]);
|
|
29165
|
+
return;
|
|
29166
|
+
}
|
|
29102
29167
|
logger.log("Skipping integration setup.");
|
|
29103
29168
|
}
|
|
29104
29169
|
|
|
@@ -36305,8 +36370,8 @@ var AuditLogger = class _AuditLogger {
|
|
|
36305
36370
|
if (typeof window === "undefined") {
|
|
36306
36371
|
try {
|
|
36307
36372
|
const fs2 = await import("fs");
|
|
36308
|
-
const
|
|
36309
|
-
const logPath =
|
|
36373
|
+
const path2 = await import("path");
|
|
36374
|
+
const logPath = path2.join(process.cwd(), "audit.log");
|
|
36310
36375
|
fs2.appendFileSync(logPath, logLine);
|
|
36311
36376
|
} catch (error) {
|
|
36312
36377
|
console.error("[AuditLogger] Failed to write to file:", error);
|
|
@@ -36815,7 +36880,7 @@ var $toString = function toString(text, opts, cb) {
|
|
|
36815
36880
|
};
|
|
36816
36881
|
|
|
36817
36882
|
// src/daemon/wallet/cocod-client.ts
|
|
36818
|
-
import { existsSync as
|
|
36883
|
+
import { existsSync as existsSync8 } from "fs";
|
|
36819
36884
|
var DEFAULT_CONFIG_DIR = `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`;
|
|
36820
36885
|
var DEFAULT_SOCKET_PATH = process.env.COCOD_SOCKET || `${DEFAULT_CONFIG_DIR}/cocod.sock`;
|
|
36821
36886
|
function resolveCocodExecutable(cocodPath) {
|
|
@@ -36825,7 +36890,7 @@ function resolveCocodExecutable(cocodPath) {
|
|
|
36825
36890
|
async function isCocodInstalled(cocodPath) {
|
|
36826
36891
|
const executable = resolveCocodExecutable(cocodPath);
|
|
36827
36892
|
if (executable.includes("/")) {
|
|
36828
|
-
return
|
|
36893
|
+
return existsSync8(executable);
|
|
36829
36894
|
}
|
|
36830
36895
|
try {
|
|
36831
36896
|
const proc = Bun.spawn({
|
|
@@ -36874,11 +36939,11 @@ async function initDaemon() {
|
|
|
36874
36939
|
}
|
|
36875
36940
|
logger.log("cocod installed successfully.");
|
|
36876
36941
|
}
|
|
36877
|
-
if (!
|
|
36878
|
-
|
|
36942
|
+
if (!existsSync9(CONFIG_DIR)) {
|
|
36943
|
+
mkdirSync5(CONFIG_DIR, { recursive: true });
|
|
36879
36944
|
logger.log(`Created config directory: ${CONFIG_DIR}`);
|
|
36880
36945
|
}
|
|
36881
|
-
if (!
|
|
36946
|
+
if (!existsSync9(CONFIG_FILE)) {
|
|
36882
36947
|
const config2 = {
|
|
36883
36948
|
...DEFAULT_CONFIG,
|
|
36884
36949
|
cocodPath: null
|
|
@@ -36953,6 +37018,8 @@ ${initStderr}`.toLowerCase();
|
|
|
36953
37018
|
Initialization complete!`);
|
|
36954
37019
|
logger.log(`
|
|
36955
37020
|
use 'routstrd wallet receive cashu <token>' or 'routstrd wallet receive bolt11 2100' to top up your local wallet!`);
|
|
37021
|
+
logger.log(`
|
|
37022
|
+
To ensure routstrd persists across system restarts, run: 'routstrd service install'`);
|
|
36956
37023
|
}
|
|
36957
37024
|
async function checkCocodInstalled() {
|
|
36958
37025
|
try {
|
|
@@ -37259,6 +37326,10 @@ program.command("monitor").description("Open interactive TUI for usage monitorin
|
|
|
37259
37326
|
const { runUsageTui: runUsageTui2 } = await Promise.resolve().then(() => (init_usage(), exports_usage));
|
|
37260
37327
|
await runUsageTui2();
|
|
37261
37328
|
});
|
|
37329
|
+
program.command("top").description("Open interactive TUI for usage monitoring (alias for monitor)").action(async () => {
|
|
37330
|
+
const { runUsageTui: runUsageTui2 } = await Promise.resolve().then(() => (init_usage(), exports_usage));
|
|
37331
|
+
await runUsageTui2();
|
|
37332
|
+
});
|
|
37262
37333
|
var walletCmd = program.command("wallet").description("Wallet operations");
|
|
37263
37334
|
walletCmd.command("status").description("Check wallet status").action(async () => {
|
|
37264
37335
|
await handleDaemonCommand("/wallet/status");
|
|
@@ -37340,6 +37411,62 @@ walletMintsCmd.command("info <url>").description("Get wallet mint info").action(
|
|
|
37340
37411
|
program.command("stop").description("Stop the background daemon").action(async () => {
|
|
37341
37412
|
await handleDaemonCommand("/stop", { method: "POST" });
|
|
37342
37413
|
});
|
|
37414
|
+
var serviceCmd = program.command("service").description("Manage routstrd as a system service using PM2");
|
|
37415
|
+
serviceCmd.command("install").description("Install and start routstrd using PM2 for persistence").action(async () => {
|
|
37416
|
+
try {
|
|
37417
|
+
execSync("pm2 -v", { stdio: "ignore" });
|
|
37418
|
+
} catch (e) {
|
|
37419
|
+
console.log("PM2 not found. Installing PM2 globally with bun...");
|
|
37420
|
+
try {
|
|
37421
|
+
execSync("bun install -g pm2", { stdio: "inherit" });
|
|
37422
|
+
} catch (err) {
|
|
37423
|
+
console.error("Failed to install PM2. Please install it manually: bun install -g pm2");
|
|
37424
|
+
process.exit(1);
|
|
37425
|
+
}
|
|
37426
|
+
}
|
|
37427
|
+
let daemonPath;
|
|
37428
|
+
try {
|
|
37429
|
+
daemonPath = Bun.resolveSync("./daemon/index.js", import.meta.url);
|
|
37430
|
+
} catch (e) {
|
|
37431
|
+
const path = __require("path");
|
|
37432
|
+
daemonPath = path.join(path.dirname(import.meta.url).replace("file://", ""), "daemon", "index.js");
|
|
37433
|
+
}
|
|
37434
|
+
if (!existsSync9(daemonPath)) {
|
|
37435
|
+
console.error(`Could not find daemon at ${daemonPath}. Did you run 'bun run build'?`);
|
|
37436
|
+
process.exit(1);
|
|
37437
|
+
}
|
|
37438
|
+
console.log("Starting routstrd via PM2...");
|
|
37439
|
+
try {
|
|
37440
|
+
execSync(`pm2 start "${daemonPath}" --name routstrd --interpreter bun`, {
|
|
37441
|
+
stdio: "inherit"
|
|
37442
|
+
});
|
|
37443
|
+
console.log(`
|
|
37444
|
+
\u2705 routstrd is now managed by PM2.`);
|
|
37445
|
+
console.log(`
|
|
37446
|
+
To ensure it starts on system reboot, run:`);
|
|
37447
|
+
console.log(" pm2 startup");
|
|
37448
|
+
console.log(" pm2 save");
|
|
37449
|
+
console.log(`
|
|
37450
|
+
To view logs:`);
|
|
37451
|
+
console.log(" pm2 logs routstrd");
|
|
37452
|
+
} catch (err) {
|
|
37453
|
+
console.error("Failed to start routstrd via PM2.");
|
|
37454
|
+
process.exit(1);
|
|
37455
|
+
}
|
|
37456
|
+
});
|
|
37457
|
+
serviceCmd.command("uninstall").description("Stop and remove routstrd from PM2").action(() => {
|
|
37458
|
+
try {
|
|
37459
|
+
execSync("pm2 delete routstrd", { stdio: "inherit" });
|
|
37460
|
+
console.log("\u2705 routstrd service removed from PM2.");
|
|
37461
|
+
} catch (e) {
|
|
37462
|
+
console.error("Failed to remove service. It might not be running in PM2.");
|
|
37463
|
+
}
|
|
37464
|
+
});
|
|
37465
|
+
serviceCmd.command("logs").description("View PM2 logs for routstrd").action(() => {
|
|
37466
|
+
try {
|
|
37467
|
+
execSync("pm2 logs routstrd", { stdio: "inherit" });
|
|
37468
|
+
} catch (e) {}
|
|
37469
|
+
});
|
|
37343
37470
|
program.command("restart").description("Restart the background daemon").option("--port <port>", "Port to listen on").option("-p, --provider <provider>", "Default provider to use").action(async (options) => {
|
|
37344
37471
|
const config = await loadConfig();
|
|
37345
37472
|
const wasRunning = await isDaemonRunning();
|
|
@@ -37438,69 +37565,31 @@ program.command("logs").description("View daemon logs").option("-f, --follow", "
|
|
|
37438
37565
|
const yesterday = new Date;
|
|
37439
37566
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
37440
37567
|
const yesterdayFile = getLogFileForDate2(yesterday);
|
|
37441
|
-
if (!
|
|
37568
|
+
if (!existsSync9(todayFile) && !existsSync9(yesterdayFile)) {
|
|
37442
37569
|
console.log("No log files found. Daemon may not have started yet.");
|
|
37443
37570
|
console.log(`Logs directory: ${LOGS_DIR2}`);
|
|
37444
37571
|
process.exit(1);
|
|
37445
37572
|
}
|
|
37446
37573
|
const lines = parseInt(options.lines, 10);
|
|
37447
|
-
const
|
|
37448
|
-
|
|
37449
|
-
|
|
37450
|
-
const yesterdayContent = await Bun.file(yesterdayFile).text();
|
|
37451
|
-
allLines = yesterdayContent.split(`
|
|
37452
|
-
`).filter(Boolean);
|
|
37453
|
-
}
|
|
37454
|
-
if (existsSync8(todayFile)) {
|
|
37455
|
-
const todayContent = await Bun.file(todayFile).text();
|
|
37456
|
-
allLines = allLines.concat(todayContent.split(`
|
|
37457
|
-
`).filter(Boolean));
|
|
37458
|
-
}
|
|
37459
|
-
return allLines.slice(-lines);
|
|
37460
|
-
};
|
|
37461
|
-
const printLines = async () => {
|
|
37462
|
-
const lastLines = await readLastLines();
|
|
37463
|
-
for (const line of lastLines) {
|
|
37464
|
-
console.log(line);
|
|
37465
|
-
}
|
|
37466
|
-
};
|
|
37574
|
+
const logFiles = [yesterdayFile, todayFile].filter((file, index, files) => {
|
|
37575
|
+
return existsSync9(file) && files.indexOf(file) === index;
|
|
37576
|
+
});
|
|
37467
37577
|
if (options.follow) {
|
|
37468
|
-
|
|
37469
|
-
|
|
37470
|
-
|
|
37471
|
-
|
|
37472
|
-
}
|
|
37473
|
-
await printLines();
|
|
37474
|
-
const interval = setInterval(async () => {
|
|
37475
|
-
const newLogFile = getLogFileForDate2();
|
|
37476
|
-
if (newLogFile !== currentLogFile) {
|
|
37477
|
-
console.log(`
|
|
37478
|
-
--- Switched to ${newLogFile} ---
|
|
37479
|
-
`);
|
|
37480
|
-
currentLogFile = newLogFile;
|
|
37481
|
-
lastSize = existsSync8(currentLogFile) ? (await Bun.file(currentLogFile).text()).length : 0;
|
|
37482
|
-
}
|
|
37483
|
-
if (existsSync8(currentLogFile)) {
|
|
37484
|
-
const content2 = await Bun.file(currentLogFile).text();
|
|
37485
|
-
const currentSize = content2.length;
|
|
37486
|
-
if (currentSize > lastSize) {
|
|
37487
|
-
const allLines = content2.split(`
|
|
37488
|
-
`).filter(Boolean);
|
|
37489
|
-
const newLines = allLines.slice(Math.floor(lastSize === 0 ? 0 : -1), -1);
|
|
37490
|
-
for (const line of newLines) {
|
|
37491
|
-
console.log(line);
|
|
37492
|
-
}
|
|
37493
|
-
lastSize = currentSize;
|
|
37494
|
-
}
|
|
37495
|
-
}
|
|
37496
|
-
}, 1000);
|
|
37497
|
-
process.on("SIGINT", () => {
|
|
37498
|
-
clearInterval(interval);
|
|
37499
|
-
process.exit(0);
|
|
37578
|
+
const proc2 = Bun.spawn(["tail", "-n", String(lines), "-f", todayFile], {
|
|
37579
|
+
stdout: "inherit",
|
|
37580
|
+
stderr: "inherit",
|
|
37581
|
+
stdin: "inherit"
|
|
37500
37582
|
});
|
|
37501
|
-
|
|
37502
|
-
|
|
37583
|
+
const exitCode2 = await proc2.exited;
|
|
37584
|
+
process.exit(exitCode2);
|
|
37503
37585
|
}
|
|
37586
|
+
const proc = Bun.spawn(["tail", "-n", String(lines), ...logFiles], {
|
|
37587
|
+
stdout: "inherit",
|
|
37588
|
+
stderr: "inherit",
|
|
37589
|
+
stdin: "inherit"
|
|
37590
|
+
});
|
|
37591
|
+
const exitCode = await proc.exited;
|
|
37592
|
+
process.exit(exitCode);
|
|
37504
37593
|
});
|
|
37505
37594
|
function cli(args) {
|
|
37506
37595
|
program.parse(args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routstrd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -13,6 +13,7 @@
|
|
|
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",
|
|
16
17
|
"prepublishOnly": "bun run build"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"@cashu/cashu-ts": "^3.1.1",
|
|
27
|
-
"@routstr/sdk": "^0.2.
|
|
28
|
+
"@routstr/sdk": "^0.2.12",
|
|
28
29
|
"applesauce-core": "^5.1.0",
|
|
29
30
|
"applesauce-relay": "^5.1.0",
|
|
30
31
|
"commander": "^14.0.2",
|
package/src/cli.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
loadConfig,
|
|
9
9
|
} from "./cli-shared";
|
|
10
10
|
import { existsSync, mkdirSync } from "fs";
|
|
11
|
+
import { execSync } from "child_process";
|
|
11
12
|
import {
|
|
12
13
|
CONFIG_DIR,
|
|
13
14
|
DB_PATH,
|
|
@@ -203,6 +204,9 @@ async function initDaemon(): Promise<void> {
|
|
|
203
204
|
logger.log(
|
|
204
205
|
"\n use 'routstrd wallet receive cashu <token>' or 'routstrd wallet receive bolt11 2100' to top up your local wallet!",
|
|
205
206
|
);
|
|
207
|
+
logger.log(
|
|
208
|
+
"\nTo ensure routstrd persists across system restarts, run: 'routstrd service install'",
|
|
209
|
+
);
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
async function checkCocodInstalled(): Promise<boolean> {
|
|
@@ -721,6 +725,14 @@ program
|
|
|
721
725
|
await runUsageTui();
|
|
722
726
|
});
|
|
723
727
|
|
|
728
|
+
program
|
|
729
|
+
.command("top")
|
|
730
|
+
.description("Open interactive TUI for usage monitoring (alias for monitor)")
|
|
731
|
+
.action(async () => {
|
|
732
|
+
const { runUsageTui } = await import("./tui/usage/index.ts");
|
|
733
|
+
await runUsageTui();
|
|
734
|
+
});
|
|
735
|
+
|
|
724
736
|
const walletCmd = program.command("wallet").description("Wallet operations");
|
|
725
737
|
|
|
726
738
|
walletCmd
|
|
@@ -866,6 +878,89 @@ program
|
|
|
866
878
|
await handleDaemonCommand("/stop", { method: "POST" });
|
|
867
879
|
});
|
|
868
880
|
|
|
881
|
+
// Service - PM2 management
|
|
882
|
+
const serviceCmd = program
|
|
883
|
+
.command("service")
|
|
884
|
+
.description("Manage routstrd as a system service using PM2");
|
|
885
|
+
|
|
886
|
+
serviceCmd
|
|
887
|
+
.command("install")
|
|
888
|
+
.description("Install and start routstrd using PM2 for persistence")
|
|
889
|
+
.action(async () => {
|
|
890
|
+
// 1. Check if PM2 is installed
|
|
891
|
+
try {
|
|
892
|
+
execSync("pm2 -v", { stdio: "ignore" });
|
|
893
|
+
} catch (e) {
|
|
894
|
+
console.log("PM2 not found. Installing PM2 globally with bun...");
|
|
895
|
+
try {
|
|
896
|
+
execSync("bun install -g pm2", { stdio: "inherit" });
|
|
897
|
+
} catch (err) {
|
|
898
|
+
console.error("Failed to install PM2. Please install it manually: bun install -g pm2");
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// 2. Resolve the path to the daemon
|
|
904
|
+
// In a global install, we want the bundled daemon in dist/daemon/index.js
|
|
905
|
+
let daemonPath: string;
|
|
906
|
+
try {
|
|
907
|
+
// Try to resolve relative to this file first (works in dev and global)
|
|
908
|
+
daemonPath = Bun.resolveSync("./daemon/index.js", import.meta.url);
|
|
909
|
+
} catch (e) {
|
|
910
|
+
// Fallback for some bundling scenarios
|
|
911
|
+
const path = require("path");
|
|
912
|
+
daemonPath = path.join(path.dirname(import.meta.url).replace("file://", ""), "daemon", "index.js");
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (!existsSync(daemonPath)) {
|
|
916
|
+
console.error(`Could not find daemon at ${daemonPath}. Did you run 'bun run build'?`);
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
console.log("Starting routstrd via PM2...");
|
|
921
|
+
try {
|
|
922
|
+
// Use --interpreter bun to ensure it runs with bun
|
|
923
|
+
execSync(`pm2 start "${daemonPath}" --name routstrd --interpreter bun`, {
|
|
924
|
+
stdio: "inherit",
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
console.log("\n✅ routstrd is now managed by PM2.");
|
|
928
|
+
console.log("\nTo ensure it starts on system reboot, run:");
|
|
929
|
+
console.log(" pm2 startup");
|
|
930
|
+
console.log(" pm2 save");
|
|
931
|
+
console.log("\nTo view logs:");
|
|
932
|
+
console.log(" pm2 logs routstrd");
|
|
933
|
+
} catch (err) {
|
|
934
|
+
console.error("Failed to start routstrd via PM2.");
|
|
935
|
+
process.exit(1);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
serviceCmd
|
|
940
|
+
.command("uninstall")
|
|
941
|
+
.description("Stop and remove routstrd from PM2")
|
|
942
|
+
.action(() => {
|
|
943
|
+
try {
|
|
944
|
+
execSync("pm2 delete routstrd", { stdio: "inherit" });
|
|
945
|
+
console.log("✅ routstrd service removed from PM2.");
|
|
946
|
+
} catch (e) {
|
|
947
|
+
console.error(
|
|
948
|
+
"Failed to remove service. It might not be running in PM2.",
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
serviceCmd
|
|
954
|
+
.command("logs")
|
|
955
|
+
.description("View PM2 logs for routstrd")
|
|
956
|
+
.action(() => {
|
|
957
|
+
try {
|
|
958
|
+
execSync("pm2 logs routstrd", { stdio: "inherit" });
|
|
959
|
+
} catch (e) {
|
|
960
|
+
// Ignored
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
|
|
869
964
|
// Restart
|
|
870
965
|
program
|
|
871
966
|
.command("restart")
|
|
@@ -1013,76 +1108,29 @@ program
|
|
|
1013
1108
|
|
|
1014
1109
|
const lines = parseInt(options.lines, 10);
|
|
1015
1110
|
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
// Read yesterday's log first if it exists
|
|
1020
|
-
if (existsSync(yesterdayFile)) {
|
|
1021
|
-
const yesterdayContent = await Bun.file(yesterdayFile).text();
|
|
1022
|
-
allLines = yesterdayContent.split("\n").filter(Boolean);
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
// Then read today's log
|
|
1026
|
-
if (existsSync(todayFile)) {
|
|
1027
|
-
const todayContent = await Bun.file(todayFile).text();
|
|
1028
|
-
allLines = allLines.concat(todayContent.split("\n").filter(Boolean));
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
return allLines.slice(-lines);
|
|
1032
|
-
};
|
|
1033
|
-
|
|
1034
|
-
const printLines = async (): Promise<void> => {
|
|
1035
|
-
const lastLines = await readLastLines();
|
|
1036
|
-
for (const line of lastLines) {
|
|
1037
|
-
console.log(line);
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1111
|
+
const logFiles = [yesterdayFile, todayFile].filter((file, index, files) => {
|
|
1112
|
+
return existsSync(file) && files.indexOf(file) === index;
|
|
1113
|
+
});
|
|
1040
1114
|
|
|
1041
1115
|
if (options.follow) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1116
|
+
const proc = Bun.spawn(["tail", "-n", String(lines), "-f", todayFile], {
|
|
1117
|
+
stdout: "inherit",
|
|
1118
|
+
stderr: "inherit",
|
|
1119
|
+
stdin: "inherit",
|
|
1120
|
+
});
|
|
1048
1121
|
|
|
1049
|
-
await
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
// Check if we need to switch to a new date file
|
|
1053
|
-
const newLogFile = getLogFileForDate();
|
|
1054
|
-
if (newLogFile !== currentLogFile) {
|
|
1055
|
-
console.log(`\n--- Switched to ${newLogFile} ---\n`);
|
|
1056
|
-
currentLogFile = newLogFile;
|
|
1057
|
-
lastSize = existsSync(currentLogFile)
|
|
1058
|
-
? (await Bun.file(currentLogFile).text()).length
|
|
1059
|
-
: 0;
|
|
1060
|
-
}
|
|
1122
|
+
const exitCode = await proc.exited;
|
|
1123
|
+
process.exit(exitCode);
|
|
1124
|
+
}
|
|
1061
1125
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const newLines = allLines.slice(
|
|
1068
|
-
Math.floor(lastSize === 0 ? 0 : -1),
|
|
1069
|
-
-1,
|
|
1070
|
-
);
|
|
1071
|
-
for (const line of newLines) {
|
|
1072
|
-
console.log(line);
|
|
1073
|
-
}
|
|
1074
|
-
lastSize = currentSize;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}, 1000);
|
|
1126
|
+
const proc = Bun.spawn(["tail", "-n", String(lines), ...logFiles], {
|
|
1127
|
+
stdout: "inherit",
|
|
1128
|
+
stderr: "inherit",
|
|
1129
|
+
stdin: "inherit",
|
|
1130
|
+
});
|
|
1078
1131
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
process.exit(0);
|
|
1082
|
-
});
|
|
1083
|
-
} else {
|
|
1084
|
-
await printLines();
|
|
1085
|
-
}
|
|
1132
|
+
const exitCode = await proc.exited;
|
|
1133
|
+
process.exit(exitCode);
|
|
1086
1134
|
});
|
|
1087
1135
|
|
|
1088
1136
|
export function cli(args: string[]) {
|