@askalf/dario 5.4.3 → 5.4.5
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/render.js +9 -1
- package/dist/tui/tabs/config.js +25 -10
- package/dist/tui/tabs/hits.js +27 -18
- package/package.json +1 -1
package/dist/tui/render.js
CHANGED
|
@@ -143,7 +143,15 @@ export function truncate(text, maxWidth, ellipsis = '…') {
|
|
|
143
143
|
visible++;
|
|
144
144
|
i++;
|
|
145
145
|
}
|
|
146
|
-
|
|
146
|
+
// Flush SGR sequences from the clipped remainder. Without this, a cut
|
|
147
|
+
// that lands inside a dim()/fg() block drops the closing code, leaving
|
|
148
|
+
// the attribute open — it then bleeds into the following line, and past
|
|
149
|
+
// the frame entirely (clearScreen does not reset SGR). Replaying the
|
|
150
|
+
// remainder's own codes rather than appending a blanket reset keeps an
|
|
151
|
+
// enclosing inverse() wrapper intact; clipped openers arrive paired
|
|
152
|
+
// with their closers, so they render as a no-op.
|
|
153
|
+
const tail = text.slice(i).match(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g);
|
|
154
|
+
return out + ellipsis + (tail ? tail.join('') : '');
|
|
147
155
|
}
|
|
148
156
|
/** Pad `text` (right-aligned by default 'left' fills right side) to `width`. */
|
|
149
157
|
export function pad(text, width, align = 'left') {
|
package/dist/tui/tabs/config.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* change. The DarioConfig schema is the source of truth — adding a
|
|
20
20
|
* field there + a row here lights it up.
|
|
21
21
|
*/
|
|
22
|
-
import { fg, dim, brand, inverse, pad } from '../render.js';
|
|
22
|
+
import { fg, dim, brand, inverse, pad, truncate } from '../render.js';
|
|
23
23
|
import { CONFIG_SCHEMA_VERSION, defaultConfig, loadConfig, saveConfig, } from '../../config-file.js';
|
|
24
24
|
/**
|
|
25
25
|
* The visible field registry. Order = display order. New fields just
|
|
@@ -101,40 +101,55 @@ export const ConfigTab = {
|
|
|
101
101
|
const lines = [];
|
|
102
102
|
const w = dimv.cols;
|
|
103
103
|
const labelW = 26;
|
|
104
|
-
|
|
104
|
+
// Fixed value column so the hint keeps room on the same row. Every
|
|
105
|
+
// row is padded/truncated to exactly `w` below: the frame is drawn
|
|
106
|
+
// from the top with no scrollback, so a row that wraps costs a
|
|
107
|
+
// second physical line and pushes the panel head off-screen.
|
|
108
|
+
const valueW = Math.max(6, Math.min(16, w - labelW - 4));
|
|
109
|
+
// Rows that fit between the title (+blank) and the trailing blank
|
|
110
|
+
// + prompt/status pair. Window FIELDS around the selection so the
|
|
111
|
+
// list scrolls instead of overflowing the body region.
|
|
112
|
+
const listRows = Math.max(1, dimv.rows - 5);
|
|
113
|
+
const startIdx = Math.max(0, Math.min(state.selectedIdx - Math.floor(listRows / 3), FIELDS.length - listRows));
|
|
114
|
+
const endIdx = Math.min(startIdx + listRows, FIELDS.length);
|
|
105
115
|
const dirty = isDirty(state);
|
|
106
116
|
const title = dirty
|
|
107
117
|
? brand('Config') + dim(' — ') + fg('yellow', '● unsaved changes')
|
|
108
118
|
: brand('Config');
|
|
109
|
-
|
|
119
|
+
const counter = FIELDS.length > listRows
|
|
120
|
+
? dim(` ${state.selectedIdx + 1} / ${FIELDS.length}`)
|
|
121
|
+
: '';
|
|
122
|
+
lines.push(truncate(' ' + title + counter, w));
|
|
110
123
|
lines.push('');
|
|
111
|
-
for (let i =
|
|
124
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
112
125
|
const field = FIELDS[i];
|
|
113
126
|
const value = getByPath(state.config, field.path);
|
|
114
127
|
const orig = getByPath(state.snapshot, field.path);
|
|
115
128
|
const changed = !Object.is(value, orig);
|
|
116
129
|
const valueRender = renderValue(field, value, changed);
|
|
117
130
|
const hint = field.hint ? ' ' + dim('— ' + field.hint) : '';
|
|
118
|
-
const row = ' ' + pad(field.label + ':', labelW) + pad(valueRender, valueW) + hint;
|
|
131
|
+
const row = pad(truncate(' ' + pad(field.label + ':', labelW) + pad(valueRender, valueW) + hint, w), w);
|
|
119
132
|
lines.push(i === state.selectedIdx ? inverse(row) : row);
|
|
120
133
|
}
|
|
121
134
|
// ── Edit prompt or status line ─────────────────────────────
|
|
122
135
|
lines.push('');
|
|
123
136
|
if (state.editBuffer !== null) {
|
|
124
137
|
const f = FIELDS[state.selectedIdx];
|
|
125
|
-
lines.push(' ' + fg('cyan', `Edit ${f.label}:`) + ' ' + state.editBuffer + fg('cyan', '_'));
|
|
126
|
-
lines.push(' ' + dim('Enter to confirm · Esc to cancel'));
|
|
138
|
+
lines.push(truncate(' ' + fg('cyan', `Edit ${f.label}:`) + ' ' + state.editBuffer + fg('cyan', '_'), w));
|
|
139
|
+
lines.push(truncate(' ' + dim('Enter to confirm · Esc to cancel'), w));
|
|
127
140
|
}
|
|
128
141
|
else if (state.statusMessage) {
|
|
129
142
|
const color = state.statusKind === 'error' ? 'red'
|
|
130
143
|
: state.statusKind === 'success' ? 'green'
|
|
131
144
|
: 'cyan';
|
|
132
|
-
lines.push(' ' + fg(color, state.statusMessage));
|
|
145
|
+
lines.push(truncate(' ' + fg(color, state.statusMessage), w));
|
|
133
146
|
}
|
|
134
147
|
else {
|
|
135
|
-
lines.push(' ' + dim('↑↓ navigate · Enter edit · s save · d discard · r reload'));
|
|
148
|
+
lines.push(truncate(' ' + dim('↑↓ navigate · Enter edit · s save · d discard · r reload'), w));
|
|
136
149
|
}
|
|
137
|
-
|
|
150
|
+
// Hard floor for terminals too short for even one field + prompt:
|
|
151
|
+
// drop trailing rows rather than let the panel run past the body.
|
|
152
|
+
return lines.slice(0, Math.max(1, dimv.rows)).join('\n');
|
|
138
153
|
},
|
|
139
154
|
};
|
|
140
155
|
// ── Helpers ───────────────────────────────────────────────────
|
package/dist/tui/tabs/hits.js
CHANGED
|
@@ -112,17 +112,18 @@ export const HitsTab = {
|
|
|
112
112
|
const detailRows = 9;
|
|
113
113
|
const listRows = Math.max(3, totalRows - detailRows - 2);
|
|
114
114
|
if (state.buffer.length === 0) {
|
|
115
|
-
lines.push(' ' + brand('Hits') + dim(' — live request stream'));
|
|
115
|
+
lines.push(truncate(' ' + brand('Hits') + dim(' — live request stream'), w));
|
|
116
116
|
lines.push('');
|
|
117
117
|
if (state.connectionError) {
|
|
118
|
-
|
|
119
|
-
lines.push(' ' +
|
|
118
|
+
// The error text is upstream-supplied and unbounded.
|
|
119
|
+
lines.push(truncate(' ' + fg('red', `SSE error: ${state.connectionError}`), w));
|
|
120
|
+
lines.push(truncate(' ' + dim('Is `dario proxy` running? The stream reconnects automatically on the next mount.'), w));
|
|
120
121
|
}
|
|
121
122
|
else if (!state.subscribed) {
|
|
122
|
-
lines.push(' ' + dim('Connecting to /analytics/stream …'));
|
|
123
|
+
lines.push(truncate(' ' + dim('Connecting to /analytics/stream …'), w));
|
|
123
124
|
}
|
|
124
125
|
else {
|
|
125
|
-
lines.push(' ' + dim('Waiting for requests. Send one through dario to see it land here.'));
|
|
126
|
+
lines.push(truncate(' ' + dim('Waiting for requests. Send one through dario to see it land here.'), w));
|
|
126
127
|
}
|
|
127
128
|
return lines.join('\n');
|
|
128
129
|
}
|
|
@@ -136,26 +137,34 @@ export const HitsTab = {
|
|
|
136
137
|
const colTime = 9;
|
|
137
138
|
const colModel = 18;
|
|
138
139
|
const colIn = 8, colOut = 7, colLat = 7, colStatus = 5;
|
|
139
|
-
lines.push(' ' + brand('Hits') +
|
|
140
|
-
dim(` ${state.buffer.length} buffered · ${state.subscribed ? fg('green', 'live') : fg('yellow', 'disconnected')}`));
|
|
140
|
+
lines.push(truncate(' ' + brand('Hits') +
|
|
141
|
+
dim(` ${state.buffer.length} buffered · ${state.subscribed ? fg('green', 'live') : fg('yellow', 'disconnected')}`), w));
|
|
141
142
|
// ── Overage-halt banner (v4.1, dario#288) ──────────────────
|
|
142
143
|
// Pinned at the top so it's always visible while scrolling the buffer.
|
|
144
|
+
//
|
|
145
|
+
// Both lines are ordered most-actionable-first and kept short enough
|
|
146
|
+
// to fit 80 columns, because a banner that wraps costs a second
|
|
147
|
+
// physical row and pushes the panel head off the top of the screen —
|
|
148
|
+
// and it wraps exactly when the user needs to read it. The account is
|
|
149
|
+
// last on line 1 so a narrow terminal clips the least-critical field;
|
|
150
|
+
// the resume instructions on line 2 fit outright.
|
|
143
151
|
if (state.halt) {
|
|
144
152
|
const since = formatTimestamp(state.halt.since);
|
|
145
153
|
const cooldown = formatRemaining(state.halt.cooldownUntil - Date.now());
|
|
146
|
-
const line1 = ` ${fg('red', '⚠ HALTED')} ${state.halt.request.claim}
|
|
147
|
-
const line2 = ` ${dim('→
|
|
148
|
-
lines.push(line1);
|
|
149
|
-
lines.push(line2);
|
|
154
|
+
const line1 = ` ${fg('red', '⚠ HALTED')} ${state.halt.request.claim} · ${since} · ${shortenModel(state.halt.request.model)} · ${dim(state.halt.request.account)}`;
|
|
155
|
+
const line2 = ` ${dim('→ 503 until')} ${fg('cyan', 'R')} ${dim('or')} ${fg('cyan', 'dario resume')} ${dim(`· auto-resume in ${cooldown}`)}`;
|
|
156
|
+
lines.push(truncate(line1, w));
|
|
157
|
+
lines.push(truncate(line2, w));
|
|
150
158
|
}
|
|
151
159
|
lines.push('');
|
|
152
|
-
// Header row
|
|
153
|
-
|
|
160
|
+
// Header row — truncated to the same budget as the data rows below
|
|
161
|
+
// (`w - 2`) so the columns stay aligned when the terminal is narrow.
|
|
162
|
+
lines.push(truncate(' ' + dim(pad('time', colTime) +
|
|
154
163
|
pad('model', colModel) +
|
|
155
164
|
pad('in', colIn) +
|
|
156
165
|
pad('out', colOut) +
|
|
157
166
|
pad('lat', colLat) +
|
|
158
|
-
pad('st', colStatus)));
|
|
167
|
+
pad('st', colStatus)), w - 2));
|
|
159
168
|
for (let i = startIdx; i < endIdx; i++) {
|
|
160
169
|
const r = newestFirst[i];
|
|
161
170
|
// Flag any non-subscription billing red — the same condition the
|
|
@@ -185,16 +194,16 @@ export const HitsTab = {
|
|
|
185
194
|
}
|
|
186
195
|
// Scroll hint
|
|
187
196
|
if (newestFirst.length > listRows) {
|
|
188
|
-
lines.push(' ' + dim(`${state.selectedIdx + 1} / ${newestFirst.length} ` +
|
|
197
|
+
lines.push(truncate(' ' + dim(`${state.selectedIdx + 1} / ${newestFirst.length} ` +
|
|
189
198
|
(startIdx > 0 ? '↑ more ' : '') +
|
|
190
|
-
(endIdx < newestFirst.length ? '↓ more' : '')));
|
|
199
|
+
(endIdx < newestFirst.length ? '↓ more' : '')), w));
|
|
191
200
|
}
|
|
192
201
|
// Separator
|
|
193
202
|
lines.push(' ' + dim(BOX.horizontal.repeat(w - 2)));
|
|
194
203
|
// Detail pane
|
|
195
204
|
if (state.selectedIdx >= 0 && state.selectedIdx < newestFirst.length) {
|
|
196
205
|
const r = newestFirst[state.selectedIdx];
|
|
197
|
-
lines.push(' ' + brand('Selected') + dim(` ${formatTime(r.timestamp)}`));
|
|
206
|
+
lines.push(truncate(' ' + brand('Selected') + dim(` ${formatTime(r.timestamp)}`), w));
|
|
198
207
|
lines.push(' ' + renderKvRow('Account', r.account, w - 4));
|
|
199
208
|
lines.push(' ' + renderKvRow('Model', r.model, w - 4));
|
|
200
209
|
lines.push(' ' + renderKvRow('Billing bucket', billingBucketFromClaim(r.claim), w - 4));
|
|
@@ -205,7 +214,7 @@ export const HitsTab = {
|
|
|
205
214
|
}
|
|
206
215
|
else {
|
|
207
216
|
lines.push('');
|
|
208
|
-
lines.push(' ' + dim('Use ↑↓ to select a request for details.'));
|
|
217
|
+
lines.push(truncate(' ' + dim('Use ↑↓ to select a request for details.'), w));
|
|
209
218
|
}
|
|
210
219
|
return lines.join('\n');
|
|
211
220
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.5",
|
|
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": {
|