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/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 fetchUsage(limit = 1e4) {
13915
+ async function fetchUsageSummary() {
13915
13916
  try {
13916
13917
  const running = await isDaemonRunning();
13917
13918
  if (!running)
13918
13919
  return null;
13919
- const result = await callDaemon(`/usage?limit=${limit}`);
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 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 });
13924
+ const summary = result.output;
13925
+ if (!summary || typeof summary.totals !== "object")
13926
+ return null;
13930
13927
  return {
13931
- entries: visibleEntries,
13932
- totalEntries: visibleEntries.length,
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
- return new Date(timestamp).toISOString().split("T")[1]?.slice(0, 8) ?? "";
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 = getTotals(stats.entries);
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 = getModelStats(stats.entries);
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 = getClientStats(stats.entries);
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 todayStats = { date: formatDate(Date.now()), requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
14468
- for (const entry of stats.entries) {
14469
- if (entry.timestamp >= todayStart) {
14470
- todayStats.requests++;
14471
- todayStats.satsCost += entry.satsCost;
14472
- todayStats.promptTokens += entry.promptTokens;
14473
- todayStats.completionTokens += entry.completionTokens;
14474
- todayStats.totalTokens += entry.totalTokens;
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
- const days = Array.from(getDayStats(stats.entries).values()).slice(0, 7);
14485
- if (days.length > 1) {
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(hourly.values()).map((h) => h.satsCost), 1);
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 = hourly.get(h);
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 = hourly.get(i2);
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 = getModelStats(stats.entries);
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 = getProviderStats(stats.entries);
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 = getTotals(stats.entries);
14553
- const modelStats = getModelStats(stats.entries);
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 = getClientStats(stats.entries);
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
- for (const topClient of clientStats.slice(0, 3)) {
14641
- const modelMap = clientModelMap.get(topClient.client);
14642
- if (!modelMap)
14643
- continue;
14644
- const models = Array.from(modelMap.entries()).sort((a, b) => b[1].satsCost - a[1].satsCost).slice(0, 5);
14645
- clientModelLines.push(`${COLORS.bold}${topClient.client}${COLORS.reset} (${formatReqs(topClient.requests)} reqs, ${formatCost(topClient.satsCost)} sats)`);
14646
- for (const [model, data] of models) {
14647
- clientModelLines.push(` ${MODEL_COLORS[model] || MODEL_COLORS.default}${model.padEnd(18)}${COLORS.reset} ${formatNumber(data.tokens).padEnd(8)} tokens ${formatCost(data.satsCost)} sats`);
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 = getNpubStats(stats.entries, clients);
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
- for (const topNpub of npubStats.slice(0, 5)) {
14714
- const modelMap = npubModelMap.get(topNpub.npub);
14715
- if (!modelMap)
14716
- continue;
14717
- const models = Array.from(modelMap.entries()).sort((a, b) => b[1].satsCost - a[1].satsCost).slice(0, 5);
14718
- npubModelLines.push(`${COLORS.bold}${truncateNpub(topNpub.npub)}${COLORS.reset} (${formatReqs(topNpub.requests)} reqs, ${formatCost(topNpub.satsCost)} sats)`);
14719
- for (const [model, data] of models) {
14720
- npubModelLines.push(` ${MODEL_COLORS[model] || MODEL_COLORS.default}${model.padEnd(18)}${COLORS.reset} ${formatNumber(data.tokens).padEnd(8)} tokens ${formatCost(data.satsCost)} sats`);
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)} ${"TOKENS".padEnd(10)} ${"COST".padEnd(12)} ${"PROVIDER".padEnd(16)} ${"CLIENT".slice(0, clientCol)}${COLORS.reset}`);
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 = `${formatNumber(entry.totalTokens).padEnd(6)} (${formatNumber(entry.promptTokens)}+${formatNumber(entry.completionTokens)})`;
14747
- const cost = `${formatCost(entry.satsCost).padEnd(8)} sats`;
14748
- const provider = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "").slice(0, 16).padEnd(16);
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(10)} ${COLORS.green}${cost}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset}`);
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 fetchUsage(1e4);
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 callAuth("/clients");
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 callAuth("/clients/add", {
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 callAuth("/clients/delete", {
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.1",
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.9",
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 result = await callDaemon("/providers");
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.1",
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.9",
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