routstrd 0.4.9 → 0.4.10
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/README.md +34 -5
- package/dist/daemon/index.js +15172 -21987
- package/dist/index.js +21341 -28132
- package/package.json +2 -2
- package/src/daemon/models.ts +13 -10
- package/src/tui/usage/app.ts +16 -2
- package/src/tui/usage/data.ts +110 -0
- package/src/tui/usage/render.ts +45 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routstrd",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@cashu/cashu-ts": "^4.3.0",
|
|
39
39
|
"@cashu/coco-core": "^1.0.1",
|
|
40
40
|
"@cashu/coco-sqlite-bun": "^1.0.1",
|
|
41
|
-
"@routstr/sdk": "^0.4.
|
|
41
|
+
"@routstr/sdk": "^0.4.5",
|
|
42
42
|
"@scure/bip39": "^2.2.0",
|
|
43
43
|
"applesauce-core": "^5.1.0",
|
|
44
44
|
"applesauce-relay": "^5.1.0",
|
package/src/daemon/models.ts
CHANGED
|
@@ -87,22 +87,25 @@ export function createModelService(
|
|
|
87
87
|
const providers = await modelManager.bootstrapProviders(false);
|
|
88
88
|
logger.log(`Bootstrapped ${providers.length} providers`);
|
|
89
89
|
|
|
90
|
-
//
|
|
91
|
-
//
|
|
90
|
+
// Mirror discovery into the store so `providers list` reports the same
|
|
91
|
+
// set that the model manager polls and routes to. The list is
|
|
92
|
+
// *replaced*, not merged: an add-only merge keeps URLs discovery no
|
|
93
|
+
// longer reports (e.g. after a bootstrap regression or a provider
|
|
94
|
+
// unpublishing) visible to the CLI and to per-model views while
|
|
95
|
+
// routing silently ignores them.
|
|
92
96
|
const {
|
|
93
97
|
baseUrlsList,
|
|
94
98
|
setBaseUrlsList,
|
|
95
99
|
setDisabledProviders,
|
|
96
100
|
} = store.getState();
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
setBaseUrlsList(merged);
|
|
101
|
+
const known = new Set(baseUrlsList);
|
|
102
|
+
const inSync =
|
|
103
|
+
known.size === providers.length &&
|
|
104
|
+
providers.every((url) => known.has(url));
|
|
105
|
+
if (!inSync) {
|
|
106
|
+
setBaseUrlsList(providers);
|
|
104
107
|
logger.log(
|
|
105
|
-
`Synced ${
|
|
108
|
+
`Synced ${providers.length} discovered provider(s) into store (was ${baseUrlsList.length})`,
|
|
106
109
|
);
|
|
107
110
|
}
|
|
108
111
|
|
package/src/tui/usage/app.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getVisibleTabs } from "./constants.ts";
|
|
2
2
|
import type { Tab } from "./types.ts";
|
|
3
|
-
import { fetchBalance, fetchClients, fetchStatus, fetchUsageSummary, hasAnyNpubs, isDaemonRunning, type BalanceInfo, type ClientInfo, type StatusInfo } from "./data.ts";
|
|
3
|
+
import { buildClientNaming, emptyClientNaming, fetchBalance, fetchClients, fetchNpubs, fetchStatus, fetchUsageSummary, hasAnyNpubs, isDaemonRunning, type BalanceInfo, type ClientInfo, type ClientNaming, type NpubEntry, type StatusInfo } from "./data.ts";
|
|
4
4
|
import {
|
|
5
5
|
applyScrollToContent,
|
|
6
6
|
exitSearchMode,
|
|
@@ -48,6 +48,8 @@ export async function runUsageTui(): Promise<void> {
|
|
|
48
48
|
let balance: BalanceInfo | null = null;
|
|
49
49
|
let status: StatusInfo | null = null;
|
|
50
50
|
let clients: ClientInfo[] = [];
|
|
51
|
+
let npubs: NpubEntry[] = [];
|
|
52
|
+
let naming: ClientNaming = emptyClientNaming();
|
|
51
53
|
let visibleTabs: Tab[] = getVisibleTabs(false);
|
|
52
54
|
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
|
53
55
|
let autoRefresh = true;
|
|
@@ -144,6 +146,18 @@ export async function runUsageTui(): Promise<void> {
|
|
|
144
146
|
currentTab = "clients";
|
|
145
147
|
vimState.scrollPos = 0;
|
|
146
148
|
}
|
|
149
|
+
|
|
150
|
+
// Names/roles live on the auth proxy, not the usage summary. Only
|
|
151
|
+
// fetch them when the Npubs tab is actually reachable, and keep the
|
|
152
|
+
// last good list if the endpoint is temporarily unreachable.
|
|
153
|
+
if (npubsVisible) {
|
|
154
|
+
const newNpubs = await fetchNpubs();
|
|
155
|
+
if (newNpubs.length > 0) npubs = newNpubs;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Owner info (for "Name (client-id)" labels in the Recent tab) comes
|
|
159
|
+
// from /clients + /npubs; both are empty in local mode.
|
|
160
|
+
naming = buildClientNaming(clients, npubs);
|
|
147
161
|
}
|
|
148
162
|
render();
|
|
149
163
|
} finally {
|
|
@@ -170,7 +184,7 @@ export async function runUsageTui(): Promise<void> {
|
|
|
170
184
|
return;
|
|
171
185
|
}
|
|
172
186
|
|
|
173
|
-
const content = renderTabContent(currentTab, stats, balance, status, width,
|
|
187
|
+
const content = renderTabContent(currentTab, stats, balance, status, width, naming);
|
|
174
188
|
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}` : ""}`;
|
|
175
189
|
const chrome = renderHeader(currentTab, width, visibleTabs, updateInfo ?? undefined) + renderTabs(currentTab, visibleTabs) + renderSeparator(width) + renderSearchBar();
|
|
176
190
|
const chromeLines = chrome.split("\n").length - 1;
|
package/src/tui/usage/data.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { UsageTrackingEntry } from "../../daemon/types.ts";
|
|
2
2
|
import {
|
|
3
|
+
callAuth,
|
|
3
4
|
callDaemon,
|
|
4
5
|
getDaemonBaseUrl,
|
|
5
6
|
isDaemonRunning,
|
|
@@ -145,3 +146,112 @@ export async function fetchClients(): Promise<ClientInfo[]> {
|
|
|
145
146
|
export function hasAnyNpubs(clients: ClientInfo[]): boolean {
|
|
146
147
|
return clients.some((c) => !!c.ownerNpub);
|
|
147
148
|
}
|
|
149
|
+
|
|
150
|
+
/** A configured npub as returned by the auth proxy (`/npubs`). */
|
|
151
|
+
export interface NpubEntry {
|
|
152
|
+
npub: string;
|
|
153
|
+
name: string | null;
|
|
154
|
+
role: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Fetch the configured npubs (with their display names and roles) from the
|
|
159
|
+
* auth proxy. Returns an empty list when the endpoint is unavailable — e.g. a
|
|
160
|
+
* local daemon that doesn't route `/npubs` — so the TUI degrades gracefully.
|
|
161
|
+
*/
|
|
162
|
+
export async function fetchNpubs(): Promise<NpubEntry[]> {
|
|
163
|
+
try {
|
|
164
|
+
const result = await callAuth("/npubs");
|
|
165
|
+
if (result.error) return [];
|
|
166
|
+
|
|
167
|
+
// Handle both wrapped { output: { npubs } } and direct { npubs } responses.
|
|
168
|
+
const direct = (result as { npubs?: NpubEntry[] }).npubs;
|
|
169
|
+
const wrapped = (result.output as { npubs?: NpubEntry[] } | undefined)?.npubs;
|
|
170
|
+
return direct ?? wrapped ?? [];
|
|
171
|
+
} catch {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Number of trailing npub characters the auth proxy folds into a client id. */
|
|
177
|
+
export const NPUB_SUFFIX_LENGTH = 7;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Lookup tables for turning a raw usage `client` id (e.g.
|
|
181
|
+
* `claude-code-a1b2c3d`) back into a human label (e.g. `Alice (claude-code)`).
|
|
182
|
+
*
|
|
183
|
+
* All maps are empty in local mode, so {@link resolveClientLabel} falls back to
|
|
184
|
+
* the raw id with no behaviour change there.
|
|
185
|
+
*/
|
|
186
|
+
export interface ClientNaming {
|
|
187
|
+
/** Configured npubs (names + roles) from the auth proxy. */
|
|
188
|
+
npubs: NpubEntry[];
|
|
189
|
+
/** bare clientId -> owner npub, from `GET /clients`. */
|
|
190
|
+
ownersByClientId: Map<string, string>;
|
|
191
|
+
/** npub -> display name (null when unset). */
|
|
192
|
+
npubNames: Map<string, string | null>;
|
|
193
|
+
/** trailing npub suffix -> npub. */
|
|
194
|
+
npubsBySuffix: Map<string, string>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function buildClientNaming(clients: ClientInfo[], npubs: NpubEntry[]): ClientNaming {
|
|
198
|
+
const ownersByClientId = new Map<string, string>();
|
|
199
|
+
for (const c of clients) {
|
|
200
|
+
if (c.ownerNpub) ownersByClientId.set(c.clientId, c.ownerNpub);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const npubNames = new Map<string, string | null>();
|
|
204
|
+
const npubsBySuffix = new Map<string, string>();
|
|
205
|
+
const remember = (npub: string) => npubsBySuffix.set(npub.slice(-NPUB_SUFFIX_LENGTH), npub);
|
|
206
|
+
|
|
207
|
+
for (const n of npubs) {
|
|
208
|
+
npubNames.set(n.npub, n.name ?? null);
|
|
209
|
+
remember(n.npub);
|
|
210
|
+
}
|
|
211
|
+
// `GET /clients` also carries owner npubs, which covers entries whose owner
|
|
212
|
+
// isn't present in the (possibly filtered) `/npubs` response.
|
|
213
|
+
for (const owner of ownersByClientId.values()) remember(owner);
|
|
214
|
+
|
|
215
|
+
return { npubs, ownersByClientId, npubNames, npubsBySuffix };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Configured display name for an npub, or null when unset/blank. */
|
|
219
|
+
function npubDisplayName(naming: ClientNaming, npub: string): string | null {
|
|
220
|
+
const name = naming.npubNames.get(npub)?.trim();
|
|
221
|
+
return name && name.length > 0 ? name : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** `Alice (claude-code)` when the owner has a name, else the bare client id. */
|
|
225
|
+
function formatClientLabel(naming: ClientNaming, bareId: string, ownerNpub: string): string {
|
|
226
|
+
const name = npubDisplayName(naming, ownerNpub);
|
|
227
|
+
return name ? `${name} (${bareId})` : bareId;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Render a usage entry's `client` value as `Name (client-id)`, where `Name` is
|
|
232
|
+
* the owner npub's display name and the `-<npub tail>` suffix is stripped.
|
|
233
|
+
* Falls back to the raw id when no owner/name can be resolved (local mode).
|
|
234
|
+
*/
|
|
235
|
+
export function resolveClientLabel(clientId: string | undefined, naming: ClientNaming): string {
|
|
236
|
+
const raw = clientId && clientId.length > 0 ? clientId : "unknown";
|
|
237
|
+
|
|
238
|
+
// Exact match: the raw id is a known bare clientId or its suffixed form.
|
|
239
|
+
for (const [bareId, ownerNpub] of naming.ownersByClientId) {
|
|
240
|
+
const suffixed = `${bareId}-${ownerNpub.slice(-NPUB_SUFFIX_LENGTH)}`;
|
|
241
|
+
if (raw !== bareId && raw !== suffixed) continue;
|
|
242
|
+
return formatClientLabel(naming, bareId, ownerNpub);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Fallback: strip a trailing `-<npub tail>` that matches a known npub.
|
|
246
|
+
for (const [suffix, npub] of naming.npubsBySuffix) {
|
|
247
|
+
const marker = `-${suffix}`;
|
|
248
|
+
if (raw.length <= marker.length || !raw.endsWith(marker)) continue;
|
|
249
|
+
return formatClientLabel(naming, raw.slice(0, -marker.length), npub);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return raw;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function emptyClientNaming(): ClientNaming {
|
|
256
|
+
return buildClientNaming([], []);
|
|
257
|
+
}
|
package/src/tui/usage/render.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { Tab } from "./types.ts";
|
|
|
3
3
|
import {
|
|
4
4
|
formatNumber,
|
|
5
5
|
formatTime,
|
|
6
|
-
|
|
6
|
+
resolveClientLabel,
|
|
7
|
+
type ClientNaming,
|
|
7
8
|
} from "./data.ts";
|
|
8
9
|
import { vimState } from "./state.ts";
|
|
9
10
|
import { stripAnsi } from "./terminal.ts";
|
|
@@ -503,20 +504,23 @@ export function renderClients(stats: UsageStats, width: number): string {
|
|
|
503
504
|
return output;
|
|
504
505
|
}
|
|
505
506
|
|
|
506
|
-
export function renderNpubs(stats: UsageStats,
|
|
507
|
+
export function renderNpubs(stats: UsageStats, naming: ClientNaming, width: number): string {
|
|
507
508
|
const npubStats = stats.summary.npubs;
|
|
508
509
|
if (npubStats.length === 0) return renderBox(["No npub data available"], width, "Npub Breakdown");
|
|
509
510
|
|
|
511
|
+
// Index configured npubs by their npub so usage rows can show names/roles.
|
|
512
|
+
const configured = new Map(naming.npubs.map((entry) => [entry.npub, entry]));
|
|
513
|
+
|
|
510
514
|
const totalCost = stats.totalSatsCost;
|
|
511
515
|
const maxCost = npubStats[0]!.satsCost;
|
|
512
516
|
const lines: string[] = [];
|
|
513
517
|
|
|
514
|
-
const col1 = 24; //
|
|
518
|
+
const col1 = 24; // Name (or truncated npub)
|
|
515
519
|
const col2 = 12; // Requests
|
|
516
520
|
const col3 = 24; // Cost
|
|
517
521
|
const col4 = 12; // Tokens
|
|
518
522
|
|
|
519
|
-
const hNpub = "
|
|
523
|
+
const hNpub = "Name".padEnd(col1);
|
|
520
524
|
const hReqs = "Requests".padEnd(col2);
|
|
521
525
|
const hCost = "Cost".padEnd(col3);
|
|
522
526
|
const hTok = "Tokens".padEnd(col4);
|
|
@@ -527,9 +531,11 @@ export function renderNpubs(stats: UsageStats, clients: ClientInfo[], width: num
|
|
|
527
531
|
for (const npub of npubStats) {
|
|
528
532
|
const pct = totalCost > 0 ? ((npub.satsCost / totalCost) * 100).toFixed(1) : "0.0";
|
|
529
533
|
const avgCostFormatted = formatCost(npub.requests > 0 ? npub.satsCost / npub.requests : 0);
|
|
530
|
-
const
|
|
534
|
+
const entry = configured.get(npub.npub);
|
|
535
|
+
const name = entry?.name?.trim();
|
|
536
|
+
const label = name || truncateNpub(npub.npub);
|
|
531
537
|
|
|
532
|
-
const dNpub =
|
|
538
|
+
const dNpub = label.slice(0, col1 - 1).padEnd(col1);
|
|
533
539
|
const dReqs = formatReqs(npub.requests).padEnd(col2);
|
|
534
540
|
const dCost = `${formatCost(npub.satsCost)} sats (${pct}%)`.padEnd(col3);
|
|
535
541
|
const dTok = formatNumber(npub.totalTokens).padEnd(col4);
|
|
@@ -541,8 +547,9 @@ export function renderNpubs(stats: UsageStats, clients: ClientInfo[], width: num
|
|
|
541
547
|
`${COLORS.green}${dCost}${COLORS.reset}` +
|
|
542
548
|
`${COLORS.dim}${dTok}${dAvg}${COLORS.reset}`
|
|
543
549
|
);
|
|
544
|
-
// Full npub on its own line for copy-ability
|
|
545
|
-
|
|
550
|
+
// Full npub (and role when known) on its own line for copy-ability.
|
|
551
|
+
const roleSuffix = entry?.role ? ` [${entry.role}]` : "";
|
|
552
|
+
lines.push(` ${COLORS.dim}${npub.npub}${roleSuffix}${COLORS.reset}`);
|
|
546
553
|
lines.push(` ${renderBarChart("", npub.satsCost, maxCost, width - 6, COLORS.magenta, Number(pct), "npub-detail")}`);
|
|
547
554
|
lines.push("");
|
|
548
555
|
}
|
|
@@ -555,7 +562,9 @@ export function renderNpubs(stats: UsageStats, clients: ClientInfo[], width: num
|
|
|
555
562
|
// Use pre-aggregated topModels from summary
|
|
556
563
|
for (const topNpub of stats.summary.npubs.slice(0, 5)) {
|
|
557
564
|
if (topNpub.topModels.length === 0) continue;
|
|
558
|
-
|
|
565
|
+
const name = configured.get(topNpub.npub)?.name?.trim();
|
|
566
|
+
const label = name || truncateNpub(topNpub.npub);
|
|
567
|
+
npubModelLines.push(`${COLORS.bold}${label}${COLORS.reset} (${formatReqs(topNpub.requests)} reqs, ${formatCost(topNpub.satsCost)} sats)`);
|
|
559
568
|
for (const m of topNpub.topModels) {
|
|
560
569
|
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`);
|
|
561
570
|
}
|
|
@@ -576,22 +585,38 @@ function truncateNpub(npub: string): string {
|
|
|
576
585
|
return npub.slice(0, 10) + "…" + npub.slice(-6);
|
|
577
586
|
}
|
|
578
587
|
|
|
579
|
-
export function renderRecent(stats: UsageStats, width: number): string {
|
|
588
|
+
export function renderRecent(stats: UsageStats, width: number, naming: ClientNaming): string {
|
|
580
589
|
const recentEntries = stats.entries.slice(0, 50);
|
|
581
590
|
if (recentEntries.length === 0) return renderBox(["No recent entries"], width, "Recent Requests");
|
|
582
591
|
|
|
583
|
-
const
|
|
592
|
+
const timeCol = 10;
|
|
593
|
+
const modelCol = 18;
|
|
584
594
|
const tokensCol = 18;
|
|
585
595
|
const costCol = 18;
|
|
586
|
-
|
|
596
|
+
|
|
597
|
+
// Remote mode: `Alice (claude-code)` — owner display name + the client id
|
|
598
|
+
// with the `-<npub tail>` suffix stripped. Local mode (no owner/name data)
|
|
599
|
+
// keeps the previous behaviour: the raw id in a fixed 14-col cell.
|
|
600
|
+
const hasOwnerInfo = naming.npubs.length > 0 || naming.ownersByClientId.size > 0;
|
|
601
|
+
const clientLabels = recentEntries.map((entry) =>
|
|
602
|
+
hasOwnerInfo ? resolveClientLabel(entry.client, naming) : entry.client || "unknown"
|
|
603
|
+
);
|
|
604
|
+
const maxLabelLen = clientLabels.reduce((max, label) => Math.max(max, label.length), 6);
|
|
605
|
+
const clientCol = hasOwnerInfo ? Math.min(32, Math.max(14, maxLabelLen)) : 14;
|
|
606
|
+
|
|
607
|
+
// Budget: time + model + tokens + cost + provider + client + 5 separators.
|
|
608
|
+
const innerWidth = Math.max(0, width - 4);
|
|
609
|
+
const providerCol = Math.max(12, innerWidth - timeCol - modelCol - tokensCol - costCol - 5 - clientCol);
|
|
610
|
+
|
|
587
611
|
const msatsToSats = (msats?: number) => typeof msats === "number" ? msats / 1000 : 0;
|
|
588
612
|
const lines: string[] = [];
|
|
589
|
-
lines.push(`${COLORS.bold}${"TIME".padEnd(
|
|
613
|
+
lines.push(`${COLORS.bold}${["TIME".padEnd(timeCol), "MODEL".padEnd(modelCol), "I/CR/CW/O".padEnd(tokensCol), "I/O/T in sats".padEnd(costCol), "BASE:PROVIDER".padEnd(providerCol), "CLIENT".padEnd(clientCol)].join(" ")}${COLORS.reset}`);
|
|
590
614
|
lines.push(COLORS.dim + "─".repeat(width - 4) + COLORS.reset);
|
|
591
615
|
|
|
592
|
-
for (
|
|
616
|
+
for (let i = 0; i < recentEntries.length; i++) {
|
|
617
|
+
const entry = recentEntries[i]!;
|
|
593
618
|
const time = formatTime(entry.timestamp).slice(0, 8);
|
|
594
|
-
const model = entry.modelId.slice(0,
|
|
619
|
+
const model = entry.modelId.slice(0, modelCol).padEnd(modelCol);
|
|
595
620
|
const tokens = [
|
|
596
621
|
entry.promptTokens,
|
|
597
622
|
entry.cacheReadInputTokens || 0,
|
|
@@ -606,16 +631,16 @@ export function renderRecent(stats: UsageStats, width: number): string {
|
|
|
606
631
|
].join("/");
|
|
607
632
|
const baseUrl = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "");
|
|
608
633
|
const provider = `${baseUrl}:${entry.provider || "unknown"}`.slice(0, providerCol).padEnd(providerCol);
|
|
609
|
-
const
|
|
634
|
+
const clientLabel = clientLabels[i]!.slice(0, clientCol).padEnd(clientCol);
|
|
610
635
|
const clientColor = CLIENT_COLORS[entry.client || "unknown"] || CLIENT_COLORS.default || COLORS.white;
|
|
611
636
|
const modelColor = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
|
|
612
|
-
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}${
|
|
637
|
+
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}${clientLabel}${COLORS.reset}`);
|
|
613
638
|
}
|
|
614
639
|
|
|
615
640
|
return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
|
|
616
641
|
}
|
|
617
642
|
|
|
618
|
-
export function renderTabContent(activeTab: TabId, stats: UsageStats, balance: BalanceInfo | null, status: StatusInfo | null, width: number,
|
|
643
|
+
export function renderTabContent(activeTab: TabId, stats: UsageStats, balance: BalanceInfo | null, status: StatusInfo | null, width: number, naming: ClientNaming): string {
|
|
619
644
|
switch (activeTab) {
|
|
620
645
|
case "overview": return renderOverview(stats, balance, status, width);
|
|
621
646
|
case "today": return renderToday(stats, width);
|
|
@@ -623,8 +648,8 @@ export function renderTabContent(activeTab: TabId, stats: UsageStats, balance: B
|
|
|
623
648
|
case "providers": return renderProviders(stats, width);
|
|
624
649
|
case "tokens": return renderTokens(stats, width);
|
|
625
650
|
case "clients": return renderClients(stats, width);
|
|
626
|
-
case "npubs": return renderNpubs(stats,
|
|
627
|
-
case "recent": return renderRecent(stats, width);
|
|
651
|
+
case "npubs": return renderNpubs(stats, naming, width);
|
|
652
|
+
case "recent": return renderRecent(stats, width, naming);
|
|
628
653
|
default: return "Unknown tab";
|
|
629
654
|
}
|
|
630
655
|
}
|