@askalf/dario 5.4.6 → 5.4.7
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/tui/layout.js +8 -1
- package/dist/tui/render.js +9 -3
- package/dist/tui/tabs/accounts.js +27 -21
- package/dist/tui/tabs/status.js +2 -1
- package/dist/tui/tui-app.d.ts +9 -0
- package/dist/tui/tui-app.js +31 -6
- package/package.json +1 -1
package/dist/tui/layout.js
CHANGED
|
@@ -19,7 +19,14 @@ export function renderHeader(width, opts) {
|
|
|
19
19
|
const left = ` ${brand('dario')} v${opts.version} `;
|
|
20
20
|
const right = opts.status ? ` ${opts.status} ` : '';
|
|
21
21
|
const dashWidth = Math.max(0, width - visibleWidth(left) - visibleWidth(right) - 2);
|
|
22
|
-
|
|
22
|
+
const full = BOX.topLeft + left + BOX.horizontal.repeat(dashWidth) + right + BOX.topRight;
|
|
23
|
+
if (visibleWidth(full) <= width)
|
|
24
|
+
return full;
|
|
25
|
+
// Too narrow for brand + status: `dashWidth` clamps at 0 but both ends
|
|
26
|
+
// still render full-length, so the row would overflow and wrap. Drop
|
|
27
|
+
// the status (the proxy URL — also on the Status tab) before clipping
|
|
28
|
+
// the brand, which is this row's only identity.
|
|
29
|
+
return truncate(BOX.topLeft + left + BOX.topRight, width);
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
32
|
* Render the bottom footer with key-hint pairs. Wide gaps so it doesn't
|
package/dist/tui/render.js
CHANGED
|
@@ -181,9 +181,15 @@ export function pad(text, width, align = 'left') {
|
|
|
181
181
|
export function progressBar(value, width, opts = {}) {
|
|
182
182
|
const filled = opts.filled ?? '█';
|
|
183
183
|
const empty = opts.empty ?? '░';
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
// Callers derive the bar width by subtracting label columns from the
|
|
185
|
+
// terminal width, so a narrow terminal hands us a NEGATIVE width —
|
|
186
|
+
// String.repeat() throws RangeError on that, which took the whole TUI
|
|
187
|
+
// down rather than degrading. Collapse to an empty bar instead; the
|
|
188
|
+
// surrounding row still renders.
|
|
189
|
+
const w = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : 0;
|
|
190
|
+
const clamped = Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
|
|
191
|
+
const cells = Math.round(clamped * w);
|
|
192
|
+
return filled.repeat(cells) + empty.repeat(w - cells);
|
|
187
193
|
}
|
|
188
194
|
/**
|
|
189
195
|
* Box-drawing characters for borders. Set picked to render well on
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* To add: `dario accounts add <alias>`
|
|
25
25
|
* To remove: `dario accounts remove <alias>`
|
|
26
26
|
*/
|
|
27
|
-
import { fg, dim, brand, pad } from '../render.js';
|
|
27
|
+
import { fg, dim, brand, pad, truncate } from '../render.js';
|
|
28
28
|
import { renderKvRow } from '../layout.js';
|
|
29
29
|
export const AccountsTab = {
|
|
30
30
|
id: 'accounts',
|
|
@@ -57,21 +57,27 @@ export const AccountsTab = {
|
|
|
57
57
|
render(state, dimv) {
|
|
58
58
|
const lines = [];
|
|
59
59
|
const w = dimv.cols;
|
|
60
|
-
|
|
60
|
+
// Bound every row at the push site rather than at each call site. The
|
|
61
|
+
// column header (68 wide) and the disk-fallback path (which
|
|
62
|
+
// interpolates `~/.dario/accounts/<alias>.json`, unbounded by alias
|
|
63
|
+
// length) both overflowed; hand-auditing 15 separate pushes is how
|
|
64
|
+
// that got missed.
|
|
65
|
+
const push = (s) => lines.push(truncate(s, w));
|
|
66
|
+
push(' ' + brand('Accounts'));
|
|
61
67
|
if (state.loading && state.accounts.length === 0) {
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
push('');
|
|
69
|
+
push(' ' + dim('Loading accounts…'));
|
|
64
70
|
return lines.join('\n');
|
|
65
71
|
}
|
|
66
72
|
if (state.accounts.length === 0) {
|
|
67
|
-
|
|
73
|
+
push('');
|
|
68
74
|
if (state.source === 'single-account') {
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
push(' ' + dim('Single-account mode (`dario login`) — no pool.'));
|
|
76
|
+
push(' ' + 'Start a pool: ' + fg('cyan', 'dario accounts add <alias>'));
|
|
71
77
|
}
|
|
72
78
|
else {
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
push(' ' + dim('No accounts in the pool.'));
|
|
80
|
+
push(' ' + 'Add one: ' + fg('cyan', 'dario accounts add <alias>'));
|
|
75
81
|
}
|
|
76
82
|
return lines.join('\n');
|
|
77
83
|
}
|
|
@@ -79,13 +85,13 @@ export const AccountsTab = {
|
|
|
79
85
|
// doesn't, so show the util columns only when the pool populated them.
|
|
80
86
|
const hasUtil = state.accounts.some((a) => a.util5h !== undefined);
|
|
81
87
|
if (state.source === 'disk') {
|
|
82
|
-
|
|
88
|
+
push(' ' + fg('yellow', 'proxy unreachable — showing on-disk accounts (may be stale)'));
|
|
83
89
|
}
|
|
84
90
|
// Header row
|
|
85
|
-
|
|
91
|
+
push(' ' + dim(hasUtil
|
|
86
92
|
? pad('alias', 20) + pad('expires', 14) + pad('util5h', 9) + pad('util7d', 9) + pad('status', 14)
|
|
87
93
|
: pad('alias', 20) + pad('expires', 16) + pad('source', 24)));
|
|
88
|
-
|
|
94
|
+
push(' ' + dim('─'.repeat(Math.min(w - 4, 66))));
|
|
89
95
|
for (const acc of state.accounts) {
|
|
90
96
|
const aliasCol = pad(acc.alias, 20);
|
|
91
97
|
if (hasUtil) {
|
|
@@ -94,22 +100,22 @@ export const AccountsTab = {
|
|
|
94
100
|
const u7 = pad(acc.util7d !== undefined ? `${Math.round(acc.util7d * 100)}%` : '—', 9);
|
|
95
101
|
const statusCol = acc.status ?? '—';
|
|
96
102
|
const statusFg = statusCol === 'auth-cooldown' ? fg('yellow', statusCol) : dim(statusCol);
|
|
97
|
-
|
|
103
|
+
push(' ' + aliasCol + expiresCol + u5 + u7 + statusFg);
|
|
98
104
|
}
|
|
99
105
|
else {
|
|
100
106
|
const expiresCol = pad(formatExpiry(acc.expiresAt), 16);
|
|
101
107
|
const sourceCol = '~/.dario/accounts/' + acc.alias + '.json';
|
|
102
|
-
|
|
108
|
+
push(' ' + aliasCol + expiresCol + dim(sourceCol));
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
push('');
|
|
112
|
+
push(' ' + dim('Mutations via CLI:'));
|
|
113
|
+
push(' ' + fg('cyan', 'dario accounts add <alias>'));
|
|
114
|
+
push(' ' + fg('cyan', 'dario accounts remove <alias>'));
|
|
109
115
|
// Refresh hint
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
push('');
|
|
117
|
+
push(' ' + renderKvRow('', '', w - 2)); // spacer
|
|
118
|
+
push(' ' + dim(`Press ${fg('cyan', 'r')} to refresh.`));
|
|
113
119
|
return lines.join('\n');
|
|
114
120
|
},
|
|
115
121
|
};
|
package/dist/tui/tabs/status.js
CHANGED
|
@@ -139,7 +139,8 @@ export const StatusTab = {
|
|
|
139
139
|
const remainingMs = Math.max(0, s.cooldownUntil - Date.now());
|
|
140
140
|
const remaining = formatDuration(remainingMs);
|
|
141
141
|
// Red banner header — this is the loud surface when halted
|
|
142
|
-
|
|
142
|
+
// formatAgo() already ends in "ago" — don't append a second one.
|
|
143
|
+
lines.push(' ' + fg('red', '⚠ HALTED') + ' ' + dim(`${s.request.claim} detected ${formatAgo(s.since)}`));
|
|
143
144
|
lines.push(' ' + renderKvRow('Request', `${s.request.model} ${dim('account=' + s.request.account)}`, w - 4));
|
|
144
145
|
lines.push(' ' + renderKvRow('Cause', `representative-claim = ${fg('red', s.request.claim)}`, w - 4));
|
|
145
146
|
lines.push(' ' + renderKvRow('Auto-resume in', remaining === '0s' ? fg('yellow', 'now (cooldown elapsed)') : remaining, w - 4));
|
package/dist/tui/tui-app.d.ts
CHANGED
|
@@ -39,3 +39,12 @@ export interface TuiAppOpts {
|
|
|
39
39
|
version: string;
|
|
40
40
|
}
|
|
41
41
|
export declare function startTuiApp(opts: TuiAppOpts): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Compose one full frame: header, tab strip, rule, active tab body,
|
|
44
|
+
* footer. Exported for tests — the frame-height invariant (never more
|
|
45
|
+
* physical rows than the terminal has) is asserted against this.
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderTui(state: TuiState, dim_: {
|
|
48
|
+
cols: number;
|
|
49
|
+
rows: number;
|
|
50
|
+
}, version: string, proxyUrl: string): string;
|
package/dist/tui/tui-app.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { App } from './app.js';
|
|
12
12
|
import { ProxyClient } from './proxy-client.js';
|
|
13
|
-
import { fg, dim } from './render.js';
|
|
13
|
+
import { fg, dim, truncate } from './render.js';
|
|
14
14
|
import { renderFooter, renderHeader, renderTabStrip } from './layout.js';
|
|
15
15
|
import { StatusTab } from './tabs/status.js';
|
|
16
16
|
import { ConfigTab } from './tabs/config.js';
|
|
@@ -180,7 +180,12 @@ function withTabState(s, idx, sliceVal) {
|
|
|
180
180
|
return { ...s, [key]: sliceVal };
|
|
181
181
|
}
|
|
182
182
|
// ── Rendering ───────────────────────────────────────────────────
|
|
183
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Compose one full frame: header, tab strip, rule, active tab body,
|
|
185
|
+
* footer. Exported for tests — the frame-height invariant (never more
|
|
186
|
+
* physical rows than the terminal has) is asserted against this.
|
|
187
|
+
*/
|
|
188
|
+
export function renderTui(state, dim_, version, proxyUrl) {
|
|
184
189
|
const cols = dim_.cols;
|
|
185
190
|
const rows = dim_.rows;
|
|
186
191
|
const out = [];
|
|
@@ -191,11 +196,29 @@ function renderTui(state, dim_, version, proxyUrl) {
|
|
|
191
196
|
out.push(renderTabStrip(cols, tabLabels, state.activeTab));
|
|
192
197
|
out.push(dim('─'.repeat(cols)));
|
|
193
198
|
// Body — passed (cols, rows-5) so the tab knows it has rows 4..rows-2
|
|
194
|
-
const bodyRows = rows - 5;
|
|
199
|
+
const bodyRows = Math.max(1, rows - 5);
|
|
195
200
|
const tab = TABS[state.activeTab];
|
|
196
201
|
const slice = stateOf(state, state.activeTab);
|
|
197
202
|
const body = tab.render(slice, { cols, rows: bodyRows });
|
|
198
|
-
|
|
203
|
+
const bodyLineArr = body.split('\n');
|
|
204
|
+
// A tab that renders past its row budget used to push the header and
|
|
205
|
+
// tab strip off the top of the alt-screen: App.redraw writes the frame
|
|
206
|
+
// from row 1 with no scrollback, so the overflow costs the chrome the
|
|
207
|
+
// user needs most — including which tab is even active. Clip the tail
|
|
208
|
+
// instead, and say how much was dropped so the loss isn't silent.
|
|
209
|
+
//
|
|
210
|
+
// This is a floor, not a substitute for a tab budgeting its own
|
|
211
|
+
// content: a tab that overflows here is still choosing what to drop by
|
|
212
|
+
// accident of ordering. Config and Hits size themselves; the rest do
|
|
213
|
+
// not yet.
|
|
214
|
+
if (bodyLineArr.length > bodyRows) {
|
|
215
|
+
const hidden = bodyLineArr.length - bodyRows + 1;
|
|
216
|
+
const note = ` … ${hidden} more row${hidden === 1 ? '' : 's'} — resize for the rest`;
|
|
217
|
+
out.push(bodyLineArr.slice(0, bodyRows - 1).concat(truncate(dim(note), cols)).join('\n'));
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
out.push(body);
|
|
221
|
+
}
|
|
199
222
|
// Footer — fixed key hints (tab-cycling stays universal; per-tab
|
|
200
223
|
// hints are inside each tab body to keep the global footer stable).
|
|
201
224
|
const footerHints = [
|
|
@@ -206,12 +229,14 @@ function renderTui(state, dim_, version, proxyUrl) {
|
|
|
206
229
|
// Pad body to fill rows before the footer so the footer's row stays
|
|
207
230
|
// at the bottom (close to it — slight underflow OK; row count
|
|
208
231
|
// depends on each tab's content).
|
|
209
|
-
const bodyLines =
|
|
232
|
+
const bodyLines = bodyLineArr.length;
|
|
210
233
|
if (bodyLines < bodyRows) {
|
|
211
234
|
out.push(''.padEnd(bodyRows - bodyLines, '\n'));
|
|
212
235
|
}
|
|
213
236
|
out.push(renderFooter(cols, footerHints));
|
|
214
237
|
// Connect with newlines
|
|
215
238
|
void fg; // silence unused if neither tab uses it through this module
|
|
216
|
-
|
|
239
|
+
// Absolute floor — whatever a tab did, never hand the terminal more
|
|
240
|
+
// physical rows than it has.
|
|
241
|
+
return out.join('\n').split('\n').slice(0, rows).join('\n');
|
|
217
242
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.7",
|
|
4
4
|
"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
5
|
"type": "module",
|
|
6
6
|
"bin": {
|