routstrd 0.4.1 → 0.4.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/bun.lock +2 -2
- package/dist/daemon/index.js +69770 -54594
- package/dist/index.js +197 -85
- package/package.json +2 -2
- package/src/cli.ts +98 -12
- package/src/daemon/config-store.ts +96 -8
- package/src/daemon/models.test.ts +81 -0
- package/src/daemon/models.ts +18 -2
- package/src/integrations/pi.ts +35 -1
- package/src/integrations/registry.ts +9 -0
- package/src/tui/usage/data.ts +13 -2
- package/src/tui/usage/render.ts +8 -0
- package/src/utils/config.ts +3 -1
- package/tests/daemon/config-store.perms.test.ts +55 -0
- package/tests/daemon/config-store.scenario.ts +113 -0
package/dist/index.js
CHANGED
|
@@ -21234,7 +21234,10 @@ var init_constants = __esm(() => {
|
|
|
21234
21234
|
// src/tui/usage/data.ts
|
|
21235
21235
|
async function fetchStatus() {
|
|
21236
21236
|
try {
|
|
21237
|
-
const result = await
|
|
21237
|
+
const [result, config] = await Promise.all([
|
|
21238
|
+
callDaemon("/status"),
|
|
21239
|
+
loadConfig()
|
|
21240
|
+
]);
|
|
21238
21241
|
if (result.error)
|
|
21239
21242
|
return null;
|
|
21240
21243
|
const output = result.output;
|
|
@@ -21242,6 +21245,7 @@ async function fetchStatus() {
|
|
|
21242
21245
|
daemon: output?.daemon || "unknown",
|
|
21243
21246
|
wallet: output?.wallet || "unknown",
|
|
21244
21247
|
mode: output?.mode || "apikeys",
|
|
21248
|
+
url: getDaemonBaseUrl(config),
|
|
21245
21249
|
error: output?.error
|
|
21246
21250
|
};
|
|
21247
21251
|
} catch {
|
|
@@ -21559,6 +21563,12 @@ function renderOverview(stats, balance, status, width) {
|
|
|
21559
21563
|
const walletColor = status.wallet === "connected" ? COLORS.green : COLORS.red;
|
|
21560
21564
|
const modeColor = COLORS.cyan;
|
|
21561
21565
|
statusLines.push(`${COLORS.bold}Daemon:${COLORS.reset} ${daemonColor}${status.daemon}${COLORS.reset}`);
|
|
21566
|
+
if (status.url) {
|
|
21567
|
+
const label = "Running on: ";
|
|
21568
|
+
const maxUrlLen = Math.max(8, halfWidth2 - 4 - label.length);
|
|
21569
|
+
const url = status.url.length > maxUrlLen ? `${status.url.slice(0, Math.max(0, maxUrlLen - 1))}\u2026` : status.url;
|
|
21570
|
+
statusLines.push(`${COLORS.bold}${label}${COLORS.reset} ${COLORS.cyan}${url}${COLORS.reset}`);
|
|
21571
|
+
}
|
|
21562
21572
|
statusLines.push(`${COLORS.bold}Wallet:${COLORS.reset} ${walletColor}${status.wallet}${COLORS.reset}`);
|
|
21563
21573
|
statusLines.push(`${COLORS.bold}Mode:${COLORS.reset} ${modeColor}${status.mode}${COLORS.reset}`);
|
|
21564
21574
|
if (status.error) {
|
|
@@ -22276,6 +22286,53 @@ var {
|
|
|
22276
22286
|
|
|
22277
22287
|
// src/cli.ts
|
|
22278
22288
|
init_start_daemon();
|
|
22289
|
+
|
|
22290
|
+
// src/daemon/config-store.ts
|
|
22291
|
+
init_config();
|
|
22292
|
+
init_logger();
|
|
22293
|
+
import {
|
|
22294
|
+
chmodSync,
|
|
22295
|
+
existsSync as existsSync4,
|
|
22296
|
+
mkdirSync as mkdirSync2,
|
|
22297
|
+
readFileSync,
|
|
22298
|
+
renameSync,
|
|
22299
|
+
unlinkSync,
|
|
22300
|
+
writeFileSync
|
|
22301
|
+
} from "fs";
|
|
22302
|
+
var REQUESTS_DIR = `${CONFIG_DIR}/requests`;
|
|
22303
|
+
function chmodIgnoreErrors(path, mode) {
|
|
22304
|
+
try {
|
|
22305
|
+
chmodSync(path, mode);
|
|
22306
|
+
} catch {}
|
|
22307
|
+
}
|
|
22308
|
+
function ensureDirsSync() {
|
|
22309
|
+
try {
|
|
22310
|
+
mkdirSync2(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
22311
|
+
} catch {}
|
|
22312
|
+
chmodSync(CONFIG_DIR, 448);
|
|
22313
|
+
try {
|
|
22314
|
+
mkdirSync2(REQUESTS_DIR, { recursive: true, mode: 448 });
|
|
22315
|
+
} catch {}
|
|
22316
|
+
chmodSync(REQUESTS_DIR, 448);
|
|
22317
|
+
}
|
|
22318
|
+
function saveDaemonConfig(config) {
|
|
22319
|
+
ensureDirsSync();
|
|
22320
|
+
const temporaryFile = `${CONFIG_FILE}.${process.pid}.tmp`;
|
|
22321
|
+
try {
|
|
22322
|
+
writeFileSync(temporaryFile, JSON.stringify(config, null, 2), {
|
|
22323
|
+
mode: 384
|
|
22324
|
+
});
|
|
22325
|
+
renameSync(temporaryFile, CONFIG_FILE);
|
|
22326
|
+
} catch (error) {
|
|
22327
|
+
try {
|
|
22328
|
+
unlinkSync(temporaryFile);
|
|
22329
|
+
} catch {}
|
|
22330
|
+
throw error;
|
|
22331
|
+
}
|
|
22332
|
+
chmodIgnoreErrors(CONFIG_FILE, 384);
|
|
22333
|
+
}
|
|
22334
|
+
|
|
22335
|
+
// src/cli.ts
|
|
22279
22336
|
init_daemon_client();
|
|
22280
22337
|
|
|
22281
22338
|
// src/utils/clients.ts
|
|
@@ -22287,7 +22344,7 @@ import { join as join3 } from "path";
|
|
|
22287
22344
|
|
|
22288
22345
|
// src/integrations/opencode.ts
|
|
22289
22346
|
init_daemon_client();
|
|
22290
|
-
import { existsSync as
|
|
22347
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
22291
22348
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
22292
22349
|
import { dirname as dirname2 } from "path";
|
|
22293
22350
|
var OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
|
|
@@ -22299,7 +22356,7 @@ Installing routstr models in opencode.json...`);
|
|
|
22299
22356
|
const baseUrl = getDaemonBaseUrl(config);
|
|
22300
22357
|
let opencodeConfig;
|
|
22301
22358
|
try {
|
|
22302
|
-
if (
|
|
22359
|
+
if (existsSync5(configPath)) {
|
|
22303
22360
|
const content = await readFile2(configPath, "utf-8");
|
|
22304
22361
|
opencodeConfig = JSON.parse(content);
|
|
22305
22362
|
} else {
|
|
@@ -22313,7 +22370,7 @@ Installing routstr models in opencode.json...`);
|
|
|
22313
22370
|
opencodeConfig.provider = {};
|
|
22314
22371
|
}
|
|
22315
22372
|
try {
|
|
22316
|
-
|
|
22373
|
+
mkdirSync3(dirname2(configPath), { recursive: true });
|
|
22317
22374
|
const data = await callDaemon("/models");
|
|
22318
22375
|
const models = data.output?.models || [];
|
|
22319
22376
|
if (models.length === 0) {
|
|
@@ -22344,7 +22401,7 @@ Installing routstr models in opencode.json...`);
|
|
|
22344
22401
|
|
|
22345
22402
|
// src/integrations/pi.ts
|
|
22346
22403
|
init_daemon_client();
|
|
22347
|
-
import { existsSync as
|
|
22404
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
22348
22405
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
22349
22406
|
import { dirname as dirname3 } from "path";
|
|
22350
22407
|
async function installPiIntegration(config, apiKey, integrationConfig) {
|
|
@@ -22355,7 +22412,7 @@ Installing routstr models in pi models.json...`);
|
|
|
22355
22412
|
const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
|
|
22356
22413
|
let piConfig = {};
|
|
22357
22414
|
try {
|
|
22358
|
-
if (
|
|
22415
|
+
if (existsSync6(configPath)) {
|
|
22359
22416
|
const content = await readFile3(configPath, "utf-8");
|
|
22360
22417
|
piConfig = JSON.parse(content);
|
|
22361
22418
|
}
|
|
@@ -22367,21 +22424,39 @@ Installing routstr models in pi models.json...`);
|
|
|
22367
22424
|
piConfig.providers = {};
|
|
22368
22425
|
}
|
|
22369
22426
|
try {
|
|
22370
|
-
|
|
22427
|
+
mkdirSync4(dirname3(configPath), { recursive: true });
|
|
22371
22428
|
const data = await callDaemon("/models");
|
|
22372
22429
|
const models = data.output?.models || [];
|
|
22373
22430
|
if (models.length === 0) {
|
|
22374
22431
|
console.log("No models found from routstr daemon.");
|
|
22375
22432
|
return;
|
|
22376
22433
|
}
|
|
22434
|
+
const existingModels = new Map((piConfig.providers["routstr"]?.models ?? []).map((m) => [m.id, m]));
|
|
22377
22435
|
const providerModels = models.map((model) => {
|
|
22378
|
-
const
|
|
22436
|
+
const previous = existingModels.get(model.id);
|
|
22437
|
+
const entry = previous ? { ...previous } : { id: model.id };
|
|
22438
|
+
entry.id = model.id;
|
|
22379
22439
|
if (model.context_length !== undefined && model.context_length > 0) {
|
|
22380
22440
|
entry.contextWindow = model.context_length;
|
|
22441
|
+
} else {
|
|
22442
|
+
delete entry.contextWindow;
|
|
22381
22443
|
}
|
|
22444
|
+
if (model.name) {
|
|
22445
|
+
entry.name = model.name;
|
|
22446
|
+
} else {
|
|
22447
|
+
delete entry.name;
|
|
22448
|
+
}
|
|
22449
|
+
const mods = model.architecture?.input_modalities ?? [];
|
|
22450
|
+
const input = [];
|
|
22451
|
+
if (mods.includes("text"))
|
|
22452
|
+
input.push("text");
|
|
22453
|
+
if (mods.includes("image"))
|
|
22454
|
+
input.push("image");
|
|
22455
|
+
entry.input = input;
|
|
22382
22456
|
return entry;
|
|
22383
22457
|
});
|
|
22384
22458
|
piConfig.providers["routstr"] = {
|
|
22459
|
+
...piConfig.providers["routstr"] ?? {},
|
|
22385
22460
|
baseUrl,
|
|
22386
22461
|
api: "openai-completions",
|
|
22387
22462
|
apiKey,
|
|
@@ -22396,7 +22471,7 @@ Installing routstr models in pi models.json...`);
|
|
|
22396
22471
|
|
|
22397
22472
|
// src/integrations/openclaw.ts
|
|
22398
22473
|
init_daemon_client();
|
|
22399
|
-
import { existsSync as
|
|
22474
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
22400
22475
|
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
22401
22476
|
import { dirname as dirname4 } from "path";
|
|
22402
22477
|
var OPENCLAW_PROVIDER_ID = "routstr";
|
|
@@ -22410,7 +22485,7 @@ Installing routstr models in openclaw.json...`);
|
|
|
22410
22485
|
const baseUrl = getDaemonBaseUrl(config);
|
|
22411
22486
|
let openclawConfig = {};
|
|
22412
22487
|
try {
|
|
22413
|
-
if (
|
|
22488
|
+
if (existsSync7(configPath)) {
|
|
22414
22489
|
const content = await readFile4(configPath, "utf-8");
|
|
22415
22490
|
openclawConfig = JSON.parse(content);
|
|
22416
22491
|
}
|
|
@@ -22431,7 +22506,7 @@ Installing routstr models in openclaw.json...`);
|
|
|
22431
22506
|
openclawConfig.agents.defaults = {};
|
|
22432
22507
|
}
|
|
22433
22508
|
try {
|
|
22434
|
-
|
|
22509
|
+
mkdirSync5(dirname4(configPath), { recursive: true });
|
|
22435
22510
|
const data = await callDaemon("/models");
|
|
22436
22511
|
const models = data.output?.models || [];
|
|
22437
22512
|
if (models.length === 0) {
|
|
@@ -22472,7 +22547,7 @@ Installing routstr models in openclaw.json...`);
|
|
|
22472
22547
|
|
|
22473
22548
|
// src/integrations/claudecode.ts
|
|
22474
22549
|
init_daemon_client();
|
|
22475
|
-
import { existsSync as
|
|
22550
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
22476
22551
|
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
22477
22552
|
import { dirname as dirname5 } from "path";
|
|
22478
22553
|
async function installClaudeCodeIntegration(config, apiKey, integrationConfig) {
|
|
@@ -22483,7 +22558,7 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
22483
22558
|
const baseUrl = getDaemonBaseUrl(config);
|
|
22484
22559
|
let settings = {};
|
|
22485
22560
|
try {
|
|
22486
|
-
if (
|
|
22561
|
+
if (existsSync8(configPath)) {
|
|
22487
22562
|
const content = await readFile5(configPath, "utf-8");
|
|
22488
22563
|
settings = JSON.parse(content);
|
|
22489
22564
|
}
|
|
@@ -22520,7 +22595,7 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
22520
22595
|
console.error("Failed to fetch models for Claude Code integration:", error);
|
|
22521
22596
|
}
|
|
22522
22597
|
try {
|
|
22523
|
-
|
|
22598
|
+
mkdirSync6(dirname5(configPath), { recursive: true });
|
|
22524
22599
|
await writeFile5(configPath, JSON.stringify(settings, null, 2));
|
|
22525
22600
|
console.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
22526
22601
|
} catch (error) {
|
|
@@ -22529,7 +22604,7 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
22529
22604
|
}
|
|
22530
22605
|
|
|
22531
22606
|
// src/integrations/hermes.ts
|
|
22532
|
-
import { existsSync as
|
|
22607
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7 } from "fs";
|
|
22533
22608
|
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
22534
22609
|
import { dirname as dirname6 } from "path";
|
|
22535
22610
|
|
|
@@ -22652,7 +22727,7 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
22652
22727
|
}
|
|
22653
22728
|
let content = "";
|
|
22654
22729
|
try {
|
|
22655
|
-
if (
|
|
22730
|
+
if (existsSync9(configPath)) {
|
|
22656
22731
|
content = await readFile6(configPath, "utf-8");
|
|
22657
22732
|
}
|
|
22658
22733
|
} catch (error) {
|
|
@@ -22675,7 +22750,7 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
22675
22750
|
console.log(`${configPath} already contains current routstr settings.`);
|
|
22676
22751
|
return;
|
|
22677
22752
|
}
|
|
22678
|
-
|
|
22753
|
+
mkdirSync7(dirname6(configPath), { recursive: true });
|
|
22679
22754
|
await writeFile6(configPath, newContent);
|
|
22680
22755
|
console.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
22681
22756
|
} catch (error) {
|
|
@@ -22912,7 +22987,7 @@ async function addClientAction(options) {
|
|
|
22912
22987
|
// src/cli.ts
|
|
22913
22988
|
init_config();
|
|
22914
22989
|
init_logger();
|
|
22915
|
-
import { chmodSync as
|
|
22990
|
+
import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync5, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
22916
22991
|
import { execSync } from "child_process";
|
|
22917
22992
|
import { dirname as dirname8, join as join9 } from "path";
|
|
22918
22993
|
|
|
@@ -24194,13 +24269,13 @@ k("Cashu_P2BK_v1");
|
|
|
24194
24269
|
// src/daemon/wallet/coco-client.ts
|
|
24195
24270
|
import {
|
|
24196
24271
|
closeSync as closeSync2,
|
|
24197
|
-
existsSync as
|
|
24198
|
-
mkdirSync as
|
|
24272
|
+
existsSync as existsSync10,
|
|
24273
|
+
mkdirSync as mkdirSync8,
|
|
24199
24274
|
openSync as openSync2,
|
|
24200
|
-
readFileSync,
|
|
24201
|
-
renameSync,
|
|
24202
|
-
unlinkSync,
|
|
24203
|
-
writeFileSync
|
|
24275
|
+
readFileSync as readFileSync2,
|
|
24276
|
+
renameSync as renameSync2,
|
|
24277
|
+
unlinkSync as unlinkSync2,
|
|
24278
|
+
writeFileSync as writeFileSync2
|
|
24204
24279
|
} from "fs";
|
|
24205
24280
|
|
|
24206
24281
|
// node_modules/@scure/bip39/index.js
|
|
@@ -24290,7 +24365,7 @@ var SAFE_COCO_LOG_FIELDS = new Set([
|
|
|
24290
24365
|
"rollingBackOperations",
|
|
24291
24366
|
"orphanedReservations"
|
|
24292
24367
|
]);
|
|
24293
|
-
function isZombieProcess(pid, readFile7 = (path) =>
|
|
24368
|
+
function isZombieProcess(pid, readFile7 = (path) => readFileSync2(path, "utf-8")) {
|
|
24294
24369
|
try {
|
|
24295
24370
|
const stat2 = readFile7(`/proc/${pid}/stat`);
|
|
24296
24371
|
const commandEnd = stat2.lastIndexOf(")");
|
|
@@ -24322,7 +24397,7 @@ function hasErrorCode(error, codes) {
|
|
|
24322
24397
|
}
|
|
24323
24398
|
async function assertLegacyCocodNotRunning(options = {}) {
|
|
24324
24399
|
const socketPath = options.socketPath || legacyCocodSocketPath();
|
|
24325
|
-
const pathExists = options.pathExists ||
|
|
24400
|
+
const pathExists = options.pathExists || existsSync10;
|
|
24326
24401
|
if (!pathExists(socketPath))
|
|
24327
24402
|
return;
|
|
24328
24403
|
const fetchImpl = options.fetchImpl || fetch;
|
|
@@ -24345,8 +24420,8 @@ async function assertLegacyCocodNotRunning(options = {}) {
|
|
|
24345
24420
|
async function stopLegacyCocod(options = {}) {
|
|
24346
24421
|
const socketPath = options.socketPath || legacyCocodSocketPath();
|
|
24347
24422
|
const pidFilePath = options.pidFilePath || legacyCocodPidPath();
|
|
24348
|
-
const pathExists = options.pathExists ||
|
|
24349
|
-
const readFile7 = options.readFile || ((path) =>
|
|
24423
|
+
const pathExists = options.pathExists || existsSync10;
|
|
24424
|
+
const readFile7 = options.readFile || ((path) => readFileSync2(path, "utf-8"));
|
|
24350
24425
|
const isProcessRunning2 = options.isProcessRunning || defaultIsProcessRunning;
|
|
24351
24426
|
const fetchImpl = options.fetchImpl || fetch;
|
|
24352
24427
|
const killProcess = options.killProcess || ((pid2, signal) => process.kill(pid2, signal));
|
|
@@ -24409,10 +24484,10 @@ function claimPidFile(options) {
|
|
|
24409
24484
|
const pid = options.pid ?? process.pid;
|
|
24410
24485
|
const label = options.label || "wallet process lock";
|
|
24411
24486
|
const openExclusive = options.openExclusive || ((path) => openSync2(path, "wx", 384));
|
|
24412
|
-
const writePid = options.writePid || ((fd2, ownerPid) =>
|
|
24487
|
+
const writePid = options.writePid || ((fd2, ownerPid) => writeFileSync2(fd2, String(ownerPid)));
|
|
24413
24488
|
const closeFile = options.closeFile || closeSync2;
|
|
24414
|
-
const readFile7 = options.readFile || ((path) =>
|
|
24415
|
-
const removeFile = options.removeFile ||
|
|
24489
|
+
const readFile7 = options.readFile || ((path) => readFileSync2(path, "utf-8"));
|
|
24490
|
+
const removeFile = options.removeFile || unlinkSync2;
|
|
24416
24491
|
const isProcessRunning2 = options.isProcessRunning || defaultIsProcessRunning;
|
|
24417
24492
|
let fd;
|
|
24418
24493
|
try {
|
|
@@ -24469,11 +24544,11 @@ function claimPidFile(options) {
|
|
|
24469
24544
|
|
|
24470
24545
|
// src/daemon/wallet/migration.ts
|
|
24471
24546
|
import {
|
|
24472
|
-
chmodSync,
|
|
24547
|
+
chmodSync as chmodSync2,
|
|
24473
24548
|
copyFileSync,
|
|
24474
|
-
existsSync as
|
|
24475
|
-
mkdirSync as
|
|
24476
|
-
renameSync as
|
|
24549
|
+
existsSync as existsSync13,
|
|
24550
|
+
mkdirSync as mkdirSync9,
|
|
24551
|
+
renameSync as renameSync3,
|
|
24477
24552
|
rmSync,
|
|
24478
24553
|
statSync as statSync3
|
|
24479
24554
|
} from "fs";
|
|
@@ -24482,16 +24557,16 @@ import { basename, dirname as dirname7, join as join8 } from "path";
|
|
|
24482
24557
|
|
|
24483
24558
|
// src/daemon/wallet/diagnostics.ts
|
|
24484
24559
|
import { createHash } from "crypto";
|
|
24485
|
-
import { existsSync as
|
|
24560
|
+
import { existsSync as existsSync12, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
24486
24561
|
import { Database } from "bun:sqlite";
|
|
24487
24562
|
import { join as join7 } from "path";
|
|
24488
24563
|
|
|
24489
24564
|
// src/daemon/wallet/wallet-state.ts
|
|
24490
|
-
import { existsSync as
|
|
24565
|
+
import { existsSync as existsSync11, readFileSync as readFileSync3, statSync } from "fs";
|
|
24491
24566
|
import { join as join6 } from "path";
|
|
24492
24567
|
function walletState(configPath, dbPath) {
|
|
24493
|
-
const hasConfig =
|
|
24494
|
-
const hasDb =
|
|
24568
|
+
const hasConfig = existsSync11(configPath);
|
|
24569
|
+
const hasDb = existsSync11(dbPath);
|
|
24495
24570
|
if (hasConfig)
|
|
24496
24571
|
return "initialized";
|
|
24497
24572
|
if (hasDb)
|
|
@@ -24499,11 +24574,11 @@ function walletState(configPath, dbPath) {
|
|
|
24499
24574
|
return "absent";
|
|
24500
24575
|
}
|
|
24501
24576
|
function filesEqual(left, right) {
|
|
24502
|
-
if (!
|
|
24577
|
+
if (!existsSync11(left) || !existsSync11(right))
|
|
24503
24578
|
return false;
|
|
24504
24579
|
if (statSync(left).size !== statSync(right).size)
|
|
24505
24580
|
return false;
|
|
24506
|
-
return
|
|
24581
|
+
return readFileSync3(left).equals(readFileSync3(right));
|
|
24507
24582
|
}
|
|
24508
24583
|
function classifyWalletMigration(targetDir, sourceDir) {
|
|
24509
24584
|
const targetConfig = join6(targetDir, "config.json");
|
|
@@ -24516,14 +24591,14 @@ function classifyWalletMigration(targetDir, sourceDir) {
|
|
|
24516
24591
|
const sourceState = walletState(sourceConfig, sourceDb);
|
|
24517
24592
|
if (targetState === "initialized" && sourceState === "initialized") {
|
|
24518
24593
|
const configsMatch = filesEqual(targetConfig, sourceConfig);
|
|
24519
|
-
const databasesMatch = !
|
|
24594
|
+
const databasesMatch = !existsSync11(targetDb) && !existsSync11(sourceDb) ? true : filesEqual(targetDb, sourceDb);
|
|
24520
24595
|
if (configsMatch && databasesMatch)
|
|
24521
24596
|
return { kind: "already-current" };
|
|
24522
24597
|
return { kind: "conflict" };
|
|
24523
24598
|
}
|
|
24524
24599
|
if (targetState === "initialized")
|
|
24525
24600
|
return { kind: "already-current" };
|
|
24526
|
-
if (!
|
|
24601
|
+
if (!existsSync11(sourceDb) && (existsSync11(sourceWal) || existsSync11(sourceShm))) {
|
|
24527
24602
|
return { kind: "orphaned-sidecars" };
|
|
24528
24603
|
}
|
|
24529
24604
|
if (targetState === "database-only" || sourceState === "database-only") {
|
|
@@ -24598,11 +24673,11 @@ function summarizeDbReadonly(dbPath) {
|
|
|
24598
24673
|
function summarizeConfig(configPath) {
|
|
24599
24674
|
const result = { exists: false, path: configPath };
|
|
24600
24675
|
try {
|
|
24601
|
-
if (!
|
|
24676
|
+
if (!existsSync12(configPath))
|
|
24602
24677
|
return result;
|
|
24603
24678
|
result.exists = true;
|
|
24604
24679
|
result.mtimeMs = statSync2(configPath).mtimeMs;
|
|
24605
|
-
const parsed = JSON.parse(
|
|
24680
|
+
const parsed = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
24606
24681
|
result.encrypted = parsed.encrypted === true;
|
|
24607
24682
|
if (typeof parsed.createdAt === "string")
|
|
24608
24683
|
result.createdAt = parsed.createdAt;
|
|
@@ -24624,7 +24699,7 @@ function summarizeConfig(configPath) {
|
|
|
24624
24699
|
function summarizeDb(dbPath) {
|
|
24625
24700
|
const result = { exists: false, path: dbPath };
|
|
24626
24701
|
try {
|
|
24627
|
-
if (!
|
|
24702
|
+
if (!existsSync12(dbPath))
|
|
24628
24703
|
return result;
|
|
24629
24704
|
result.exists = true;
|
|
24630
24705
|
const stats = statSync2(dbPath);
|
|
@@ -24933,21 +25008,21 @@ async function migrateLegacyWallet(options = {}) {
|
|
|
24933
25008
|
const stagingDir = join8(dirname7(targetDir), `.${targetDir.split(/[\\/]/).at(-1) || "wallet"}.staging-${process.pid}-${Date.now()}`);
|
|
24934
25009
|
try {
|
|
24935
25010
|
await options.assertLegacyStopped?.();
|
|
24936
|
-
|
|
24937
|
-
|
|
25011
|
+
mkdirSync9(dirname7(targetDir), { recursive: true, mode: 448 });
|
|
25012
|
+
mkdirSync9(stagingDir, { mode: 448 });
|
|
24938
25013
|
const stagedConfig = join8(stagingDir, "config.json");
|
|
24939
25014
|
const stagedDb = join8(stagingDir, "coco.db");
|
|
24940
25015
|
copyFileSync(sourceConfig, stagedConfig);
|
|
24941
|
-
|
|
24942
|
-
const hasSourceDb =
|
|
25016
|
+
chmodSync2(stagedConfig, 384);
|
|
25017
|
+
const hasSourceDb = existsSync13(sourceDb);
|
|
24943
25018
|
if (hasSourceDb) {
|
|
24944
25019
|
snapshotDatabase(sourceDb, stagedDb);
|
|
24945
|
-
|
|
25020
|
+
chmodSync2(stagedDb, 384);
|
|
24946
25021
|
}
|
|
24947
25022
|
if (statSync3(stagedConfig).size !== statSync3(sourceConfig).size) {
|
|
24948
25023
|
throw new Error("Cannot migrate wallet: staged config failed size validation.");
|
|
24949
25024
|
}
|
|
24950
|
-
|
|
25025
|
+
renameSync3(stagingDir, targetDir);
|
|
24951
25026
|
} catch (error) {
|
|
24952
25027
|
rmSync(stagingDir, { recursive: true, force: true });
|
|
24953
25028
|
throw error;
|
|
@@ -24955,13 +25030,13 @@ async function migrateLegacyWallet(options = {}) {
|
|
|
24955
25030
|
releaseLegacyLock?.();
|
|
24956
25031
|
}
|
|
24957
25032
|
const cleanupWarnings = [];
|
|
24958
|
-
const sourceFiles = [sourceConfig, sourceDb, sourceWal, sourceShm].filter(
|
|
25033
|
+
const sourceFiles = [sourceConfig, sourceDb, sourceWal, sourceShm].filter(existsSync13);
|
|
24959
25034
|
if (sourceFiles.length > 0) {
|
|
24960
25035
|
const archiveDir = join8(sourceDir, `wallet-migrated-${Date.now()}`);
|
|
24961
25036
|
try {
|
|
24962
|
-
|
|
25037
|
+
mkdirSync9(archiveDir, { mode: 448 });
|
|
24963
25038
|
for (const path of sourceFiles)
|
|
24964
|
-
|
|
25039
|
+
renameSync3(path, join8(archiveDir, basename(path)));
|
|
24965
25040
|
} catch (error) {
|
|
24966
25041
|
const message = error instanceof Error ? error.message : String(error);
|
|
24967
25042
|
cleanupWarnings.push(`Could not archive legacy wallet files: ${message}`);
|
|
@@ -27095,7 +27170,7 @@ zoo`.split(`
|
|
|
27095
27170
|
// package.json
|
|
27096
27171
|
var package_default = {
|
|
27097
27172
|
name: "routstrd",
|
|
27098
|
-
version: "0.4.
|
|
27173
|
+
version: "0.4.3",
|
|
27099
27174
|
module: "src/index.ts",
|
|
27100
27175
|
type: "module",
|
|
27101
27176
|
private: false,
|
|
@@ -27121,7 +27196,7 @@ var package_default = {
|
|
|
27121
27196
|
"@cashu/cashu-ts": "^4.3.0",
|
|
27122
27197
|
"@cashu/coco-core": "^1.0.1",
|
|
27123
27198
|
"@cashu/coco-sqlite-bun": "^1.0.1",
|
|
27124
|
-
"@routstr/sdk": "^0.4.
|
|
27199
|
+
"@routstr/sdk": "^0.4.1",
|
|
27125
27200
|
"@scure/bip39": "^2.2.0",
|
|
27126
27201
|
"applesauce-core": "^5.1.0",
|
|
27127
27202
|
"applesauce-relay": "^5.1.0",
|
|
@@ -27162,10 +27237,10 @@ ${invoice}`);
|
|
|
27162
27237
|
}
|
|
27163
27238
|
function initializeWallet(walletDir2 = walletDir()) {
|
|
27164
27239
|
const walletConfig = join9(walletDir2, "config.json");
|
|
27165
|
-
|
|
27166
|
-
|
|
27167
|
-
if (
|
|
27168
|
-
|
|
27240
|
+
mkdirSync10(walletDir2, { recursive: true, mode: 448 });
|
|
27241
|
+
chmodSync3(walletDir2, 448);
|
|
27242
|
+
if (existsSync14(walletConfig)) {
|
|
27243
|
+
chmodSync3(walletConfig, 384);
|
|
27169
27244
|
console.log("Wallet already initialized.");
|
|
27170
27245
|
return;
|
|
27171
27246
|
}
|
|
@@ -27176,7 +27251,7 @@ function initializeWallet(walletDir2 = walletDir()) {
|
|
|
27176
27251
|
encrypted: false,
|
|
27177
27252
|
createdAt: new Date().toISOString()
|
|
27178
27253
|
};
|
|
27179
|
-
|
|
27254
|
+
writeFileSync3(walletConfig, JSON.stringify(config, null, 2), {
|
|
27180
27255
|
mode: 384,
|
|
27181
27256
|
flag: "wx"
|
|
27182
27257
|
});
|
|
@@ -27204,7 +27279,7 @@ Restarting routstrd daemon...`);
|
|
|
27204
27279
|
for (let i2 = 0;i2 < 100; i2++) {
|
|
27205
27280
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
27206
27281
|
const healthDown = !await isDaemonRunning();
|
|
27207
|
-
const pidFileReleased = !
|
|
27282
|
+
const pidFileReleased = !existsSync14(pidFilePath);
|
|
27208
27283
|
if (healthDown && pidFileReleased)
|
|
27209
27284
|
break;
|
|
27210
27285
|
}
|
|
@@ -27247,16 +27322,16 @@ async function requireLocalDaemon() {
|
|
|
27247
27322
|
}
|
|
27248
27323
|
async function initDaemon() {
|
|
27249
27324
|
console.log("Initializing routstrd...");
|
|
27250
|
-
if (!
|
|
27251
|
-
mkdirSync9(CONFIG_DIR, { recursive: true });
|
|
27325
|
+
if (!existsSync14(CONFIG_DIR)) {
|
|
27252
27326
|
console.log(`Created config directory: ${CONFIG_DIR}`);
|
|
27253
27327
|
}
|
|
27254
|
-
|
|
27328
|
+
ensureDirsSync();
|
|
27329
|
+
if (!existsSync14(CONFIG_FILE)) {
|
|
27255
27330
|
const config2 = {
|
|
27256
27331
|
...DEFAULT_CONFIG,
|
|
27257
27332
|
cocodPath: null
|
|
27258
27333
|
};
|
|
27259
|
-
|
|
27334
|
+
saveDaemonConfig(config2);
|
|
27260
27335
|
console.log(`Created config file: ${CONFIG_FILE}`);
|
|
27261
27336
|
}
|
|
27262
27337
|
const config = await loadConfig();
|
|
@@ -27265,7 +27340,7 @@ async function initDaemon() {
|
|
|
27265
27340
|
const nsec = nip19_exports.nsecEncode(secretKey);
|
|
27266
27341
|
const npub = npubFromSecretKey(secretKey);
|
|
27267
27342
|
config.nsec = nsec;
|
|
27268
|
-
|
|
27343
|
+
saveDaemonConfig(config);
|
|
27269
27344
|
console.log(`
|
|
27270
27345
|
A new Nostr identity has been generated for authentication.`);
|
|
27271
27346
|
console.log(`Your npub: ${npub}`);
|
|
@@ -27279,7 +27354,7 @@ A new Nostr identity has been generated for authentication.`);
|
|
|
27279
27354
|
socketPath: legacyCocodSocketPath()
|
|
27280
27355
|
}),
|
|
27281
27356
|
acquireLegacyLock: () => {
|
|
27282
|
-
|
|
27357
|
+
mkdirSync10(dirname8(legacyCocodPidPath()), {
|
|
27283
27358
|
recursive: true,
|
|
27284
27359
|
mode: 448
|
|
27285
27360
|
});
|
|
@@ -27307,10 +27382,7 @@ To ensure routstrd persists across system restarts, run: 'routstrd service insta
|
|
|
27307
27382
|
}
|
|
27308
27383
|
program.name("routstrd").description("Routstr daemon - Manage routstr processes").version(package_default.version, "--version", "output the version number");
|
|
27309
27384
|
program.command("update").description("Update routstrd to the latest version").action(async () => {
|
|
27310
|
-
const packages = [
|
|
27311
|
-
{ name: "routstrd", label: "routstrd" },
|
|
27312
|
-
{ name: "@routstr/cocod", label: "cocod" }
|
|
27313
|
-
];
|
|
27385
|
+
const packages = [{ name: "routstrd", label: "routstrd" }];
|
|
27314
27386
|
let updatedAny = false;
|
|
27315
27387
|
for (const { name, label } of packages) {
|
|
27316
27388
|
const installed = await getGlobalPackageVersion(name);
|
|
@@ -27444,8 +27516,8 @@ program.command("remote [url]").description("Show the configured remote daemon,
|
|
|
27444
27516
|
process.exit(1);
|
|
27445
27517
|
}
|
|
27446
27518
|
}
|
|
27447
|
-
if (!
|
|
27448
|
-
|
|
27519
|
+
if (!existsSync14(CONFIG_DIR)) {
|
|
27520
|
+
mkdirSync10(CONFIG_DIR, { recursive: true });
|
|
27449
27521
|
}
|
|
27450
27522
|
const updates = { daemonUrl: url };
|
|
27451
27523
|
if (options.authUrl) {
|
|
@@ -27463,7 +27535,7 @@ program.command("remote [url]").description("Show the configured remote daemon,
|
|
|
27463
27535
|
...config,
|
|
27464
27536
|
...updates
|
|
27465
27537
|
};
|
|
27466
|
-
|
|
27538
|
+
saveDaemonConfig(updatedConfig);
|
|
27467
27539
|
console.log(`Remote daemon URL set to: ${url}`);
|
|
27468
27540
|
if (options.authUrl) {
|
|
27469
27541
|
console.log(`Auth proxy URL set to: ${options.authUrl}`);
|
|
@@ -27485,7 +27557,7 @@ program.command("local").description("Switch back to local daemon mode (removes
|
|
|
27485
27557
|
const previousAuthUrl = config.authUrl;
|
|
27486
27558
|
const { daemonUrl: _daemonUrl, authUrl: _authUrl, ...rest } = config;
|
|
27487
27559
|
const updatedConfig = rest;
|
|
27488
|
-
|
|
27560
|
+
saveDaemonConfig(updatedConfig);
|
|
27489
27561
|
console.log("Switched back to local daemon mode.");
|
|
27490
27562
|
if (previousDaemonUrl) {
|
|
27491
27563
|
console.log(` Removed remote daemon URL: ${previousDaemonUrl}`);
|
|
@@ -27816,6 +27888,46 @@ providersCmd.command("enable <indices...>").description("Enable providers by the
|
|
|
27816
27888
|
}
|
|
27817
27889
|
}
|
|
27818
27890
|
});
|
|
27891
|
+
providersCmd.command("reviews").description("Show all known providers with their stored review events and event IDs").action(async () => {
|
|
27892
|
+
await ensureDaemonRunning();
|
|
27893
|
+
const result = await callDaemon("/providers/reviews");
|
|
27894
|
+
if (result.error) {
|
|
27895
|
+
console.log(result.error);
|
|
27896
|
+
process.exit(1);
|
|
27897
|
+
}
|
|
27898
|
+
const output = result.output;
|
|
27899
|
+
if (!output) {
|
|
27900
|
+
console.log("No provider review data available.");
|
|
27901
|
+
return;
|
|
27902
|
+
}
|
|
27903
|
+
console.log(`Providers (${output.totalCount} total, ${output.reviewEventCount} stored review events):
|
|
27904
|
+
`);
|
|
27905
|
+
for (const provider of output.providers) {
|
|
27906
|
+
const status = provider.disabled ? "DISABLED" : "enabled ";
|
|
27907
|
+
console.log(` [${provider.index}] ${status} ${provider.baseUrl} (${provider.reviewCount} review${provider.reviewCount === 1 ? "" : "s"})`);
|
|
27908
|
+
if (provider.nodePubkeys.length > 0) {
|
|
27909
|
+
console.log(` node pubkeys: ${provider.nodePubkeys.join(", ")}`);
|
|
27910
|
+
}
|
|
27911
|
+
for (const review of provider.reviews) {
|
|
27912
|
+
const label = review.label || "review";
|
|
27913
|
+
const created = new Date(review.createdAt * 1000).toISOString();
|
|
27914
|
+
console.log(` - ${label} eventId: ${review.eventId}`);
|
|
27915
|
+
console.log(` node: ${review.nodePubkey ?? "(none)"}`);
|
|
27916
|
+
console.log(` author: ${review.authorPubkey ?? "(none)"}`);
|
|
27917
|
+
console.log(` at: ${created}`);
|
|
27918
|
+
}
|
|
27919
|
+
}
|
|
27920
|
+
if (output.unmatchedReviews.length > 0) {
|
|
27921
|
+
console.log(`
|
|
27922
|
+
Unmatched review events (${output.unmatchedReviews.length} \u2014 node pubkey not in any known provider):`);
|
|
27923
|
+
for (const review of output.unmatchedReviews) {
|
|
27924
|
+
const created = new Date(review.createdAt * 1000).toISOString();
|
|
27925
|
+
console.log(` - eventId: ${review.eventId}`);
|
|
27926
|
+
console.log(` node: ${review.nodePubkey ?? "(none)"}`);
|
|
27927
|
+
console.log(` at: ${created}`);
|
|
27928
|
+
}
|
|
27929
|
+
}
|
|
27930
|
+
});
|
|
27819
27931
|
var clientsCmd = program.command("clients").description("List and manage clients");
|
|
27820
27932
|
clientsCmd.command("list").description("List all clients").action(async () => {
|
|
27821
27933
|
await listClientsAction();
|
|
@@ -28338,7 +28450,7 @@ serviceCmd.command("install").description("Install and start routstrd using PM2
|
|
|
28338
28450
|
const path = __require("path");
|
|
28339
28451
|
daemonPath = path.join(path.dirname(import.meta.url).replace("file://", ""), "daemon", "index.js");
|
|
28340
28452
|
}
|
|
28341
|
-
if (!
|
|
28453
|
+
if (!existsSync14(daemonPath)) {
|
|
28342
28454
|
console.error(`Could not find daemon at ${daemonPath}. Did you run 'bun run build'?`);
|
|
28343
28455
|
process.exit(1);
|
|
28344
28456
|
}
|
|
@@ -28386,7 +28498,7 @@ program.command("restart").description("Restart the background daemon").option("
|
|
|
28386
28498
|
for (let i2 = 0;i2 < 100; i2++) {
|
|
28387
28499
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
28388
28500
|
const healthDown = !await isDaemonRunning();
|
|
28389
|
-
const pidFileReleased = !
|
|
28501
|
+
const pidFileReleased = !existsSync14(pidFilePath);
|
|
28390
28502
|
if (healthDown && pidFileReleased) {
|
|
28391
28503
|
break;
|
|
28392
28504
|
}
|
|
@@ -28444,7 +28556,7 @@ xcashu mode is coming soon! Only lazyrefund/apikeys is available at this time.`)
|
|
|
28444
28556
|
...config,
|
|
28445
28557
|
mode: selectedMode
|
|
28446
28558
|
};
|
|
28447
|
-
|
|
28559
|
+
saveDaemonConfig(updatedConfig);
|
|
28448
28560
|
console.log(`Mode set to '${selectedMode}'. Restarting daemon...`);
|
|
28449
28561
|
const wasRunning = await isDaemonRunning();
|
|
28450
28562
|
if (wasRunning) {
|
|
@@ -28477,7 +28589,7 @@ function getLogFileForDate2(logsDir, date = new Date) {
|
|
|
28477
28589
|
return `${logsDir}/${year}-${month}-${day}.log`;
|
|
28478
28590
|
}
|
|
28479
28591
|
function readLastLines(file, lines) {
|
|
28480
|
-
const content =
|
|
28592
|
+
const content = readFileSync5(file, "utf8");
|
|
28481
28593
|
const allLines = content.replace(/\r\n/g, `
|
|
28482
28594
|
`).split(`
|
|
28483
28595
|
`);
|
|
@@ -28494,7 +28606,7 @@ async function followLogFile(file, lines) {
|
|
|
28494
28606
|
let position = statSync4(file).size;
|
|
28495
28607
|
while (true) {
|
|
28496
28608
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
28497
|
-
if (!
|
|
28609
|
+
if (!existsSync14(file)) {
|
|
28498
28610
|
continue;
|
|
28499
28611
|
}
|
|
28500
28612
|
const size = statSync4(file).size;
|
|
@@ -28516,17 +28628,17 @@ program.command("logs").description("View daemon logs").option("-f, --follow", "
|
|
|
28516
28628
|
const yesterday = new Date;
|
|
28517
28629
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
28518
28630
|
const yesterdayFile = getLogFileForDate2(logsDir, yesterday);
|
|
28519
|
-
if (!
|
|
28631
|
+
if (!existsSync14(todayFile) && !existsSync14(yesterdayFile)) {
|
|
28520
28632
|
console.log("No log files found. Daemon may not have started yet.");
|
|
28521
28633
|
console.log(`Logs directory: ${logsDir}`);
|
|
28522
28634
|
process.exit(1);
|
|
28523
28635
|
}
|
|
28524
28636
|
const lines = parseInt(options.lines, 10);
|
|
28525
28637
|
const logFiles = [yesterdayFile, todayFile].filter((file, index, files) => {
|
|
28526
|
-
return
|
|
28638
|
+
return existsSync14(file) && files.indexOf(file) === index;
|
|
28527
28639
|
});
|
|
28528
28640
|
if (options.follow) {
|
|
28529
|
-
if (
|
|
28641
|
+
if (existsSync14(todayFile)) {
|
|
28530
28642
|
await followLogFile(todayFile, lines);
|
|
28531
28643
|
} else {
|
|
28532
28644
|
console.log("No log file for today to follow.");
|