routstrd 0.3.1 → 0.3.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 +2364 -2769
- package/dist/index.js +102 -284
- package/package.json +2 -2
- package/say_hi.sh +32 -0
- package/src/cli.ts +15 -2
- package/src/daemon/http/usage-summary.test.ts +259 -0
- package/src/daemon/http/usage-summary.ts +361 -0
- package/src/daemon/models.ts +55 -0
- package/src/daemon/request-response-log-sink.ts +269 -0
- package/src/daemon/types.ts +6 -0
- package/src/tui/usage/app.ts +2 -2
- package/src/tui/usage/data.ts +15 -180
- package/src/tui/usage/render.ts +77 -112
- package/src/tui/usage/types.ts +4 -45
- package/src/utils/clients.ts +3 -4
- package/src/utils/config.ts +11 -0
- package/new-task.md +0 -60
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,
|
|
@@ -13911,42 +13912,34 @@ async function fetchBalance() {
|
|
|
13911
13912
|
return null;
|
|
13912
13913
|
}
|
|
13913
13914
|
}
|
|
13914
|
-
async function
|
|
13915
|
+
async function fetchUsageSummary() {
|
|
13915
13916
|
try {
|
|
13916
13917
|
const running = await isDaemonRunning();
|
|
13917
13918
|
if (!running)
|
|
13918
13919
|
return null;
|
|
13919
|
-
const
|
|
13920
|
+
const tz = new Date().getTimezoneOffset();
|
|
13921
|
+
const result = await callDaemon(`/usage/summary?tz=${tz}`);
|
|
13920
13922
|
if (result.error)
|
|
13921
13923
|
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 });
|
|
13924
|
+
const summary = result.output;
|
|
13925
|
+
if (!summary || typeof summary.totals !== "object")
|
|
13926
|
+
return null;
|
|
13930
13927
|
return {
|
|
13931
|
-
entries:
|
|
13932
|
-
totalEntries:
|
|
13933
|
-
totalSatsCost: totals.satsCost,
|
|
13934
|
-
recentSatsCost: totals.satsCost,
|
|
13935
|
-
limit
|
|
13928
|
+
entries: summary.recent,
|
|
13929
|
+
totalEntries: summary.totals.requests,
|
|
13930
|
+
totalSatsCost: summary.totals.satsCost,
|
|
13931
|
+
recentSatsCost: summary.totals.satsCost,
|
|
13932
|
+
limit: 50,
|
|
13933
|
+
summary
|
|
13936
13934
|
};
|
|
13937
13935
|
} catch {
|
|
13938
13936
|
return null;
|
|
13939
13937
|
}
|
|
13940
13938
|
}
|
|
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
|
-
return new Date(timestamp).toISOString().split("T")[0] ?? "";
|
|
13947
|
-
}
|
|
13948
13939
|
function formatTime(timestamp) {
|
|
13949
|
-
|
|
13940
|
+
const d = new Date(timestamp);
|
|
13941
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
13942
|
+
return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
13950
13943
|
}
|
|
13951
13944
|
function formatNumber(n) {
|
|
13952
13945
|
if (n >= 1e6)
|
|
@@ -13955,116 +13948,6 @@ function formatNumber(n) {
|
|
|
13955
13948
|
return (n / 1000).toFixed(1) + "K";
|
|
13956
13949
|
return n.toString();
|
|
13957
13950
|
}
|
|
13958
|
-
function getDayStats(entries) {
|
|
13959
|
-
const days = new Map;
|
|
13960
|
-
for (const entry of entries) {
|
|
13961
|
-
const date = formatDate(entry.timestamp);
|
|
13962
|
-
const existing = days.get(date) || { date, requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
13963
|
-
days.set(date, {
|
|
13964
|
-
...existing,
|
|
13965
|
-
requests: existing.requests + 1,
|
|
13966
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
13967
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
13968
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
13969
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
13970
|
-
});
|
|
13971
|
-
}
|
|
13972
|
-
return days;
|
|
13973
|
-
}
|
|
13974
|
-
function getHourlyToday(entries) {
|
|
13975
|
-
const todayStart = getTodayStart();
|
|
13976
|
-
const hours = new Map;
|
|
13977
|
-
for (const entry of entries) {
|
|
13978
|
-
if (entry.timestamp < todayStart)
|
|
13979
|
-
continue;
|
|
13980
|
-
const hour = new Date(entry.timestamp).getHours();
|
|
13981
|
-
const existing = hours.get(hour) || {
|
|
13982
|
-
date: formatDate(entry.timestamp),
|
|
13983
|
-
requests: 0,
|
|
13984
|
-
satsCost: 0,
|
|
13985
|
-
promptTokens: 0,
|
|
13986
|
-
completionTokens: 0,
|
|
13987
|
-
totalTokens: 0
|
|
13988
|
-
};
|
|
13989
|
-
hours.set(hour, {
|
|
13990
|
-
...existing,
|
|
13991
|
-
requests: existing.requests + 1,
|
|
13992
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
13993
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
13994
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
13995
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
13996
|
-
});
|
|
13997
|
-
}
|
|
13998
|
-
return hours;
|
|
13999
|
-
}
|
|
14000
|
-
function getModelStats(entries) {
|
|
14001
|
-
const models = new Map;
|
|
14002
|
-
for (const entry of entries) {
|
|
14003
|
-
const existing = models.get(entry.modelId) || {
|
|
14004
|
-
modelId: entry.modelId,
|
|
14005
|
-
requests: 0,
|
|
14006
|
-
satsCost: 0,
|
|
14007
|
-
promptTokens: 0,
|
|
14008
|
-
completionTokens: 0,
|
|
14009
|
-
totalTokens: 0
|
|
14010
|
-
};
|
|
14011
|
-
models.set(entry.modelId, {
|
|
14012
|
-
...existing,
|
|
14013
|
-
requests: existing.requests + 1,
|
|
14014
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14015
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14016
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14017
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14018
|
-
});
|
|
14019
|
-
}
|
|
14020
|
-
return Array.from(models.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14021
|
-
}
|
|
14022
|
-
function getProviderStats(entries) {
|
|
14023
|
-
const providers = new Map;
|
|
14024
|
-
for (const entry of entries) {
|
|
14025
|
-
const url = entry.baseUrl || "unknown";
|
|
14026
|
-
const existing = providers.get(url) || {
|
|
14027
|
-
baseUrl: url,
|
|
14028
|
-
requests: 0,
|
|
14029
|
-
satsCost: 0,
|
|
14030
|
-
promptTokens: 0,
|
|
14031
|
-
completionTokens: 0,
|
|
14032
|
-
totalTokens: 0
|
|
14033
|
-
};
|
|
14034
|
-
providers.set(url, {
|
|
14035
|
-
...existing,
|
|
14036
|
-
requests: existing.requests + 1,
|
|
14037
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14038
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14039
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14040
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14041
|
-
});
|
|
14042
|
-
}
|
|
14043
|
-
return Array.from(providers.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14044
|
-
}
|
|
14045
|
-
function getClientStats(entries) {
|
|
14046
|
-
const clients = new Map;
|
|
14047
|
-
for (const entry of entries) {
|
|
14048
|
-
const client = entry.client || "unknown";
|
|
14049
|
-
const existing = clients.get(client) || {
|
|
14050
|
-
client,
|
|
14051
|
-
requests: 0,
|
|
14052
|
-
satsCost: 0,
|
|
14053
|
-
promptTokens: 0,
|
|
14054
|
-
completionTokens: 0,
|
|
14055
|
-
totalTokens: 0
|
|
14056
|
-
};
|
|
14057
|
-
clients.set(client, {
|
|
14058
|
-
...existing,
|
|
14059
|
-
requests: existing.requests + 1,
|
|
14060
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14061
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14062
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14063
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14064
|
-
});
|
|
14065
|
-
}
|
|
14066
|
-
return Array.from(clients.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14067
|
-
}
|
|
14068
13951
|
async function fetchClients() {
|
|
14069
13952
|
try {
|
|
14070
13953
|
const running = await isDaemonRunning();
|
|
@@ -14086,48 +13969,6 @@ async function fetchClients() {
|
|
|
14086
13969
|
function hasAnyNpubs(clients) {
|
|
14087
13970
|
return clients.some((c) => !!c.ownerNpub);
|
|
14088
13971
|
}
|
|
14089
|
-
function getNpubStats(entries, clients) {
|
|
14090
|
-
const clientNpubMap = new Map;
|
|
14091
|
-
for (const c of clients) {
|
|
14092
|
-
if (c.ownerNpub) {
|
|
14093
|
-
clientNpubMap.set(c.clientId, c.ownerNpub);
|
|
14094
|
-
}
|
|
14095
|
-
}
|
|
14096
|
-
const npubs = new Map;
|
|
14097
|
-
for (const entry of entries) {
|
|
14098
|
-
const npub = clientNpubMap.get(entry.client || "");
|
|
14099
|
-
if (!npub)
|
|
14100
|
-
continue;
|
|
14101
|
-
const existing = npubs.get(npub) || {
|
|
14102
|
-
npub,
|
|
14103
|
-
requests: 0,
|
|
14104
|
-
satsCost: 0,
|
|
14105
|
-
promptTokens: 0,
|
|
14106
|
-
completionTokens: 0,
|
|
14107
|
-
totalTokens: 0
|
|
14108
|
-
};
|
|
14109
|
-
npubs.set(npub, {
|
|
14110
|
-
...existing,
|
|
14111
|
-
requests: existing.requests + 1,
|
|
14112
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14113
|
-
promptTokens: existing.promptTokens + entry.promptTokens,
|
|
14114
|
-
completionTokens: existing.completionTokens + entry.completionTokens,
|
|
14115
|
-
totalTokens: existing.totalTokens + entry.totalTokens
|
|
14116
|
-
});
|
|
14117
|
-
}
|
|
14118
|
-
return Array.from(npubs.values()).sort((a, b) => b.satsCost - a.satsCost);
|
|
14119
|
-
}
|
|
14120
|
-
function getTotals(entries) {
|
|
14121
|
-
if (!entries || !Array.isArray(entries)) {
|
|
14122
|
-
return { promptTokens: 0, completionTokens: 0, totalTokens: 0, satsCost: 0 };
|
|
14123
|
-
}
|
|
14124
|
-
return entries.reduce((acc, entry) => ({
|
|
14125
|
-
promptTokens: acc.promptTokens + entry.promptTokens,
|
|
14126
|
-
completionTokens: acc.completionTokens + entry.completionTokens,
|
|
14127
|
-
totalTokens: acc.totalTokens + entry.totalTokens,
|
|
14128
|
-
satsCost: acc.satsCost + entry.satsCost
|
|
14129
|
-
}), { promptTokens: 0, completionTokens: 0, totalTokens: 0, satsCost: 0 });
|
|
14130
|
-
}
|
|
14131
13972
|
var init_data = __esm(() => {
|
|
14132
13973
|
init_daemon_client();
|
|
14133
13974
|
});
|
|
@@ -14327,7 +14168,7 @@ function renderBarChart(label, value, maxValue, width, color, percentageValue, s
|
|
|
14327
14168
|
return `${paddedLabel} ${bar}${suffix}`;
|
|
14328
14169
|
}
|
|
14329
14170
|
function renderOverview(stats, balance, status, width) {
|
|
14330
|
-
const totals =
|
|
14171
|
+
const totals = stats.summary.totals;
|
|
14331
14172
|
const totalRequests = stats.totalEntries;
|
|
14332
14173
|
const totalVisibleCost = stats.totalSatsCost;
|
|
14333
14174
|
const avgCost = totalRequests > 0 ? totalVisibleCost / totalRequests : 0;
|
|
@@ -14436,7 +14277,7 @@ function renderOverview(stats, balance, status, width) {
|
|
|
14436
14277
|
output = renderBox(statusLines, width, "System Status") + `
|
|
14437
14278
|
` + output;
|
|
14438
14279
|
}
|
|
14439
|
-
const modelStats =
|
|
14280
|
+
const modelStats = stats.summary.models;
|
|
14440
14281
|
if (modelStats.length > 0) {
|
|
14441
14282
|
const maxCost = modelStats[0].satsCost;
|
|
14442
14283
|
const totalCost = Math.max(totalVisibleCost, 1);
|
|
@@ -14447,7 +14288,7 @@ function renderOverview(stats, balance, status, width) {
|
|
|
14447
14288
|
output += `
|
|
14448
14289
|
` + renderBox(modelLines, width, "Top Models by Cost");
|
|
14449
14290
|
}
|
|
14450
|
-
const clientStats =
|
|
14291
|
+
const clientStats = stats.summary.clients;
|
|
14451
14292
|
if (clientStats.length > 0) {
|
|
14452
14293
|
const maxCost = clientStats[0].satsCost;
|
|
14453
14294
|
const totalCost = Math.max(totalVisibleCost, 1);
|
|
@@ -14461,18 +14302,18 @@ function renderOverview(stats, balance, status, width) {
|
|
|
14461
14302
|
return output;
|
|
14462
14303
|
}
|
|
14463
14304
|
function renderToday(stats, width) {
|
|
14464
|
-
const hourly = getHourlyToday(stats.entries);
|
|
14465
|
-
const todayStart = getTodayStart();
|
|
14466
14305
|
const currentHour = new Date().getHours();
|
|
14467
|
-
const
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
}
|
|
14306
|
+
const _now = new Date;
|
|
14307
|
+
const todayDateStr = `${_now.getFullYear()}-${String(_now.getMonth() + 1).padStart(2, "0")}-${String(_now.getDate()).padStart(2, "0")}`;
|
|
14308
|
+
let todayStats;
|
|
14309
|
+
let recentDays;
|
|
14310
|
+
let hourlyMap;
|
|
14311
|
+
if (stats.summary) {
|
|
14312
|
+
const { days, hoursToday } = stats.summary;
|
|
14313
|
+
const todayDayStat = days[0]?.date === todayDateStr ? days[0] : undefined;
|
|
14314
|
+
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 };
|
|
14315
|
+
recentDays = days.filter((d) => d.date !== todayDateStr).slice(0, 6);
|
|
14316
|
+
hourlyMap = new Map(hoursToday.map((h) => [h.hour, { requests: h.requests, satsCost: h.satsCost }]));
|
|
14476
14317
|
}
|
|
14477
14318
|
const summaryLines = [
|
|
14478
14319
|
`${COLORS.bold}Date:${COLORS.reset} ${todayStats.date}`,
|
|
@@ -14481,18 +14322,17 @@ function renderToday(stats, width) {
|
|
|
14481
14322
|
`${COLORS.bold}Tokens:${COLORS.reset} ${formatNumber(todayStats.totalTokens)} (p: ${formatNumber(todayStats.promptTokens)} + c: ${formatNumber(todayStats.completionTokens)})`
|
|
14482
14323
|
];
|
|
14483
14324
|
let output = renderBox(summaryLines, width, "Today");
|
|
14484
|
-
|
|
14485
|
-
|
|
14486
|
-
const dayLines = days.slice(1).map((d) => `${d.date}: ${formatReqs(d.requests)} req, ${formatCost(d.satsCost)} sats, ${formatNumber(d.totalTokens)} tokens`);
|
|
14325
|
+
if (recentDays.length > 0) {
|
|
14326
|
+
const dayLines = recentDays.map((d) => `${d.date}: ${formatReqs(d.requests)} req, ${formatCost(d.satsCost)} sats, ${formatNumber(d.totalTokens)} tokens`);
|
|
14487
14327
|
output += `
|
|
14488
14328
|
` + renderBox(dayLines, width, "Recent Days");
|
|
14489
14329
|
}
|
|
14490
14330
|
const hourLines = [];
|
|
14491
|
-
const maxHourCost = Math.max(...Array.from(
|
|
14331
|
+
const maxHourCost = Math.max(...Array.from(hourlyMap.values()).map((h) => h.satsCost), 1);
|
|
14492
14332
|
const totalTodayCost = Math.max(todayStats.satsCost, 1);
|
|
14493
14333
|
const hourLabels = [];
|
|
14494
14334
|
for (let h = currentHour;h >= 0; h--) {
|
|
14495
|
-
const hStat =
|
|
14335
|
+
const hStat = hourlyMap.get(h);
|
|
14496
14336
|
const reqs = hStat?.requests || 0;
|
|
14497
14337
|
const cost = hStat?.satsCost || 0;
|
|
14498
14338
|
hourLabels.push(`${h.toString().padStart(2, "0")}:00 (${formatReqs(reqs)} req, ${formatCost(cost)} sats) `);
|
|
@@ -14500,7 +14340,7 @@ function renderToday(stats, width) {
|
|
|
14500
14340
|
const maxHourLabel = Math.max(...hourLabels.map((l) => l.length));
|
|
14501
14341
|
startBarSection("hourly", maxHourLabel);
|
|
14502
14342
|
for (let i2 = currentHour;i2 >= 0; i2--) {
|
|
14503
|
-
const hStat =
|
|
14343
|
+
const hStat = hourlyMap.get(i2);
|
|
14504
14344
|
const reqs = hStat?.requests || 0;
|
|
14505
14345
|
const cost = hStat?.satsCost || 0;
|
|
14506
14346
|
hourLines.push(renderBarChart(hourLabels[currentHour - i2], cost, maxHourCost, width - 4, i2 === currentHour ? COLORS.green : COLORS.cyan, cost / totalTodayCost * 100, "hourly"));
|
|
@@ -14511,7 +14351,7 @@ function renderToday(stats, width) {
|
|
|
14511
14351
|
return output;
|
|
14512
14352
|
}
|
|
14513
14353
|
function renderModels(stats, width) {
|
|
14514
|
-
const modelStats =
|
|
14354
|
+
const modelStats = stats.summary.models;
|
|
14515
14355
|
if (modelStats.length === 0)
|
|
14516
14356
|
return renderBox(["No model data available"], width, "Models");
|
|
14517
14357
|
const totalCost = stats.totalSatsCost;
|
|
@@ -14534,7 +14374,7 @@ function renderModels(stats, width) {
|
|
|
14534
14374
|
return renderBox(lines, width, "Model Breakdown");
|
|
14535
14375
|
}
|
|
14536
14376
|
function renderProviders(stats, width) {
|
|
14537
|
-
const providerStats =
|
|
14377
|
+
const providerStats = stats.summary.providers;
|
|
14538
14378
|
if (providerStats.length === 0)
|
|
14539
14379
|
return renderBox(["No provider data available"], width, "Providers");
|
|
14540
14380
|
const lines = [];
|
|
@@ -14549,8 +14389,8 @@ function renderProviders(stats, width) {
|
|
|
14549
14389
|
return renderBox(lines, width, "Provider Breakdown");
|
|
14550
14390
|
}
|
|
14551
14391
|
function renderTokens(stats, width) {
|
|
14552
|
-
const totals =
|
|
14553
|
-
const modelStats =
|
|
14392
|
+
const totals = stats.summary.totals;
|
|
14393
|
+
const modelStats = stats.summary.models;
|
|
14554
14394
|
const summaryLines = [
|
|
14555
14395
|
`${COLORS.bold}Total Prompt Tokens:${COLORS.reset} ${formatNumber(totals.promptTokens)}`,
|
|
14556
14396
|
`${COLORS.bold}Total Completion Tokens:${COLORS.reset} ${formatNumber(totals.completionTokens)}`,
|
|
@@ -14568,29 +14408,14 @@ function renderTokens(stats, width) {
|
|
|
14568
14408
|
output += `
|
|
14569
14409
|
` + renderBox(tokenLines, width, "Tokens by Model");
|
|
14570
14410
|
}
|
|
14571
|
-
const sizeBuckets =
|
|
14572
|
-
tiny: { min: 0, max: 1000, count: 0, cost: 0 },
|
|
14573
|
-
small: { min: 1000, max: 1e4, count: 0, cost: 0 },
|
|
14574
|
-
medium: { min: 1e4, max: 50000, count: 0, cost: 0 },
|
|
14575
|
-
large: { min: 50000, max: 1e5, count: 0, cost: 0 },
|
|
14576
|
-
huge: { min: 1e5, max: Infinity, count: 0, cost: 0 }
|
|
14577
|
-
};
|
|
14578
|
-
for (const entry of stats.entries) {
|
|
14579
|
-
for (const bucket of Object.values(sizeBuckets)) {
|
|
14580
|
-
if (entry.totalTokens >= bucket.min && entry.totalTokens < bucket.max) {
|
|
14581
|
-
bucket.count++;
|
|
14582
|
-
bucket.cost += entry.satsCost;
|
|
14583
|
-
break;
|
|
14584
|
-
}
|
|
14585
|
-
}
|
|
14586
|
-
}
|
|
14411
|
+
const sizeBuckets = stats.summary.sizeBuckets;
|
|
14587
14412
|
const sizeLines = Object.entries(sizeBuckets).map(([name, bucket]) => `${name.padEnd(6)}: ${formatReqs(bucket.count).padStart(5)} reqs, ${formatCost(bucket.cost)} sats`);
|
|
14588
14413
|
output += `
|
|
14589
14414
|
` + renderBox(sizeLines, width, "Request Size Distribution");
|
|
14590
14415
|
return output;
|
|
14591
14416
|
}
|
|
14592
14417
|
function renderClients(stats, width) {
|
|
14593
|
-
const clientStats =
|
|
14418
|
+
const clientStats = stats.summary.clients;
|
|
14594
14419
|
if (clientStats.length === 0)
|
|
14595
14420
|
return renderBox(["No client data available (API key auth not used)"], width, "Client Breakdown");
|
|
14596
14421
|
const totalCost = stats.totalSatsCost;
|
|
@@ -14622,31 +14447,17 @@ function renderClients(stats, width) {
|
|
|
14622
14447
|
}
|
|
14623
14448
|
endBarSection("client-detail");
|
|
14624
14449
|
let output = renderBox(lines, width, "Client Breakdown");
|
|
14625
|
-
const clientModelMap = new Map;
|
|
14626
|
-
for (const entry of stats.entries) {
|
|
14627
|
-
const client = entry.client || "unknown";
|
|
14628
|
-
const model = entry.modelId;
|
|
14629
|
-
if (!clientModelMap.has(client))
|
|
14630
|
-
clientModelMap.set(client, new Map);
|
|
14631
|
-
const modelMap = clientModelMap.get(client);
|
|
14632
|
-
const existing = modelMap.get(model) || { requests: 0, satsCost: 0, tokens: 0 };
|
|
14633
|
-
modelMap.set(model, {
|
|
14634
|
-
requests: existing.requests + 1,
|
|
14635
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14636
|
-
tokens: existing.tokens + entry.totalTokens
|
|
14637
|
-
});
|
|
14638
|
-
}
|
|
14639
14450
|
const clientModelLines = [];
|
|
14640
|
-
|
|
14641
|
-
const
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14451
|
+
if (stats.summary) {
|
|
14452
|
+
for (const topClient of stats.summary.clients.filter((c) => c.client !== "unknown").slice(0, 3)) {
|
|
14453
|
+
if (topClient.topModels.length === 0)
|
|
14454
|
+
continue;
|
|
14455
|
+
clientModelLines.push(`${COLORS.bold}${topClient.client}${COLORS.reset} (${formatReqs(topClient.requests)} reqs, ${formatCost(topClient.satsCost)} sats)`);
|
|
14456
|
+
for (const m of topClient.topModels) {
|
|
14457
|
+
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`);
|
|
14458
|
+
}
|
|
14459
|
+
clientModelLines.push("");
|
|
14648
14460
|
}
|
|
14649
|
-
clientModelLines.push("");
|
|
14650
14461
|
}
|
|
14651
14462
|
if (clientModelLines.length > 0) {
|
|
14652
14463
|
output += `
|
|
@@ -14655,7 +14466,7 @@ function renderClients(stats, width) {
|
|
|
14655
14466
|
return output;
|
|
14656
14467
|
}
|
|
14657
14468
|
function renderNpubs(stats, clients, width) {
|
|
14658
|
-
const npubStats =
|
|
14469
|
+
const npubStats = stats.summary.npubs;
|
|
14659
14470
|
if (npubStats.length === 0)
|
|
14660
14471
|
return renderBox(["No npub data available"], width, "Npub Breakdown");
|
|
14661
14472
|
const totalCost = stats.totalSatsCost;
|
|
@@ -14688,38 +14499,17 @@ function renderNpubs(stats, clients, width) {
|
|
|
14688
14499
|
}
|
|
14689
14500
|
endBarSection("npub-detail");
|
|
14690
14501
|
let output = renderBox(lines, width, "Npub Breakdown");
|
|
14691
|
-
const npubModelMap = new Map;
|
|
14692
|
-
const clientNpubMap = new Map;
|
|
14693
|
-
for (const c of clients) {
|
|
14694
|
-
if (c.ownerNpub)
|
|
14695
|
-
clientNpubMap.set(c.clientId, c.ownerNpub);
|
|
14696
|
-
}
|
|
14697
|
-
for (const entry of stats.entries) {
|
|
14698
|
-
const npub = clientNpubMap.get(entry.client || "");
|
|
14699
|
-
if (!npub)
|
|
14700
|
-
continue;
|
|
14701
|
-
const model = entry.modelId;
|
|
14702
|
-
if (!npubModelMap.has(npub))
|
|
14703
|
-
npubModelMap.set(npub, new Map);
|
|
14704
|
-
const modelMap = npubModelMap.get(npub);
|
|
14705
|
-
const existing = modelMap.get(model) || { requests: 0, satsCost: 0, tokens: 0 };
|
|
14706
|
-
modelMap.set(model, {
|
|
14707
|
-
requests: existing.requests + 1,
|
|
14708
|
-
satsCost: existing.satsCost + entry.satsCost,
|
|
14709
|
-
tokens: existing.tokens + entry.totalTokens
|
|
14710
|
-
});
|
|
14711
|
-
}
|
|
14712
14502
|
const npubModelLines = [];
|
|
14713
|
-
|
|
14714
|
-
const
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14503
|
+
if (stats.summary) {
|
|
14504
|
+
for (const topNpub of stats.summary.npubs.slice(0, 5)) {
|
|
14505
|
+
if (topNpub.topModels.length === 0)
|
|
14506
|
+
continue;
|
|
14507
|
+
npubModelLines.push(`${COLORS.bold}${truncateNpub(topNpub.npub)}${COLORS.reset} (${formatReqs(topNpub.requests)} reqs, ${formatCost(topNpub.satsCost)} sats)`);
|
|
14508
|
+
for (const m of topNpub.topModels) {
|
|
14509
|
+
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`);
|
|
14510
|
+
}
|
|
14511
|
+
npubModelLines.push("");
|
|
14721
14512
|
}
|
|
14722
|
-
npubModelLines.push("");
|
|
14723
14513
|
}
|
|
14724
14514
|
if (npubModelLines.length > 0) {
|
|
14725
14515
|
output += `
|
|
@@ -14737,19 +14527,34 @@ function renderRecent(stats, width) {
|
|
|
14737
14527
|
if (recentEntries.length === 0)
|
|
14738
14528
|
return renderBox(["No recent entries"], width, "Recent Requests");
|
|
14739
14529
|
const clientCol = 14;
|
|
14530
|
+
const tokensCol = 18;
|
|
14531
|
+
const costCol = 18;
|
|
14532
|
+
const providerCol = Math.max(16, width - 4 - 10 - 18 - tokensCol - costCol - clientCol - 5);
|
|
14533
|
+
const msatsToSats = (msats) => typeof msats === "number" ? msats / 1000 : 0;
|
|
14740
14534
|
const lines = [];
|
|
14741
|
-
lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"
|
|
14535
|
+
lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"I/CR/CW/O".padEnd(tokensCol)} ${"I/O/T in sats".padEnd(costCol)} ${"BASE:PROVIDER".padEnd(providerCol)} ${"CLIENT".slice(0, clientCol)}${COLORS.reset}`);
|
|
14742
14536
|
lines.push(COLORS.dim + "\u2500".repeat(width - 4) + COLORS.reset);
|
|
14743
14537
|
for (const entry of recentEntries) {
|
|
14744
14538
|
const time = formatTime(entry.timestamp).slice(0, 8);
|
|
14745
14539
|
const model = entry.modelId.slice(0, 18).padEnd(18);
|
|
14746
|
-
const tokens =
|
|
14747
|
-
|
|
14748
|
-
|
|
14540
|
+
const tokens = [
|
|
14541
|
+
entry.promptTokens,
|
|
14542
|
+
entry.cacheReadInputTokens || 0,
|
|
14543
|
+
entry.cacheCreationInputTokens || 0,
|
|
14544
|
+
entry.completionTokens
|
|
14545
|
+
].map(formatNumber).join("/");
|
|
14546
|
+
const totalSats = typeof entry.totalMsats === "number" ? entry.totalMsats / 1000 : entry.satsCost;
|
|
14547
|
+
const cost = [
|
|
14548
|
+
formatCost(msatsToSats(entry.inputMsats)),
|
|
14549
|
+
formatCost(msatsToSats(entry.outputMsats)),
|
|
14550
|
+
formatCost(totalSats)
|
|
14551
|
+
].join("/");
|
|
14552
|
+
const baseUrl = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "");
|
|
14553
|
+
const provider = `${baseUrl}:${entry.provider || "unknown"}`.slice(0, providerCol).padEnd(providerCol);
|
|
14749
14554
|
const clientName = (entry.client || "unknown").slice(0, clientCol - 1);
|
|
14750
14555
|
const clientColor = CLIENT_COLORS[entry.client || "unknown"] || CLIENT_COLORS.default || COLORS.white;
|
|
14751
14556
|
const modelColor = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
|
|
14752
|
-
lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(
|
|
14557
|
+
lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(tokensCol)} ${COLORS.green}${cost.padEnd(costCol)}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset}`);
|
|
14753
14558
|
}
|
|
14754
14559
|
return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
|
|
14755
14560
|
}
|
|
@@ -14836,7 +14641,7 @@ async function runUsageTui() {
|
|
|
14836
14641
|
const width = getWidth();
|
|
14837
14642
|
const height = getHeight();
|
|
14838
14643
|
if (forceFetch || shouldUpdate) {
|
|
14839
|
-
stats = await
|
|
14644
|
+
stats = await fetchUsageSummary();
|
|
14840
14645
|
balance = await fetchBalance();
|
|
14841
14646
|
status = await fetchStatus();
|
|
14842
14647
|
clients = await fetchClients();
|
|
@@ -15440,7 +15245,7 @@ async function runIntegrationsForClients(clientIds, config) {
|
|
|
15440
15245
|
|
|
15441
15246
|
// src/utils/clients.ts
|
|
15442
15247
|
async function getClientsList() {
|
|
15443
|
-
const result = await
|
|
15248
|
+
const result = await callDaemon("/clients");
|
|
15444
15249
|
const clients = result.output?.clients;
|
|
15445
15250
|
if (!clients) {
|
|
15446
15251
|
return [];
|
|
@@ -15469,7 +15274,7 @@ async function addDaemonClient(name) {
|
|
|
15469
15274
|
};
|
|
15470
15275
|
return { client, created: false };
|
|
15471
15276
|
}
|
|
15472
|
-
const result = await
|
|
15277
|
+
const result = await callDaemon("/clients/add", {
|
|
15473
15278
|
method: "POST",
|
|
15474
15279
|
body: { name, id: derivedId }
|
|
15475
15280
|
});
|
|
@@ -15507,7 +15312,7 @@ async function listClientsAction() {
|
|
|
15507
15312
|
}
|
|
15508
15313
|
async function deleteClientAction(id) {
|
|
15509
15314
|
await ensureDaemonRunning();
|
|
15510
|
-
const result = await
|
|
15315
|
+
const result = await callDaemon("/clients/delete", {
|
|
15511
15316
|
method: "POST",
|
|
15512
15317
|
body: { id }
|
|
15513
15318
|
});
|
|
@@ -15783,7 +15588,7 @@ async function isCocodInstalled(cocodPath) {
|
|
|
15783
15588
|
// package.json
|
|
15784
15589
|
var package_default = {
|
|
15785
15590
|
name: "routstrd",
|
|
15786
|
-
version: "0.3.
|
|
15591
|
+
version: "0.3.3",
|
|
15787
15592
|
module: "src/index.ts",
|
|
15788
15593
|
type: "module",
|
|
15789
15594
|
private: false,
|
|
@@ -15807,7 +15612,7 @@ var package_default = {
|
|
|
15807
15612
|
},
|
|
15808
15613
|
dependencies: {
|
|
15809
15614
|
"@cashu/cashu-ts": "^4.3.0",
|
|
15810
|
-
"@routstr/sdk": "^0.3.
|
|
15615
|
+
"@routstr/sdk": "^0.3.11",
|
|
15811
15616
|
"applesauce-core": "^5.1.0",
|
|
15812
15617
|
"applesauce-relay": "^5.1.0",
|
|
15813
15618
|
"applesauce-wallet-connect": "^6.0.0",
|
|
@@ -15876,6 +15681,18 @@ async function initDaemon() {
|
|
|
15876
15681
|
console.log(`Created config file: ${CONFIG_FILE}`);
|
|
15877
15682
|
}
|
|
15878
15683
|
const config = await loadConfig();
|
|
15684
|
+
if (!config.nsec) {
|
|
15685
|
+
const secretKey = generateSecretKey();
|
|
15686
|
+
const nsec = nip19_exports.nsecEncode(secretKey);
|
|
15687
|
+
const npub = npubFromSecretKey(secretKey);
|
|
15688
|
+
config.nsec = nsec;
|
|
15689
|
+
await Bun.write(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
15690
|
+
console.log(`
|
|
15691
|
+
A new Nostr identity has been generated for authentication.`);
|
|
15692
|
+
console.log(`Your npub: ${npub}`);
|
|
15693
|
+
console.log(`You can view it in the config file at: ${CONFIG_FILE}
|
|
15694
|
+
`);
|
|
15695
|
+
}
|
|
15879
15696
|
if (!await isCocodInstalled(config.cocodPath)) {
|
|
15880
15697
|
if (config.cocodPath) {
|
|
15881
15698
|
logger.error(`Configured cocod executable was not found: ${config.cocodPath}`);
|
|
@@ -16249,9 +16066,10 @@ program.command("usage").description("Show recent usage logs and total sats cost
|
|
|
16249
16066
|
});
|
|
16250
16067
|
});
|
|
16251
16068
|
var providersCmd = program.command("providers").description("List and manage providers");
|
|
16252
|
-
providersCmd.command("list").description("List all providers with their enabled/disabled status").action(async () => {
|
|
16069
|
+
providersCmd.command("list").description("List all providers with their enabled/disabled status").option("--refresh", "Force re-fetch all Nostr events and refresh models from all enabled providers", false).action(async (options) => {
|
|
16253
16070
|
await ensureDaemonRunning();
|
|
16254
|
-
const
|
|
16071
|
+
const query = options.refresh ? "/providers?refresh=true" : "/providers";
|
|
16072
|
+
const result = await callDaemon(query);
|
|
16255
16073
|
if (result.error) {
|
|
16256
16074
|
console.log(result.error);
|
|
16257
16075
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routstrd",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@cashu/cashu-ts": "^4.3.0",
|
|
27
|
-
"@routstr/sdk": "^0.3.
|
|
27
|
+
"@routstr/sdk": "^0.3.11",
|
|
28
28
|
"applesauce-core": "^5.1.0",
|
|
29
29
|
"applesauce-relay": "^5.1.0",
|
|
30
30
|
"applesauce-wallet-connect": "^6.0.0",
|
package/say_hi.sh
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
LANGUAGES=(
|
|
5
|
+
"Mandarin Chinese"
|
|
6
|
+
"Spanish"
|
|
7
|
+
"English"
|
|
8
|
+
"Hindi"
|
|
9
|
+
"Arabic"
|
|
10
|
+
"Portuguese"
|
|
11
|
+
"Bengali"
|
|
12
|
+
"Russian"
|
|
13
|
+
"Japanese"
|
|
14
|
+
"German"
|
|
15
|
+
"French"
|
|
16
|
+
"Italian"
|
|
17
|
+
"Korean"
|
|
18
|
+
"Turkish"
|
|
19
|
+
"Vietnamese"
|
|
20
|
+
"Tamil"
|
|
21
|
+
"Polish"
|
|
22
|
+
"Dutch"
|
|
23
|
+
"Swahili"
|
|
24
|
+
"Thai"
|
|
25
|
+
"Greek"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
for lang in "${LANGUAGES[@]}"; do
|
|
29
|
+
echo "=== $lang ==="
|
|
30
|
+
pi -p -c "in $lang"
|
|
31
|
+
echo
|
|
32
|
+
done
|