@askalf/dario
Advanced tools
@@ -22,3 +22,10 @@ /** | ||
| const dashWidth = Math.max(0, width - visibleWidth(left) - visibleWidth(right) - 2); | ||
| return BOX.topLeft + left + BOX.horizontal.repeat(dashWidth) + right + BOX.topRight; | ||
| const full = BOX.topLeft + left + BOX.horizontal.repeat(dashWidth) + right + BOX.topRight; | ||
| if (visibleWidth(full) <= width) | ||
| return full; | ||
| // Too narrow for brand + status: `dashWidth` clamps at 0 but both ends | ||
| // still render full-length, so the row would overflow and wrap. Drop | ||
| // the status (the proxy URL — also on the Status tab) before clipping | ||
| // the brand, which is this row's only identity. | ||
| return truncate(BOX.topLeft + left + BOX.topRight, width); | ||
| } | ||
@@ -25,0 +32,0 @@ /** |
@@ -184,5 +184,11 @@ /** | ||
| const empty = opts.empty ?? '░'; | ||
| const clamped = Math.max(0, Math.min(1, value)); | ||
| const cells = Math.round(clamped * width); | ||
| return filled.repeat(cells) + empty.repeat(width - cells); | ||
| // Callers derive the bar width by subtracting label columns from the | ||
| // terminal width, so a narrow terminal hands us a NEGATIVE width — | ||
| // String.repeat() throws RangeError on that, which took the whole TUI | ||
| // down rather than degrading. Collapse to an empty bar instead; the | ||
| // surrounding row still renders. | ||
| const w = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : 0; | ||
| const clamped = Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0; | ||
| const cells = Math.round(clamped * w); | ||
| return filled.repeat(cells) + empty.repeat(w - cells); | ||
| } | ||
@@ -189,0 +195,0 @@ /** |
@@ -27,3 +27,3 @@ /** | ||
| */ | ||
| import { fg, dim, brand, pad } from '../render.js'; | ||
| import { fg, dim, brand, pad, truncate } from '../render.js'; | ||
| import { renderKvRow } from '../layout.js'; | ||
@@ -61,17 +61,23 @@ export const AccountsTab = { | ||
| const w = dimv.cols; | ||
| lines.push(' ' + brand('Accounts')); | ||
| // Bound every row at the push site rather than at each call site. The | ||
| // column header (68 wide) and the disk-fallback path (which | ||
| // interpolates `~/.dario/accounts/<alias>.json`, unbounded by alias | ||
| // length) both overflowed; hand-auditing 15 separate pushes is how | ||
| // that got missed. | ||
| const push = (s) => lines.push(truncate(s, w)); | ||
| push(' ' + brand('Accounts')); | ||
| if (state.loading && state.accounts.length === 0) { | ||
| lines.push(''); | ||
| lines.push(' ' + dim('Loading accounts…')); | ||
| push(''); | ||
| push(' ' + dim('Loading accounts…')); | ||
| return lines.join('\n'); | ||
| } | ||
| if (state.accounts.length === 0) { | ||
| lines.push(''); | ||
| push(''); | ||
| if (state.source === 'single-account') { | ||
| lines.push(' ' + dim('Single-account mode (`dario login`) — no pool.')); | ||
| lines.push(' ' + 'Start a pool: ' + fg('cyan', 'dario accounts add <alias>')); | ||
| push(' ' + dim('Single-account mode (`dario login`) — no pool.')); | ||
| push(' ' + 'Start a pool: ' + fg('cyan', 'dario accounts add <alias>')); | ||
| } | ||
| else { | ||
| lines.push(' ' + dim('No accounts in the pool.')); | ||
| lines.push(' ' + 'Add one: ' + fg('cyan', 'dario accounts add <alias>')); | ||
| push(' ' + dim('No accounts in the pool.')); | ||
| push(' ' + 'Add one: ' + fg('cyan', 'dario accounts add <alias>')); | ||
| } | ||
@@ -84,9 +90,9 @@ return lines.join('\n'); | ||
| if (state.source === 'disk') { | ||
| lines.push(' ' + fg('yellow', 'proxy unreachable — showing on-disk accounts (may be stale)')); | ||
| push(' ' + fg('yellow', 'proxy unreachable — showing on-disk accounts (may be stale)')); | ||
| } | ||
| // Header row | ||
| lines.push(' ' + dim(hasUtil | ||
| push(' ' + dim(hasUtil | ||
| ? pad('alias', 20) + pad('expires', 14) + pad('util5h', 9) + pad('util7d', 9) + pad('status', 14) | ||
| : pad('alias', 20) + pad('expires', 16) + pad('source', 24))); | ||
| lines.push(' ' + dim('─'.repeat(Math.min(w - 4, 66)))); | ||
| push(' ' + dim('─'.repeat(Math.min(w - 4, 66)))); | ||
| for (const acc of state.accounts) { | ||
@@ -100,3 +106,3 @@ const aliasCol = pad(acc.alias, 20); | ||
| const statusFg = statusCol === 'auth-cooldown' ? fg('yellow', statusCol) : dim(statusCol); | ||
| lines.push(' ' + aliasCol + expiresCol + u5 + u7 + statusFg); | ||
| push(' ' + aliasCol + expiresCol + u5 + u7 + statusFg); | ||
| } | ||
@@ -106,13 +112,13 @@ else { | ||
| const sourceCol = '~/.dario/accounts/' + acc.alias + '.json'; | ||
| lines.push(' ' + aliasCol + expiresCol + dim(sourceCol)); | ||
| push(' ' + aliasCol + expiresCol + dim(sourceCol)); | ||
| } | ||
| } | ||
| lines.push(''); | ||
| lines.push(' ' + dim('Mutations via CLI:')); | ||
| lines.push(' ' + fg('cyan', 'dario accounts add <alias>')); | ||
| lines.push(' ' + fg('cyan', 'dario accounts remove <alias>')); | ||
| push(''); | ||
| push(' ' + dim('Mutations via CLI:')); | ||
| push(' ' + fg('cyan', 'dario accounts add <alias>')); | ||
| push(' ' + fg('cyan', 'dario accounts remove <alias>')); | ||
| // Refresh hint | ||
| lines.push(''); | ||
| lines.push(' ' + renderKvRow('', '', w - 2)); // spacer | ||
| lines.push(' ' + dim(`Press ${fg('cyan', 'r')} to refresh.`)); | ||
| push(''); | ||
| push(' ' + renderKvRow('', '', w - 2)); // spacer | ||
| push(' ' + dim(`Press ${fg('cyan', 'r')} to refresh.`)); | ||
| return lines.join('\n'); | ||
@@ -119,0 +125,0 @@ }, |
@@ -142,3 +142,4 @@ /** | ||
| // Red banner header — this is the loud surface when halted | ||
| lines.push(' ' + fg('red', '⚠ HALTED') + ' ' + dim(`${s.request.claim} detected ${formatAgo(s.since)} ago`)); | ||
| // formatAgo() already ends in "ago" — don't append a second one. | ||
| lines.push(' ' + fg('red', '⚠ HALTED') + ' ' + dim(`${s.request.claim} detected ${formatAgo(s.since)}`)); | ||
| lines.push(' ' + renderKvRow('Request', `${s.request.model} ${dim('account=' + s.request.account)}`, w - 4)); | ||
@@ -145,0 +146,0 @@ lines.push(' ' + renderKvRow('Cause', `representative-claim = ${fg('red', s.request.claim)}`, w - 4)); |
@@ -42,1 +42,10 @@ /** | ||
| export declare function startTuiApp(opts: TuiAppOpts): Promise<void>; | ||
| /** | ||
| * Compose one full frame: header, tab strip, rule, active tab body, | ||
| * footer. Exported for tests — the frame-height invariant (never more | ||
| * physical rows than the terminal has) is asserted against this. | ||
| */ | ||
| export declare function renderTui(state: TuiState, dim_: { | ||
| cols: number; | ||
| rows: number; | ||
| }, version: string, proxyUrl: string): string; |
+31
-6
@@ -13,3 +13,3 @@ /** | ||
| import { ProxyClient } from './proxy-client.js'; | ||
| import { fg, dim } from './render.js'; | ||
| import { fg, dim, truncate } from './render.js'; | ||
| import { renderFooter, renderHeader, renderTabStrip } from './layout.js'; | ||
@@ -184,3 +184,8 @@ import { StatusTab } from './tabs/status.js'; | ||
| // ── Rendering ─────────────────────────────────────────────────── | ||
| function renderTui(state, dim_, version, proxyUrl) { | ||
| /** | ||
| * Compose one full frame: header, tab strip, rule, active tab body, | ||
| * footer. Exported for tests — the frame-height invariant (never more | ||
| * physical rows than the terminal has) is asserted against this. | ||
| */ | ||
| export function renderTui(state, dim_, version, proxyUrl) { | ||
| const cols = dim_.cols; | ||
@@ -196,7 +201,25 @@ const rows = dim_.rows; | ||
| // Body — passed (cols, rows-5) so the tab knows it has rows 4..rows-2 | ||
| const bodyRows = rows - 5; | ||
| const bodyRows = Math.max(1, rows - 5); | ||
| const tab = TABS[state.activeTab]; | ||
| const slice = stateOf(state, state.activeTab); | ||
| const body = tab.render(slice, { cols, rows: bodyRows }); | ||
| out.push(body); | ||
| const bodyLineArr = body.split('\n'); | ||
| // A tab that renders past its row budget used to push the header and | ||
| // tab strip off the top of the alt-screen: App.redraw writes the frame | ||
| // from row 1 with no scrollback, so the overflow costs the chrome the | ||
| // user needs most — including which tab is even active. Clip the tail | ||
| // instead, and say how much was dropped so the loss isn't silent. | ||
| // | ||
| // This is a floor, not a substitute for a tab budgeting its own | ||
| // content: a tab that overflows here is still choosing what to drop by | ||
| // accident of ordering. Config and Hits size themselves; the rest do | ||
| // not yet. | ||
| if (bodyLineArr.length > bodyRows) { | ||
| const hidden = bodyLineArr.length - bodyRows + 1; | ||
| const note = ` … ${hidden} more row${hidden === 1 ? '' : 's'} — resize for the rest`; | ||
| out.push(bodyLineArr.slice(0, bodyRows - 1).concat(truncate(dim(note), cols)).join('\n')); | ||
| } | ||
| else { | ||
| out.push(body); | ||
| } | ||
| // Footer — fixed key hints (tab-cycling stays universal; per-tab | ||
@@ -212,3 +235,3 @@ // hints are inside each tab body to keep the global footer stable). | ||
| // depends on each tab's content). | ||
| const bodyLines = body.split('\n').length; | ||
| const bodyLines = bodyLineArr.length; | ||
| if (bodyLines < bodyRows) { | ||
@@ -220,3 +243,5 @@ out.push(''.padEnd(bodyRows - bodyLines, '\n')); | ||
| void fg; // silence unused if neither tab uses it through this module | ||
| return out.join('\n'); | ||
| // Absolute floor — whatever a tab did, never hand the terminal more | ||
| // physical rows than it has. | ||
| return out.join('\n').split('\n').slice(0, rows).join('\n'); | ||
| } |
+1
-1
| { | ||
| "name": "@askalf/dario", | ||
| "version": "5.4.6", | ||
| "version": "5.4.7", | ||
| "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1480529
0.2%28180
0.19%