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/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 fetchUsage(limit = 1e4) {
13909
+ async function fetchUsageSummary() {
13915
13910
  try {
13916
- const running = await isDaemonRunning();
13917
- if (!running)
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 entries = result.output;
13923
- const visibleEntries = Array.isArray(entries) ? entries : [];
13924
- const totals = visibleEntries.reduce((acc, entry) => ({
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: visibleEntries,
13932
- totalEntries: visibleEntries.length,
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 = getTotals(stats.entries);
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 = getModelStats(stats.entries);
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 = getClientStats(stats.entries);
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 todayStats = { date: formatDate(Date.now()), requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
14472
- for (const entry of stats.entries) {
14473
- if (entry.timestamp >= todayStart) {
14474
- todayStats.requests++;
14475
- todayStats.satsCost += entry.satsCost;
14476
- todayStats.promptTokens += entry.promptTokens;
14477
- todayStats.completionTokens += entry.completionTokens;
14478
- todayStats.totalTokens += entry.totalTokens;
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
- const days = Array.from(getDayStats(stats.entries).values()).slice(0, 7);
14489
- if (days.length > 1) {
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(hourly.values()).map((h) => h.satsCost), 1);
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 = hourly.get(h);
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 = hourly.get(i2);
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 = getModelStats(stats.entries);
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 = getProviderStats(stats.entries);
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 = getTotals(stats.entries);
14557
- const modelStats = getModelStats(stats.entries);
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 = getClientStats(stats.entries);
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
- for (const topClient of clientStats.slice(0, 3)) {
14645
- const modelMap = clientModelMap.get(topClient.client);
14646
- if (!modelMap)
14647
- continue;
14648
- const models = Array.from(modelMap.entries()).sort((a, b) => b[1].satsCost - a[1].satsCost).slice(0, 5);
14649
- clientModelLines.push(`${COLORS.bold}${topClient.client}${COLORS.reset} (${formatReqs(topClient.requests)} reqs, ${formatCost(topClient.satsCost)} sats)`);
14650
- for (const [model, data] of models) {
14651
- clientModelLines.push(` ${MODEL_COLORS[model] || MODEL_COLORS.default}${model.padEnd(18)}${COLORS.reset} ${formatNumber(data.tokens).padEnd(8)} tokens ${formatCost(data.satsCost)} sats`);
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 = getNpubStats(stats.entries, clients);
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
- for (const topNpub of npubStats.slice(0, 5)) {
14718
- const modelMap = npubModelMap.get(topNpub.npub);
14719
- if (!modelMap)
14720
- continue;
14721
- const models = Array.from(modelMap.entries()).sort((a, b) => b[1].satsCost - a[1].satsCost).slice(0, 5);
14722
- npubModelLines.push(`${COLORS.bold}${truncateNpub(topNpub.npub)}${COLORS.reset} (${formatReqs(topNpub.requests)} reqs, ${formatCost(topNpub.satsCost)} sats)`);
14723
- for (const [model, data] of models) {
14724
- npubModelLines.push(` ${MODEL_COLORS[model] || MODEL_COLORS.default}${model.padEnd(18)}${COLORS.reset} ${formatNumber(data.tokens).padEnd(8)} tokens ${formatCost(data.satsCost)} sats`);
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 rendering = false;
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 render2(forceFetch = false) {
14851
- if (rendering)
14623
+ async function fetchData() {
14624
+ if (fetching)
14852
14625
  return;
14853
- rendering = true;
14626
+ fetching = true;
14854
14627
  try {
14855
- const width = getWidth();
14856
- const height = getHeight();
14857
- if (forceFetch || shouldUpdate) {
14858
- stats = await fetchUsage(1e4);
14859
- balance = await fetchBalance();
14860
- status = await fetchStatus();
14861
- clients = await fetchClients();
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
- if (!stats) {
14871
- stdout.write(moveCursor(1, 1) + eraseDown() + `${COLORS.red}Error: Could not fetch usage data.${COLORS.reset}
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
- return;
14876
- }
14877
- const content = renderTabContent(currentTab, stats, balance, status, width, clients);
14878
- 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}` : ""}`;
14879
- const chrome = renderHeader(currentTab, width, visibleTabs) + renderTabs(currentTab, visibleTabs) + renderSeparator(width) + renderSearchBar();
14880
- const chromeLines = chrome.split(`
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
- const footerSeparator = renderSeparator(width);
14883
- const footerLines = footerSeparator.split(`
14673
+ const footerSeparator = renderSeparator(width);
14674
+ const footerLines = footerSeparator.split(`
14884
14675
  `).length - 1;
14885
- const contentViewportHeight = Math.max(1, height - chromeLines - footerLines - 1);
14886
- const visibleContent = applyScrollToContent(content, contentViewportHeight);
14887
- const footerBlock = (visibleContent ? `
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
- stdout.write(moveCursor(1, 1) + eraseDown() + chrome + visibleContent + footerBlock);
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(false);
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(false);
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(false);
14701
+ render2();
14914
14702
  return;
14915
14703
  }
14916
14704
  if (key === "\x03") {
14917
14705
  exitSearchMode();
14918
- render2(false);
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(false);
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
- shouldUpdate = true;
14933
- render2(true);
14720
+ fetchData();
14934
14721
  return;
14935
14722
  }
14936
14723
  if (key === "a" || key === "A") {
14937
14724
  autoRefresh = !autoRefresh;
14938
- shouldUpdate = true;
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(false);
14730
+ render2();
14945
14731
  return;
14946
14732
  }
14947
14733
  if (key === "k" || key === "\x1B[A" || key === "\x1BOA") {
14948
14734
  scrollUp();
14949
- render2(false);
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(false);
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(false);
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(false);
14756
+ render2();
14971
14757
  return;
14972
14758
  }
14973
14759
  vimState.lastKey = "g";
14974
14760
  vimState.lastKeyTime = Date.now();
14975
14761
  scrollToTop();
14976
- render2(false);
14762
+ render2();
14977
14763
  return;
14978
14764
  }
14979
14765
  if (key === "\x02") {
14980
14766
  pageUp();
14981
- render2(false);
14767
+ render2();
14982
14768
  return;
14983
14769
  }
14984
14770
  if (key === "\x06") {
14985
14771
  pageDown();
14986
- render2(false);
14772
+ render2();
14987
14773
  return;
14988
14774
  }
14989
14775
  if (key === "\x15") {
14990
14776
  scrollUp(10);
14991
- render2(false);
14777
+ render2();
14992
14778
  return;
14993
14779
  }
14994
14780
  if (key === "\x04") {
14995
14781
  scrollDown(10);
14996
- render2(false);
14782
+ render2();
14997
14783
  return;
14998
14784
  }
14999
14785
  if (key === "\x1B[H" || key === "\x1B[1~" || key === "\x1BOH") {
15000
14786
  scrollToTop();
15001
- render2(false);
14787
+ render2();
15002
14788
  return;
15003
14789
  }
15004
14790
  if (key === "\x1B[F" || key === "\x1B[4~" || key === "\x1BOF") {
15005
14791
  scrollToBottom();
15006
- render2(false);
14792
+ render2();
15007
14793
  return;
15008
14794
  }
15009
14795
  if (key === "/") {
15010
14796
  startSearch(false);
15011
- render2(false);
14797
+ render2();
15012
14798
  return;
15013
14799
  }
15014
14800
  if (key === "?") {
15015
14801
  startSearch(true);
15016
- render2(false);
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(false);
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(false);
14818
+ render2();
15033
14819
  return;
15034
14820
  }
15035
14821
  if (key === "\x1B") {
15036
14822
  scrollToTop();
15037
- render2(false);
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(false);
14830
+ render2();
15045
14831
  }
15046
14832
  };
15047
14833
  if (isInteractive)
15048
14834
  stdin.on("data", handleKey);
15049
- await render2(true);
14835
+ await fetchData();
15050
14836
  refreshInterval = setInterval(() => {
15051
14837
  if (autoRefresh) {
15052
- shouldUpdate = true;
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.2",
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.10",
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",