routstrd 0.3.2 → 0.3.4
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 +791 -135
- package/dist/index.js +131 -346
- package/package.json +2 -2
- package/src/daemon/http/usage-summary.test.ts +259 -0
- package/src/daemon/http/usage-summary.ts +361 -0
- package/src/daemon/request-response-log-sink.ts +269 -0
- package/src/tui/usage/app.ts +85 -69
- package/src/tui/usage/data.ts +12 -193
- package/src/tui/usage/render.ts +57 -107
- package/src/tui/usage/types.ts +4 -45
- package/src/utils/config.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -2188,7 +2188,7 @@ var init_logger = __esm(() => {
|
|
|
2188
2188
|
});
|
|
2189
2189
|
|
|
2190
2190
|
// src/utils/config.ts
|
|
2191
|
-
var HOME2, CONFIG_DIR, SOCKET_PATH, PID_FILE, DB_PATH, CONFIG_FILE, LOGS_DIR2, DEFAULT_CONFIG;
|
|
2191
|
+
var HOME2, CONFIG_DIR, SOCKET_PATH, PID_FILE, DB_PATH, CONFIG_FILE, LOGS_DIR2, REQUEST_RESPONSE_LOGS_DIR, DEFAULT_CONFIG;
|
|
2192
2192
|
var init_config = __esm(() => {
|
|
2193
2193
|
HOME2 = process.env.HOME || process.env.USERPROFILE || "";
|
|
2194
2194
|
CONFIG_DIR = process.env.ROUTSTRD_DIR || `${HOME2}/.routstrd`;
|
|
@@ -2197,6 +2197,7 @@ var init_config = __esm(() => {
|
|
|
2197
2197
|
DB_PATH = `${CONFIG_DIR}/routstr.db`;
|
|
2198
2198
|
CONFIG_FILE = `${CONFIG_DIR}/config.json`;
|
|
2199
2199
|
LOGS_DIR2 = `${CONFIG_DIR}/logs`;
|
|
2200
|
+
REQUEST_RESPONSE_LOGS_DIR = `${CONFIG_DIR}/request-response-logs`;
|
|
2200
2201
|
DEFAULT_CONFIG = {
|
|
2201
2202
|
port: 8008,
|
|
2202
2203
|
provider: null,
|
|
@@ -13875,9 +13876,6 @@ var init_constants = __esm(() => {
|
|
|
13875
13876
|
// src/tui/usage/data.ts
|
|
13876
13877
|
async function fetchStatus() {
|
|
13877
13878
|
try {
|
|
13878
|
-
const running = await isDaemonRunning();
|
|
13879
|
-
if (!running)
|
|
13880
|
-
return null;
|
|
13881
13879
|
const result = await callDaemon("/status");
|
|
13882
13880
|
if (result.error)
|
|
13883
13881
|
return null;
|
|
@@ -13894,9 +13892,6 @@ async function fetchStatus() {
|
|
|
13894
13892
|
}
|
|
13895
13893
|
async function fetchBalance() {
|
|
13896
13894
|
try {
|
|
13897
|
-
const running = await isDaemonRunning();
|
|
13898
|
-
if (!running)
|
|
13899
|
-
return null;
|
|
13900
13895
|
const result = await callDaemon("/keys/balance");
|
|
13901
13896
|
if (result.error)
|
|
13902
13897
|
return null;
|
|
@@ -13911,42 +13906,27 @@ async function fetchBalance() {
|
|
|
13911
13906
|
return null;
|
|
13912
13907
|
}
|
|
13913
13908
|
}
|
|
13914
|
-
async function
|
|
13909
|
+
async function fetchUsageSummary() {
|
|
13915
13910
|
try {
|
|
13916
|
-
const
|
|
13917
|
-
|
|
13918
|
-
return null;
|
|
13919
|
-
const result = await callDaemon(`/usage?limit=${limit}`);
|
|
13911
|
+
const tz = new Date().getTimezoneOffset();
|
|
13912
|
+
const result = await callDaemon(`/usage/summary?tz=${tz}`);
|
|
13920
13913
|
if (result.error)
|
|
13921
13914
|
return null;
|
|
13922
|
-
const
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
promptTokens: acc.promptTokens + entry.promptTokens,
|
|
13926
|
-
completionTokens: acc.completionTokens + entry.completionTokens,
|
|
13927
|
-
totalTokens: acc.totalTokens + entry.totalTokens,
|
|
13928
|
-
satsCost: acc.satsCost + entry.satsCost
|
|
13929
|
-
}), { promptTokens: 0, completionTokens: 0, totalTokens: 0, satsCost: 0 });
|
|
13915
|
+
const summary = result.output;
|
|
13916
|
+
if (!summary || typeof summary.totals !== "object")
|
|
13917
|
+
return null;
|
|
13930
13918
|
return {
|
|
13931
|
-
entries:
|
|
13932
|
-
totalEntries:
|
|
13933
|
-
totalSatsCost: totals.satsCost,
|
|
13934
|
-
recentSatsCost: totals.satsCost,
|
|
13935
|
-
limit
|
|
13919
|
+
entries: summary.recent,
|
|
13920
|
+
totalEntries: summary.totals.requests,
|
|
13921
|
+
totalSatsCost: summary.totals.satsCost,
|
|
13922
|
+
recentSatsCost: summary.totals.satsCost,
|
|
13923
|
+
limit: 50,
|
|
13924
|
+
summary
|
|
13936
13925
|
};
|
|
13937
13926
|
} catch {
|
|
13938
13927
|
return null;
|
|
13939
13928
|
}
|
|
13940
13929
|
}
|
|
13941
|
-
function getTodayStart() {
|
|
13942
|
-
const now2 = new Date;
|
|
13943
|
-
return new Date(now2.getFullYear(), now2.getMonth(), now2.getDate()).getTime();
|
|
13944
|
-
}
|
|
13945
|
-
function formatDate(timestamp) {
|
|
13946
|
-
const d = new Date(timestamp);
|
|
13947
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
13948
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
13949
|
-
}
|
|
13950
13930
|
function formatTime(timestamp) {
|
|
13951
13931
|
const d = new Date(timestamp);
|
|
13952
13932
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -13959,121 +13939,8 @@ function formatNumber(n) {
|
|
|
13959
13939
|
return (n / 1000).toFixed(1) + "K";
|
|
13960
13940
|
return n.toString();
|
|
13961
13941
|
}
|
|
13962
|
-
function getDayStats(entries) {
|
|
13963
|
-
const days = new Map;
|
|
13964
|
-
for (const entry of entries) {
|
|
13965
|
-
const date = formatDate(entry.timestamp);
|
|
13966
|
-
const existing = days.get(date) || { date, requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
13967
|
-
days.set(date, {
|
|
13968
|
-
...existing,
|
|
13969
|
-
requests: existing.requests + 1,
|
|
13970
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
13971
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
13972
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
13973
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
13974
|
-
});
|
|
13975
|
-
}
|
|
13976
|
-
return days;
|
|
13977
|
-
}
|
|
13978
|
-
function getHourlyToday(entries) {
|
|
13979
|
-
const todayStart = getTodayStart();
|
|
13980
|
-
const hours = new Map;
|
|
13981
|
-
for (const entry of entries) {
|
|
13982
|
-
if (entry.timestamp < todayStart)
|
|
13983
|
-
continue;
|
|
13984
|
-
const hour = new Date(entry.timestamp).getHours();
|
|
13985
|
-
const existing = hours.get(hour) || {
|
|
13986
|
-
date: formatDate(entry.timestamp),
|
|
13987
|
-
requests: 0,
|
|
13988
|
-
satsCost: 0,
|
|
13989
|
-
promptTokens: 0,
|
|
13990
|
-
completionTokens: 0,
|
|
13991
|
-
totalTokens: 0
|
|
13992
|
-
};
|
|
13993
|
-
hours.set(hour, {
|
|
13994
|
-
...existing,
|
|
13995
|
-
requests: existing.requests + 1,
|
|
13996
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
13997
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
13998
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
13999
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14000
|
-
});
|
|
14001
|
-
}
|
|
14002
|
-
return hours;
|
|
14003
|
-
}
|
|
14004
|
-
function getModelStats(entries) {
|
|
14005
|
-
const models = new Map;
|
|
14006
|
-
for (const entry of entries) {
|
|
14007
|
-
const existing = models.get(entry.modelId) || {
|
|
14008
|
-
modelId: entry.modelId,
|
|
14009
|
-
requests: 0,
|
|
14010
|
-
satsCost: 0,
|
|
14011
|
-
promptTokens: 0,
|
|
14012
|
-
completionTokens: 0,
|
|
14013
|
-
totalTokens: 0
|
|
14014
|
-
};
|
|
14015
|
-
models.set(entry.modelId, {
|
|
14016
|
-
...existing,
|
|
14017
|
-
requests: existing.requests + 1,
|
|
14018
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14019
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14020
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14021
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14022
|
-
});
|
|
14023
|
-
}
|
|
14024
|
-
return Array.from(models.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14025
|
-
}
|
|
14026
|
-
function getProviderStats(entries) {
|
|
14027
|
-
const providers = new Map;
|
|
14028
|
-
for (const entry of entries) {
|
|
14029
|
-
const url = entry.baseUrl || "unknown";
|
|
14030
|
-
const existing = providers.get(url) || {
|
|
14031
|
-
baseUrl: url,
|
|
14032
|
-
requests: 0,
|
|
14033
|
-
satsCost: 0,
|
|
14034
|
-
promptTokens: 0,
|
|
14035
|
-
completionTokens: 0,
|
|
14036
|
-
totalTokens: 0
|
|
14037
|
-
};
|
|
14038
|
-
providers.set(url, {
|
|
14039
|
-
...existing,
|
|
14040
|
-
requests: existing.requests + 1,
|
|
14041
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14042
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14043
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14044
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14045
|
-
});
|
|
14046
|
-
}
|
|
14047
|
-
return Array.from(providers.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14048
|
-
}
|
|
14049
|
-
function getClientStats(entries) {
|
|
14050
|
-
const clients = new Map;
|
|
14051
|
-
for (const entry of entries) {
|
|
14052
|
-
const client = entry.client || "unknown";
|
|
14053
|
-
const existing = clients.get(client) || {
|
|
14054
|
-
client,
|
|
14055
|
-
requests: 0,
|
|
14056
|
-
satsCost: 0,
|
|
14057
|
-
promptTokens: 0,
|
|
14058
|
-
completionTokens: 0,
|
|
14059
|
-
totalTokens: 0
|
|
14060
|
-
};
|
|
14061
|
-
clients.set(client, {
|
|
14062
|
-
...existing,
|
|
14063
|
-
requests: existing.requests + 1,
|
|
14064
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14065
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14066
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14067
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14068
|
-
});
|
|
14069
|
-
}
|
|
14070
|
-
return Array.from(clients.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14071
|
-
}
|
|
14072
13942
|
async function fetchClients() {
|
|
14073
13943
|
try {
|
|
14074
|
-
const running = await isDaemonRunning();
|
|
14075
|
-
if (!running)
|
|
14076
|
-
return [];
|
|
14077
13944
|
const result = await callDaemon("/clients");
|
|
14078
13945
|
if (result.error)
|
|
14079
13946
|
return [];
|
|
@@ -14090,48 +13957,6 @@ async function fetchClients() {
|
|
|
14090
13957
|
function hasAnyNpubs(clients) {
|
|
14091
13958
|
return clients.some((c) => !!c.ownerNpub);
|
|
14092
13959
|
}
|
|
14093
|
-
function getNpubStats(entries, clients) {
|
|
14094
|
-
const clientNpubMap = new Map;
|
|
14095
|
-
for (const c of clients) {
|
|
14096
|
-
if (c.ownerNpub) {
|
|
14097
|
-
clientNpubMap.set(c.clientId, c.ownerNpub);
|
|
14098
|
-
}
|
|
14099
|
-
}
|
|
14100
|
-
const npubs = new Map;
|
|
14101
|
-
for (const entry of entries) {
|
|
14102
|
-
const npub = clientNpubMap.get(entry.client || "");
|
|
14103
|
-
if (!npub)
|
|
14104
|
-
continue;
|
|
14105
|
-
const existing = npubs.get(npub) || {
|
|
14106
|
-
npub,
|
|
14107
|
-
requests: 0,
|
|
14108
|
-
satsCost: 0,
|
|
14109
|
-
promptTokens: 0,
|
|
14110
|
-
completionTokens: 0,
|
|
14111
|
-
totalTokens: 0
|
|
14112
|
-
};
|
|
14113
|
-
npubs.set(npub, {
|
|
14114
|
-
...existing,
|
|
14115
|
-
requests: existing.requests + 1,
|
|
14116
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14117
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14118
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14119
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14120
|
-
});
|
|
14121
|
-
}
|
|
14122
|
-
return Array.from(npubs.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14123
|
-
}
|
|
14124
|
-
function getTotals(entries) {
|
|
14125
|
-
if (!entries || !Array.isArray(entries)) {
|
|
14126
|
-
return { promptTokens: 0, completionTokens: 0, totalTokens: 0, satsCost: 0 };
|
|
14127
|
-
}
|
|
14128
|
-
return entries.reduce((acc, entry) => ({
|
|
14129
|
-
promptTokens: acc.promptTokens + entry.promptTokens,
|
|
14130
|
-
completionTokens: acc.completionTokens + entry.completionTokens,
|
|
14131
|
-
totalTokens: acc.totalTokens + entry.totalTokens,
|
|
14132
|
-
satsCost: acc.satsCost + entry.satsCost
|
|
14133
|
-
}), { promptTokens: 0, completionTokens: 0, totalTokens: 0, satsCost: 0 });
|
|
14134
|
-
}
|
|
14135
13960
|
var init_data = __esm(() => {
|
|
14136
13961
|
init_daemon_client();
|
|
14137
13962
|
});
|
|
@@ -14331,7 +14156,7 @@ function renderBarChart(label, value, maxValue, width, color, percentageValue, s
|
|
|
14331
14156
|
return `${paddedLabel} ${bar}${suffix}`;
|
|
14332
14157
|
}
|
|
14333
14158
|
function renderOverview(stats, balance, status, width) {
|
|
14334
|
-
const totals =
|
|
14159
|
+
const totals = stats.summary.totals;
|
|
14335
14160
|
const totalRequests = stats.totalEntries;
|
|
14336
14161
|
const totalVisibleCost = stats.totalSatsCost;
|
|
14337
14162
|
const avgCost = totalRequests > 0 ? totalVisibleCost / totalRequests : 0;
|
|
@@ -14440,7 +14265,7 @@ function renderOverview(stats, balance, status, width) {
|
|
|
14440
14265
|
output = renderBox(statusLines, width, "System Status") + `
|
|
14441
14266
|
` + output;
|
|
14442
14267
|
}
|
|
14443
|
-
const modelStats =
|
|
14268
|
+
const modelStats = stats.summary.models;
|
|
14444
14269
|
if (modelStats.length > 0) {
|
|
14445
14270
|
const maxCost = modelStats[0].satsCost;
|
|
14446
14271
|
const totalCost = Math.max(totalVisibleCost, 1);
|
|
@@ -14451,7 +14276,7 @@ function renderOverview(stats, balance, status, width) {
|
|
|
14451
14276
|
output += `
|
|
14452
14277
|
` + renderBox(modelLines, width, "Top Models by Cost");
|
|
14453
14278
|
}
|
|
14454
|
-
const clientStats =
|
|
14279
|
+
const clientStats = stats.summary.clients;
|
|
14455
14280
|
if (clientStats.length > 0) {
|
|
14456
14281
|
const maxCost = clientStats[0].satsCost;
|
|
14457
14282
|
const totalCost = Math.max(totalVisibleCost, 1);
|
|
@@ -14465,18 +14290,18 @@ function renderOverview(stats, balance, status, width) {
|
|
|
14465
14290
|
return output;
|
|
14466
14291
|
}
|
|
14467
14292
|
function renderToday(stats, width) {
|
|
14468
|
-
const hourly = getHourlyToday(stats.entries);
|
|
14469
|
-
const todayStart = getTodayStart();
|
|
14470
14293
|
const currentHour = new Date().getHours();
|
|
14471
|
-
const
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
}
|
|
14294
|
+
const _now = new Date;
|
|
14295
|
+
const todayDateStr = `${_now.getFullYear()}-${String(_now.getMonth() + 1).padStart(2, "0")}-${String(_now.getDate()).padStart(2, "0")}`;
|
|
14296
|
+
let todayStats;
|
|
14297
|
+
let recentDays;
|
|
14298
|
+
let hourlyMap;
|
|
14299
|
+
if (stats.summary) {
|
|
14300
|
+
const { days, hoursToday } = stats.summary;
|
|
14301
|
+
const todayDayStat = days[0]?.date === todayDateStr ? days[0] : undefined;
|
|
14302
|
+
todayStats = todayDayStat ? { date: todayDayStat.date, requests: todayDayStat.requests, satsCost: todayDayStat.satsCost, promptTokens: todayDayStat.promptTokens, completionTokens: todayDayStat.completionTokens, totalTokens: todayDayStat.totalTokens } : { date: todayDateStr, requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
14303
|
+
recentDays = days.filter((d) => d.date !== todayDateStr).slice(0, 6);
|
|
14304
|
+
hourlyMap = new Map(hoursToday.map((h) => [h.hour, { requests: h.requests, satsCost: h.satsCost }]));
|
|
14480
14305
|
}
|
|
14481
14306
|
const summaryLines = [
|
|
14482
14307
|
`${COLORS.bold}Date:${COLORS.reset} ${todayStats.date}`,
|
|
@@ -14485,18 +14310,17 @@ function renderToday(stats, width) {
|
|
|
14485
14310
|
`${COLORS.bold}Tokens:${COLORS.reset} ${formatNumber(todayStats.totalTokens)} (p: ${formatNumber(todayStats.promptTokens)} + c: ${formatNumber(todayStats.completionTokens)})`
|
|
14486
14311
|
];
|
|
14487
14312
|
let output = renderBox(summaryLines, width, "Today");
|
|
14488
|
-
|
|
14489
|
-
|
|
14490
|
-
const dayLines = days.slice(1).map((d) => `${d.date}: ${formatReqs(d.requests)} req, ${formatCost(d.satsCost)} sats, ${formatNumber(d.totalTokens)} tokens`);
|
|
14313
|
+
if (recentDays.length > 0) {
|
|
14314
|
+
const dayLines = recentDays.map((d) => `${d.date}: ${formatReqs(d.requests)} req, ${formatCost(d.satsCost)} sats, ${formatNumber(d.totalTokens)} tokens`);
|
|
14491
14315
|
output += `
|
|
14492
14316
|
` + renderBox(dayLines, width, "Recent Days");
|
|
14493
14317
|
}
|
|
14494
14318
|
const hourLines = [];
|
|
14495
|
-
const maxHourCost = Math.max(...Array.from(
|
|
14319
|
+
const maxHourCost = Math.max(...Array.from(hourlyMap.values()).map((h) => h.satsCost), 1);
|
|
14496
14320
|
const totalTodayCost = Math.max(todayStats.satsCost, 1);
|
|
14497
14321
|
const hourLabels = [];
|
|
14498
14322
|
for (let h = currentHour;h >= 0; h--) {
|
|
14499
|
-
const hStat =
|
|
14323
|
+
const hStat = hourlyMap.get(h);
|
|
14500
14324
|
const reqs = hStat?.requests || 0;
|
|
14501
14325
|
const cost = hStat?.satsCost || 0;
|
|
14502
14326
|
hourLabels.push(`${h.toString().padStart(2, "0")}:00 (${formatReqs(reqs)} req, ${formatCost(cost)} sats) `);
|
|
@@ -14504,7 +14328,7 @@ function renderToday(stats, width) {
|
|
|
14504
14328
|
const maxHourLabel = Math.max(...hourLabels.map((l) => l.length));
|
|
14505
14329
|
startBarSection("hourly", maxHourLabel);
|
|
14506
14330
|
for (let i2 = currentHour;i2 >= 0; i2--) {
|
|
14507
|
-
const hStat =
|
|
14331
|
+
const hStat = hourlyMap.get(i2);
|
|
14508
14332
|
const reqs = hStat?.requests || 0;
|
|
14509
14333
|
const cost = hStat?.satsCost || 0;
|
|
14510
14334
|
hourLines.push(renderBarChart(hourLabels[currentHour - i2], cost, maxHourCost, width - 4, i2 === currentHour ? COLORS.green : COLORS.cyan, cost / totalTodayCost * 100, "hourly"));
|
|
@@ -14515,7 +14339,7 @@ function renderToday(stats, width) {
|
|
|
14515
14339
|
return output;
|
|
14516
14340
|
}
|
|
14517
14341
|
function renderModels(stats, width) {
|
|
14518
|
-
const modelStats =
|
|
14342
|
+
const modelStats = stats.summary.models;
|
|
14519
14343
|
if (modelStats.length === 0)
|
|
14520
14344
|
return renderBox(["No model data available"], width, "Models");
|
|
14521
14345
|
const totalCost = stats.totalSatsCost;
|
|
@@ -14538,7 +14362,7 @@ function renderModels(stats, width) {
|
|
|
14538
14362
|
return renderBox(lines, width, "Model Breakdown");
|
|
14539
14363
|
}
|
|
14540
14364
|
function renderProviders(stats, width) {
|
|
14541
|
-
const providerStats =
|
|
14365
|
+
const providerStats = stats.summary.providers;
|
|
14542
14366
|
if (providerStats.length === 0)
|
|
14543
14367
|
return renderBox(["No provider data available"], width, "Providers");
|
|
14544
14368
|
const lines = [];
|
|
@@ -14553,8 +14377,8 @@ function renderProviders(stats, width) {
|
|
|
14553
14377
|
return renderBox(lines, width, "Provider Breakdown");
|
|
14554
14378
|
}
|
|
14555
14379
|
function renderTokens(stats, width) {
|
|
14556
|
-
const totals =
|
|
14557
|
-
const modelStats =
|
|
14380
|
+
const totals = stats.summary.totals;
|
|
14381
|
+
const modelStats = stats.summary.models;
|
|
14558
14382
|
const summaryLines = [
|
|
14559
14383
|
`${COLORS.bold}Total Prompt Tokens:${COLORS.reset} ${formatNumber(totals.promptTokens)}`,
|
|
14560
14384
|
`${COLORS.bold}Total Completion Tokens:${COLORS.reset} ${formatNumber(totals.completionTokens)}`,
|
|
@@ -14572,29 +14396,14 @@ function renderTokens(stats, width) {
|
|
|
14572
14396
|
output += `
|
|
14573
14397
|
` + renderBox(tokenLines, width, "Tokens by Model");
|
|
14574
14398
|
}
|
|
14575
|
-
const sizeBuckets =
|
|
14576
|
-
tiny: { min: 0, max: 1000, count: 0, cost: 0 },
|
|
14577
|
-
small: { min: 1000, max: 1e4, count: 0, cost: 0 },
|
|
14578
|
-
medium: { min: 1e4, max: 50000, count: 0, cost: 0 },
|
|
14579
|
-
large: { min: 50000, max: 1e5, count: 0, cost: 0 },
|
|
14580
|
-
huge: { min: 1e5, max: Infinity, count: 0, cost: 0 }
|
|
14581
|
-
};
|
|
14582
|
-
for (const entry of stats.entries) {
|
|
14583
|
-
for (const bucket of Object.values(sizeBuckets)) {
|
|
14584
|
-
if (entry.totalTokens >= bucket.min && entry.totalTokens < bucket.max) {
|
|
14585
|
-
bucket.count++;
|
|
14586
|
-
bucket.cost += entry.satsCost;
|
|
14587
|
-
break;
|
|
14588
|
-
}
|
|
14589
|
-
}
|
|
14590
|
-
}
|
|
14399
|
+
const sizeBuckets = stats.summary.sizeBuckets;
|
|
14591
14400
|
const sizeLines = Object.entries(sizeBuckets).map(([name, bucket]) => `${name.padEnd(6)}: ${formatReqs(bucket.count).padStart(5)} reqs, ${formatCost(bucket.cost)} sats`);
|
|
14592
14401
|
output += `
|
|
14593
14402
|
` + renderBox(sizeLines, width, "Request Size Distribution");
|
|
14594
14403
|
return output;
|
|
14595
14404
|
}
|
|
14596
14405
|
function renderClients(stats, width) {
|
|
14597
|
-
const clientStats =
|
|
14406
|
+
const clientStats = stats.summary.clients;
|
|
14598
14407
|
if (clientStats.length === 0)
|
|
14599
14408
|
return renderBox(["No client data available (API key auth not used)"], width, "Client Breakdown");
|
|
14600
14409
|
const totalCost = stats.totalSatsCost;
|
|
@@ -14626,31 +14435,17 @@ function renderClients(stats, width) {
|
|
|
14626
14435
|
}
|
|
14627
14436
|
endBarSection("client-detail");
|
|
14628
14437
|
let output = renderBox(lines, width, "Client Breakdown");
|
|
14629
|
-
const clientModelMap = new Map;
|
|
14630
|
-
for (const entry of stats.entries) {
|
|
14631
|
-
const client = entry.client || "unknown";
|
|
14632
|
-
const model = entry.modelId;
|
|
14633
|
-
if (!clientModelMap.has(client))
|
|
14634
|
-
clientModelMap.set(client, new Map);
|
|
14635
|
-
const modelMap = clientModelMap.get(client);
|
|
14636
|
-
const existing = modelMap.get(model) || { requests: 0, satsCost: 0, tokens: 0 };
|
|
14637
|
-
modelMap.set(model, {
|
|
14638
|
-
requests: existing.requests + 1,
|
|
14639
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14640
|
-
tokens: existing.tokens + entry.totalTokens
|
|
14641
|
-
});
|
|
14642
|
-
}
|
|
14643
14438
|
const clientModelLines = [];
|
|
14644
|
-
|
|
14645
|
-
const
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14439
|
+
if (stats.summary) {
|
|
14440
|
+
for (const topClient of stats.summary.clients.filter((c) => c.client !== "unknown").slice(0, 3)) {
|
|
14441
|
+
if (topClient.topModels.length === 0)
|
|
14442
|
+
continue;
|
|
14443
|
+
clientModelLines.push(`${COLORS.bold}${topClient.client}${COLORS.reset} (${formatReqs(topClient.requests)} reqs, ${formatCost(topClient.satsCost)} sats)`);
|
|
14444
|
+
for (const m of topClient.topModels) {
|
|
14445
|
+
clientModelLines.push(` ${MODEL_COLORS[m.modelId] || MODEL_COLORS.default}${m.modelId.padEnd(18)}${COLORS.reset} ${formatNumber(m.totalTokens).padEnd(8)} tokens ${formatCost(m.satsCost)} sats`);
|
|
14446
|
+
}
|
|
14447
|
+
clientModelLines.push("");
|
|
14652
14448
|
}
|
|
14653
|
-
clientModelLines.push("");
|
|
14654
14449
|
}
|
|
14655
14450
|
if (clientModelLines.length > 0) {
|
|
14656
14451
|
output += `
|
|
@@ -14659,7 +14454,7 @@ function renderClients(stats, width) {
|
|
|
14659
14454
|
return output;
|
|
14660
14455
|
}
|
|
14661
14456
|
function renderNpubs(stats, clients, width) {
|
|
14662
|
-
const npubStats =
|
|
14457
|
+
const npubStats = stats.summary.npubs;
|
|
14663
14458
|
if (npubStats.length === 0)
|
|
14664
14459
|
return renderBox(["No npub data available"], width, "Npub Breakdown");
|
|
14665
14460
|
const totalCost = stats.totalSatsCost;
|
|
@@ -14692,38 +14487,17 @@ function renderNpubs(stats, clients, width) {
|
|
|
14692
14487
|
}
|
|
14693
14488
|
endBarSection("npub-detail");
|
|
14694
14489
|
let output = renderBox(lines, width, "Npub Breakdown");
|
|
14695
|
-
const npubModelMap = new Map;
|
|
14696
|
-
const clientNpubMap = new Map;
|
|
14697
|
-
for (const c of clients) {
|
|
14698
|
-
if (c.ownerNpub)
|
|
14699
|
-
clientNpubMap.set(c.clientId, c.ownerNpub);
|
|
14700
|
-
}
|
|
14701
|
-
for (const entry of stats.entries) {
|
|
14702
|
-
const npub = clientNpubMap.get(entry.client || "");
|
|
14703
|
-
if (!npub)
|
|
14704
|
-
continue;
|
|
14705
|
-
const model = entry.modelId;
|
|
14706
|
-
if (!npubModelMap.has(npub))
|
|
14707
|
-
npubModelMap.set(npub, new Map);
|
|
14708
|
-
const modelMap = npubModelMap.get(npub);
|
|
14709
|
-
const existing = modelMap.get(model) || { requests: 0, satsCost: 0, tokens: 0 };
|
|
14710
|
-
modelMap.set(model, {
|
|
14711
|
-
requests: existing.requests + 1,
|
|
14712
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14713
|
-
tokens: existing.tokens + entry.totalTokens
|
|
14714
|
-
});
|
|
14715
|
-
}
|
|
14716
14490
|
const npubModelLines = [];
|
|
14717
|
-
|
|
14718
|
-
const
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14491
|
+
if (stats.summary) {
|
|
14492
|
+
for (const topNpub of stats.summary.npubs.slice(0, 5)) {
|
|
14493
|
+
if (topNpub.topModels.length === 0)
|
|
14494
|
+
continue;
|
|
14495
|
+
npubModelLines.push(`${COLORS.bold}${truncateNpub(topNpub.npub)}${COLORS.reset} (${formatReqs(topNpub.requests)} reqs, ${formatCost(topNpub.satsCost)} sats)`);
|
|
14496
|
+
for (const m of topNpub.topModels) {
|
|
14497
|
+
npubModelLines.push(` ${MODEL_COLORS[m.modelId] || MODEL_COLORS.default}${m.modelId.padEnd(18)}${COLORS.reset} ${formatNumber(m.totalTokens).padEnd(8)} tokens ${formatCost(m.satsCost)} sats`);
|
|
14498
|
+
}
|
|
14499
|
+
npubModelLines.push("");
|
|
14725
14500
|
}
|
|
14726
|
-
npubModelLines.push("");
|
|
14727
14501
|
}
|
|
14728
14502
|
if (npubModelLines.length > 0) {
|
|
14729
14503
|
output += `
|
|
@@ -14820,10 +14594,9 @@ async function runUsageTui() {
|
|
|
14820
14594
|
let clients = [];
|
|
14821
14595
|
let visibleTabs = getVisibleTabs(false);
|
|
14822
14596
|
let refreshInterval = null;
|
|
14823
|
-
let shouldUpdate = true;
|
|
14824
14597
|
let autoRefresh = true;
|
|
14825
14598
|
let cleanedUp = false;
|
|
14826
|
-
let
|
|
14599
|
+
let fetching = false;
|
|
14827
14600
|
if (isInteractive) {
|
|
14828
14601
|
stdout.write(enterAlternateScreen() + hideCursor());
|
|
14829
14602
|
stdin.setRawMode?.(true);
|
|
@@ -14847,55 +14620,70 @@ async function runUsageTui() {
|
|
|
14847
14620
|
}
|
|
14848
14621
|
process.on("SIGINT", () => cleanup(0));
|
|
14849
14622
|
process.on("SIGTERM", () => cleanup(0));
|
|
14850
|
-
async function
|
|
14851
|
-
if (
|
|
14623
|
+
async function fetchData() {
|
|
14624
|
+
if (fetching)
|
|
14852
14625
|
return;
|
|
14853
|
-
|
|
14626
|
+
fetching = true;
|
|
14854
14627
|
try {
|
|
14855
|
-
const
|
|
14856
|
-
|
|
14857
|
-
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14628
|
+
const running2 = await isDaemonRunning();
|
|
14629
|
+
if (!running2) {
|
|
14630
|
+
stats = null;
|
|
14631
|
+
} else {
|
|
14632
|
+
const [newStats, newBalance, newStatus, newClients] = await Promise.all([
|
|
14633
|
+
fetchUsageSummary(),
|
|
14634
|
+
fetchBalance(),
|
|
14635
|
+
fetchStatus(),
|
|
14636
|
+
fetchClients()
|
|
14637
|
+
]);
|
|
14638
|
+
if (newStats)
|
|
14639
|
+
stats = newStats;
|
|
14640
|
+
if (newBalance)
|
|
14641
|
+
balance = newBalance;
|
|
14642
|
+
if (newStatus)
|
|
14643
|
+
status = newStatus;
|
|
14644
|
+
if (newClients && newClients.length > 0)
|
|
14645
|
+
clients = newClients;
|
|
14862
14646
|
const npubsVisible = hasAnyNpubs(clients);
|
|
14863
14647
|
visibleTabs = getVisibleTabs(npubsVisible);
|
|
14864
14648
|
if (currentTab === "npubs" && !npubsVisible) {
|
|
14865
14649
|
currentTab = "clients";
|
|
14866
14650
|
vimState.scrollPos = 0;
|
|
14867
14651
|
}
|
|
14868
|
-
shouldUpdate = false;
|
|
14869
14652
|
}
|
|
14870
|
-
|
|
14871
|
-
|
|
14653
|
+
render2();
|
|
14654
|
+
} finally {
|
|
14655
|
+
fetching = false;
|
|
14656
|
+
}
|
|
14657
|
+
}
|
|
14658
|
+
function render2() {
|
|
14659
|
+
const width = getWidth();
|
|
14660
|
+
const height = getHeight();
|
|
14661
|
+
if (!stats) {
|
|
14662
|
+
stdout.write(moveCursor(1, 1) + eraseDown() + `${COLORS.red}Error: Could not fetch usage data.${COLORS.reset}
|
|
14872
14663
|
` + `Make sure routstrd is running.
|
|
14873
14664
|
` + `
|
|
14874
14665
|
Press Q to quit.`);
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
14666
|
+
return;
|
|
14667
|
+
}
|
|
14668
|
+
const content = renderTabContent(currentTab, stats, balance, status, width, clients);
|
|
14669
|
+
const footer = `${COLORS.dim}Press [Q] to quit, [R] to refresh, [A] to toggle auto-refresh${autoRefresh ? " (on)" : " (off)"} scroll:${vimState.scrollPos}${COLORS.reset}${vimState.mode === "normal" ? ` ${COLORS.yellow}vim: hjkl/arrows, / search, g top, gg bottom${COLORS.reset}` : ""}`;
|
|
14670
|
+
const chrome = renderHeader(currentTab, width, visibleTabs) + renderTabs(currentTab, visibleTabs) + renderSeparator(width) + renderSearchBar();
|
|
14671
|
+
const chromeLines = chrome.split(`
|
|
14881
14672
|
`).length - 1;
|
|
14882
|
-
|
|
14883
|
-
|
|
14673
|
+
const footerSeparator = renderSeparator(width);
|
|
14674
|
+
const footerLines = footerSeparator.split(`
|
|
14884
14675
|
`).length - 1;
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14676
|
+
const contentViewportHeight = Math.max(1, height - chromeLines - footerLines - 1);
|
|
14677
|
+
const visibleContent = applyScrollToContent(content, contentViewportHeight);
|
|
14678
|
+
const footerBlock = (visibleContent ? `
|
|
14888
14679
|
` : "") + footerSeparator + footer;
|
|
14889
|
-
|
|
14890
|
-
} finally {
|
|
14891
|
-
rendering = false;
|
|
14892
|
-
}
|
|
14680
|
+
stdout.write(moveCursor(1, 1) + eraseDown() + chrome + visibleContent + footerBlock);
|
|
14893
14681
|
}
|
|
14894
14682
|
const handleKey = (key) => {
|
|
14895
14683
|
if (vimState.isSearching) {
|
|
14896
14684
|
if (key === "\x1B" || key === "\x1B[3~") {
|
|
14897
14685
|
exitSearchMode();
|
|
14898
|
-
render2(
|
|
14686
|
+
render2();
|
|
14899
14687
|
return;
|
|
14900
14688
|
}
|
|
14901
14689
|
if (key === "\r" || key === `
|
|
@@ -14903,117 +14691,115 @@ Press Q to quit.`);
|
|
|
14903
14691
|
if (stats?.entries)
|
|
14904
14692
|
performSearch(vimState.searchQuery, stats.entries);
|
|
14905
14693
|
exitSearchMode();
|
|
14906
|
-
render2(
|
|
14694
|
+
render2();
|
|
14907
14695
|
return;
|
|
14908
14696
|
}
|
|
14909
14697
|
if (key === "\x7F" || key === "\b") {
|
|
14910
14698
|
vimState.searchQuery = vimState.searchQuery.slice(0, -1);
|
|
14911
14699
|
if (stats?.entries)
|
|
14912
14700
|
performSearch(vimState.searchQuery, stats.entries);
|
|
14913
|
-
render2(
|
|
14701
|
+
render2();
|
|
14914
14702
|
return;
|
|
14915
14703
|
}
|
|
14916
14704
|
if (key === "\x03") {
|
|
14917
14705
|
exitSearchMode();
|
|
14918
|
-
render2(
|
|
14706
|
+
render2();
|
|
14919
14707
|
return;
|
|
14920
14708
|
}
|
|
14921
14709
|
if (key.length === 1 && key.charCodeAt(0) >= 32 && key.charCodeAt(0) < 127) {
|
|
14922
14710
|
vimState.searchQuery += key;
|
|
14923
14711
|
if (stats?.entries)
|
|
14924
14712
|
performSearch(vimState.searchQuery, stats.entries);
|
|
14925
|
-
render2(
|
|
14713
|
+
render2();
|
|
14926
14714
|
}
|
|
14927
14715
|
return;
|
|
14928
14716
|
}
|
|
14929
14717
|
if (key === "q" || key === "Q" || key === "\x03")
|
|
14930
14718
|
return cleanup(0);
|
|
14931
14719
|
if (key === "r" || key === "R") {
|
|
14932
|
-
|
|
14933
|
-
render2(true);
|
|
14720
|
+
fetchData();
|
|
14934
14721
|
return;
|
|
14935
14722
|
}
|
|
14936
14723
|
if (key === "a" || key === "A") {
|
|
14937
14724
|
autoRefresh = !autoRefresh;
|
|
14938
|
-
|
|
14939
|
-
render2(false);
|
|
14725
|
+
render2();
|
|
14940
14726
|
return;
|
|
14941
14727
|
}
|
|
14942
14728
|
if (key === "j" || key === "\x1B[B" || key === "\x1BOB") {
|
|
14943
14729
|
scrollDown();
|
|
14944
|
-
render2(
|
|
14730
|
+
render2();
|
|
14945
14731
|
return;
|
|
14946
14732
|
}
|
|
14947
14733
|
if (key === "k" || key === "\x1B[A" || key === "\x1BOA") {
|
|
14948
14734
|
scrollUp();
|
|
14949
|
-
render2(
|
|
14735
|
+
render2();
|
|
14950
14736
|
return;
|
|
14951
14737
|
}
|
|
14952
14738
|
if (key === "l" || key === "\x1B[C" || key === "\x1BOC") {
|
|
14953
14739
|
const currentIdx = visibleTabs.findIndex((t) => t.id === currentTab);
|
|
14954
14740
|
currentTab = visibleTabs[(currentIdx + 1) % visibleTabs.length].id;
|
|
14955
14741
|
vimState.scrollPos = 0;
|
|
14956
|
-
render2(
|
|
14742
|
+
render2();
|
|
14957
14743
|
return;
|
|
14958
14744
|
}
|
|
14959
14745
|
if (key === "h" || key === "\x1B[D" || key === "\x1BOD") {
|
|
14960
14746
|
const currentIdx = visibleTabs.findIndex((t) => t.id === currentTab);
|
|
14961
14747
|
currentTab = visibleTabs[(currentIdx - 1 + visibleTabs.length) % visibleTabs.length].id;
|
|
14962
14748
|
vimState.scrollPos = 0;
|
|
14963
|
-
render2(
|
|
14749
|
+
render2();
|
|
14964
14750
|
return;
|
|
14965
14751
|
}
|
|
14966
14752
|
if (key === "g") {
|
|
14967
14753
|
if (vimState.lastKey === "g" && Date.now() - vimState.lastKeyTime < 300) {
|
|
14968
14754
|
scrollToBottom();
|
|
14969
14755
|
vimState.lastKey = "";
|
|
14970
|
-
render2(
|
|
14756
|
+
render2();
|
|
14971
14757
|
return;
|
|
14972
14758
|
}
|
|
14973
14759
|
vimState.lastKey = "g";
|
|
14974
14760
|
vimState.lastKeyTime = Date.now();
|
|
14975
14761
|
scrollToTop();
|
|
14976
|
-
render2(
|
|
14762
|
+
render2();
|
|
14977
14763
|
return;
|
|
14978
14764
|
}
|
|
14979
14765
|
if (key === "\x02") {
|
|
14980
14766
|
pageUp();
|
|
14981
|
-
render2(
|
|
14767
|
+
render2();
|
|
14982
14768
|
return;
|
|
14983
14769
|
}
|
|
14984
14770
|
if (key === "\x06") {
|
|
14985
14771
|
pageDown();
|
|
14986
|
-
render2(
|
|
14772
|
+
render2();
|
|
14987
14773
|
return;
|
|
14988
14774
|
}
|
|
14989
14775
|
if (key === "\x15") {
|
|
14990
14776
|
scrollUp(10);
|
|
14991
|
-
render2(
|
|
14777
|
+
render2();
|
|
14992
14778
|
return;
|
|
14993
14779
|
}
|
|
14994
14780
|
if (key === "\x04") {
|
|
14995
14781
|
scrollDown(10);
|
|
14996
|
-
render2(
|
|
14782
|
+
render2();
|
|
14997
14783
|
return;
|
|
14998
14784
|
}
|
|
14999
14785
|
if (key === "\x1B[H" || key === "\x1B[1~" || key === "\x1BOH") {
|
|
15000
14786
|
scrollToTop();
|
|
15001
|
-
render2(
|
|
14787
|
+
render2();
|
|
15002
14788
|
return;
|
|
15003
14789
|
}
|
|
15004
14790
|
if (key === "\x1B[F" || key === "\x1B[4~" || key === "\x1BOF") {
|
|
15005
14791
|
scrollToBottom();
|
|
15006
|
-
render2(
|
|
14792
|
+
render2();
|
|
15007
14793
|
return;
|
|
15008
14794
|
}
|
|
15009
14795
|
if (key === "/") {
|
|
15010
14796
|
startSearch(false);
|
|
15011
|
-
render2(
|
|
14797
|
+
render2();
|
|
15012
14798
|
return;
|
|
15013
14799
|
}
|
|
15014
14800
|
if (key === "?") {
|
|
15015
14801
|
startSearch(true);
|
|
15016
|
-
render2(
|
|
14802
|
+
render2();
|
|
15017
14803
|
return;
|
|
15018
14804
|
}
|
|
15019
14805
|
if (key === "n") {
|
|
@@ -15021,7 +14807,7 @@ Press Q to quit.`);
|
|
|
15021
14807
|
prevSearchResult(stats?.entries.length || 0);
|
|
15022
14808
|
else
|
|
15023
14809
|
nextSearchResult(stats?.entries.length || 0);
|
|
15024
|
-
render2(
|
|
14810
|
+
render2();
|
|
15025
14811
|
return;
|
|
15026
14812
|
}
|
|
15027
14813
|
if (key === "N") {
|
|
@@ -15029,28 +14815,27 @@ Press Q to quit.`);
|
|
|
15029
14815
|
nextSearchResult(stats?.entries.length || 0);
|
|
15030
14816
|
else
|
|
15031
14817
|
prevSearchResult(stats?.entries.length || 0);
|
|
15032
|
-
render2(
|
|
14818
|
+
render2();
|
|
15033
14819
|
return;
|
|
15034
14820
|
}
|
|
15035
14821
|
if (key === "\x1B") {
|
|
15036
14822
|
scrollToTop();
|
|
15037
|
-
render2(
|
|
14823
|
+
render2();
|
|
15038
14824
|
return;
|
|
15039
14825
|
}
|
|
15040
14826
|
const tab = visibleTabs.find((t) => t.key === key);
|
|
15041
14827
|
if (tab) {
|
|
15042
14828
|
currentTab = tab.id;
|
|
15043
14829
|
vimState.scrollPos = 0;
|
|
15044
|
-
render2(
|
|
14830
|
+
render2();
|
|
15045
14831
|
}
|
|
15046
14832
|
};
|
|
15047
14833
|
if (isInteractive)
|
|
15048
14834
|
stdin.on("data", handleKey);
|
|
15049
|
-
await
|
|
14835
|
+
await fetchData();
|
|
15050
14836
|
refreshInterval = setInterval(() => {
|
|
15051
14837
|
if (autoRefresh) {
|
|
15052
|
-
|
|
15053
|
-
render2(true);
|
|
14838
|
+
fetchData();
|
|
15054
14839
|
}
|
|
15055
14840
|
}, 2000);
|
|
15056
14841
|
}
|
|
@@ -15802,7 +15587,7 @@ async function isCocodInstalled(cocodPath) {
|
|
|
15802
15587
|
// package.json
|
|
15803
15588
|
var package_default = {
|
|
15804
15589
|
name: "routstrd",
|
|
15805
|
-
version: "0.3.
|
|
15590
|
+
version: "0.3.4",
|
|
15806
15591
|
module: "src/index.ts",
|
|
15807
15592
|
type: "module",
|
|
15808
15593
|
private: false,
|
|
@@ -15826,7 +15611,7 @@ var package_default = {
|
|
|
15826
15611
|
},
|
|
15827
15612
|
dependencies: {
|
|
15828
15613
|
"@cashu/cashu-ts": "^4.3.0",
|
|
15829
|
-
"@routstr/sdk": "^0.3.
|
|
15614
|
+
"@routstr/sdk": "^0.3.12",
|
|
15830
15615
|
"applesauce-core": "^5.1.0",
|
|
15831
15616
|
"applesauce-relay": "^5.1.0",
|
|
15832
15617
|
"applesauce-wallet-connect": "^6.0.0",
|