openlimiter 0.1.0 → 0.2.0
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/cli.d.ts +11 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +184 -31
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +112 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +253 -8
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +130 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +218 -0
- package/dist/render.js.map +1 -0
- package/dist/statusline.d.ts +91 -0
- package/dist/statusline.d.ts.map +1 -0
- package/dist/statusline.js +328 -0
- package/dist/statusline.js.map +1 -0
- package/package.json +4 -4
package/dist/render.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { failureSentence, floorFixed, freshness } from "@openlimiter/core";
|
|
2
|
+
export function pressureOf(percent) {
|
|
3
|
+
if (!Number.isFinite(percent))
|
|
4
|
+
return "none";
|
|
5
|
+
if (percent >= 90)
|
|
6
|
+
return "critical";
|
|
7
|
+
if (percent >= 80)
|
|
8
|
+
return "urgent";
|
|
9
|
+
if (percent >= 60)
|
|
10
|
+
return "watch";
|
|
11
|
+
return "healthy";
|
|
12
|
+
}
|
|
13
|
+
/** How many of the ten blocks a percentage lights, never rounding upward. */
|
|
14
|
+
export const BAR_SEGMENTS = 10;
|
|
15
|
+
/**
|
|
16
|
+
* The statusline's own density.
|
|
17
|
+
*
|
|
18
|
+
* Five blocks, not ten. A statusline shares one row with a model name, a
|
|
19
|
+
* branch, a directory and whatever else the host puts there, and six ten block
|
|
20
|
+
* bars would take the row on their own. Half the blocks at twice the price per
|
|
21
|
+
* block reads at a glance and still lands every reading in the right band,
|
|
22
|
+
* which is all a status row is for. The snapshot table keeps all ten, because
|
|
23
|
+
* a table is read deliberately and has the room.
|
|
24
|
+
*/
|
|
25
|
+
export const STATUSLINE_BAR_SEGMENTS = 5;
|
|
26
|
+
export function filledSegments(percent, segments = BAR_SEGMENTS) {
|
|
27
|
+
if (!Number.isFinite(percent))
|
|
28
|
+
return 0;
|
|
29
|
+
if (!Number.isInteger(segments) || segments < 1)
|
|
30
|
+
return 0;
|
|
31
|
+
const bounded = Math.min(100, Math.max(0, percent));
|
|
32
|
+
return Math.min(segments, Math.floor(bounded / (100 / segments)));
|
|
33
|
+
}
|
|
34
|
+
/* ------------------------------------------------------------------ colour */
|
|
35
|
+
const ESCAPE = "\u001b[";
|
|
36
|
+
const RESET = ESCAPE + "0m";
|
|
37
|
+
/**
|
|
38
|
+
* The eight colour codes every terminal has had since the 1970s.
|
|
39
|
+
*
|
|
40
|
+
* There is no orange among them, which is the whole difficulty with a four band
|
|
41
|
+
* scale: the band between yellow and red has no colour of its own to be drawn
|
|
42
|
+
* in on a plain terminal.
|
|
43
|
+
*/
|
|
44
|
+
const BAND_CODE = {
|
|
45
|
+
healthy: "32",
|
|
46
|
+
watch: "33",
|
|
47
|
+
urgent: "33",
|
|
48
|
+
critical: "31",
|
|
49
|
+
none: "90"
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Orange, from the 256 colour cube, for the terminals that have one.
|
|
53
|
+
*
|
|
54
|
+
* `38;5;208` selects entry 208 of the extended palette, which is the orange
|
|
55
|
+
* sitting between the yellow at 80 and the red at 90 rather than beside either.
|
|
56
|
+
*/
|
|
57
|
+
const ORANGE_256 = "38;5;208";
|
|
58
|
+
/** The one red every failure line is painted in. */
|
|
59
|
+
const RED = "31";
|
|
60
|
+
/**
|
|
61
|
+
* Whether this run may emit escape codes.
|
|
62
|
+
*
|
|
63
|
+
* Two things veto colour, and either one is enough: the NO_COLOR convention,
|
|
64
|
+
* which any value at all switches on, and standard output not being a
|
|
65
|
+
* terminal, which is what a pipe into a file or another program looks like.
|
|
66
|
+
*/
|
|
67
|
+
export function supportsColor(environment, isTty) {
|
|
68
|
+
if (environment["NO_COLOR"] !== undefined)
|
|
69
|
+
return false;
|
|
70
|
+
return isTty;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether this terminal has more than the eight standard colours.
|
|
74
|
+
*
|
|
75
|
+
* Two signals, either one enough. A `TERM` naming `256color` is the convention
|
|
76
|
+
* terminfo has used for decades, and a `COLORTERM` set to anything at all is
|
|
77
|
+
* what the terminals that went further than 256 announce themselves with. Both
|
|
78
|
+
* are claims the terminal makes about itself, not measurements, so this answers
|
|
79
|
+
* a question about what may safely be emitted and never about what is true.
|
|
80
|
+
*
|
|
81
|
+
* A terminal that says neither is not an error and is not a lesser rendering.
|
|
82
|
+
* It gets the yellow of the band below, so the reading still reads as pressure
|
|
83
|
+
* and only the distinction between 80 and 60 is lost. Information degrades; it
|
|
84
|
+
* never breaks, and no escape code is ever emitted that a terminal would print
|
|
85
|
+
* as literal characters.
|
|
86
|
+
*/
|
|
87
|
+
export function supports256Color(environment) {
|
|
88
|
+
const term = environment["TERM"];
|
|
89
|
+
if (term !== undefined && term.includes("256color"))
|
|
90
|
+
return true;
|
|
91
|
+
return environment["COLORTERM"] !== undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The one place a band becomes a colour.
|
|
95
|
+
*
|
|
96
|
+
* Both the ten block table bar and the five block statusline bar come through
|
|
97
|
+
* here, so a reading is the same colour wherever it is drawn, and the orange
|
|
98
|
+
* fallback is decided once rather than at each call site.
|
|
99
|
+
*/
|
|
100
|
+
export function bandCode(band, wide) {
|
|
101
|
+
if (band === "urgent" && wide)
|
|
102
|
+
return ORANGE_256;
|
|
103
|
+
return BAND_CODE[band];
|
|
104
|
+
}
|
|
105
|
+
function paint(text, code, color) {
|
|
106
|
+
return color ? ESCAPE + code + "m" + text + RESET : text;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The segmented bar.
|
|
110
|
+
*
|
|
111
|
+
* Ten blocks by default, the same ten the dashboard and the desktop window
|
|
112
|
+
* draw, and five when the statusline asks for its own density. With colour it
|
|
113
|
+
* is drawn in block characters and painted in the band's colour. Without colour
|
|
114
|
+
* it is the same positions in plain ASCII, because a terminal that reported no
|
|
115
|
+
* colour support may also be a log file, and a log file should carry no escape
|
|
116
|
+
* codes at all.
|
|
117
|
+
*
|
|
118
|
+
* The block count is the only thing the caller chooses. The band, and therefore
|
|
119
|
+
* the colour, comes from the reading alone, so the same percentage is the same
|
|
120
|
+
* colour at either density.
|
|
121
|
+
*
|
|
122
|
+
* `wide` is the terminal's own claim about its palette, and it defaults to the
|
|
123
|
+
* answer read off the environment so every caller gets the right one without
|
|
124
|
+
* carrying it. A test passes it explicitly, because a test that asks the
|
|
125
|
+
* machine it happens to be running on what colours it has is not a test.
|
|
126
|
+
*/
|
|
127
|
+
export function meterBar(percent, state, color, segments = BAR_SEGMENTS, wide = supports256Color(process.env)) {
|
|
128
|
+
const known = state !== "unknown";
|
|
129
|
+
const filled = known ? filledSegments(percent, segments) : 0;
|
|
130
|
+
const empty = Math.max(0, segments - filled);
|
|
131
|
+
const glyphs = color
|
|
132
|
+
? "█".repeat(filled) + "░".repeat(empty)
|
|
133
|
+
: "#".repeat(filled) + ".".repeat(empty);
|
|
134
|
+
const band = known ? pressureOf(percent) : "none";
|
|
135
|
+
return paint(glyphs, bandCode(band, wide), color);
|
|
136
|
+
}
|
|
137
|
+
/** A failure, in red, in our own fixed words. */
|
|
138
|
+
export function failureLine(provider, category, color) {
|
|
139
|
+
return paint(provider + " " + category + " " + failureSentence[category], RED, color);
|
|
140
|
+
}
|
|
141
|
+
export function failureLines(failures, color) {
|
|
142
|
+
return failures.map((failure) => failureLine(failure.provider, failure.category, color));
|
|
143
|
+
}
|
|
144
|
+
/* ------------------------------------------------------------------ fields */
|
|
145
|
+
const MINUTE = 60_000;
|
|
146
|
+
const HOUR = 3_600_000;
|
|
147
|
+
const DAY = 86_400_000;
|
|
148
|
+
/**
|
|
149
|
+
* Time left before the window resets, compact enough for a table column.
|
|
150
|
+
*
|
|
151
|
+
* Two units at most and no spaces, so every row stays one space separated
|
|
152
|
+
* record that a script can still split. A window with no reset says so rather
|
|
153
|
+
* than printing a zero.
|
|
154
|
+
*/
|
|
155
|
+
export function timeToReset(resetAt, now) {
|
|
156
|
+
if (resetAt === null)
|
|
157
|
+
return "NONE";
|
|
158
|
+
const target = Date.parse(resetAt);
|
|
159
|
+
const current = Date.parse(now);
|
|
160
|
+
if (!Number.isFinite(target) || !Number.isFinite(current))
|
|
161
|
+
return "NONE";
|
|
162
|
+
const remaining = target - current;
|
|
163
|
+
if (remaining <= 0)
|
|
164
|
+
return "PASSED";
|
|
165
|
+
if (remaining < MINUTE)
|
|
166
|
+
return "<1m";
|
|
167
|
+
if (remaining < HOUR)
|
|
168
|
+
return String(Math.floor(remaining / MINUTE)) + "m";
|
|
169
|
+
if (remaining < DAY) {
|
|
170
|
+
return String(Math.floor(remaining / HOUR)) + "h" +
|
|
171
|
+
String(Math.floor((remaining % HOUR) / MINUTE)) + "m";
|
|
172
|
+
}
|
|
173
|
+
return String(Math.floor(remaining / DAY)) + "d" +
|
|
174
|
+
String(Math.floor((remaining % DAY) / HOUR)) + "h";
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Money, when the reading carries it.
|
|
178
|
+
*
|
|
179
|
+
* The three fields travel together out of the normalizer, so checking one is
|
|
180
|
+
* enough, and the currency is a closed literal rather than provider text.
|
|
181
|
+
*/
|
|
182
|
+
export function amountField(snapshot) {
|
|
183
|
+
const used = snapshot.usedAmount;
|
|
184
|
+
const limit = snapshot.limitAmount;
|
|
185
|
+
if (used === undefined || limit === undefined || snapshot.currency === undefined) {
|
|
186
|
+
return "NONE";
|
|
187
|
+
}
|
|
188
|
+
return "$" + floorFixed(used, 2) + "/$" + floorFixed(limit, 2);
|
|
189
|
+
}
|
|
190
|
+
/* ------------------------------------------------------------------- table */
|
|
191
|
+
/**
|
|
192
|
+
* The snapshot table.
|
|
193
|
+
*
|
|
194
|
+
* Eight columns, always eight, separated by one space. AMOUNT and RESET read
|
|
195
|
+
* NONE where a provider has none rather than disappearing, because a table
|
|
196
|
+
* whose column count moves with its data cannot be parsed by anything.
|
|
197
|
+
*/
|
|
198
|
+
export const TABLE_HEADER = "PROVIDER METER BAR USAGE AMOUNT STATE RESET IN";
|
|
199
|
+
export function renderTable(snapshots, now, color) {
|
|
200
|
+
if (snapshots.length === 0)
|
|
201
|
+
return "No bounded quota data is available.";
|
|
202
|
+
const lines = [TABLE_HEADER];
|
|
203
|
+
for (const snapshot of snapshots) {
|
|
204
|
+
const state = freshness(snapshot.observedAt, snapshot.expiresAt, now);
|
|
205
|
+
lines.push([
|
|
206
|
+
snapshot.provider,
|
|
207
|
+
snapshot.meter,
|
|
208
|
+
meterBar(snapshot.value, state, color),
|
|
209
|
+
floorFixed(snapshot.value, 2) + snapshot.unit,
|
|
210
|
+
amountField(snapshot),
|
|
211
|
+
state,
|
|
212
|
+
snapshot.resetAt ?? "NONE",
|
|
213
|
+
timeToReset(snapshot.resetAt, now)
|
|
214
|
+
].join(" "));
|
|
215
|
+
}
|
|
216
|
+
return lines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type Advice, type ProviderCode, type Snapshot } from "@openlimiter/core";
|
|
2
|
+
import type { StatuslineColor, StatuslineConfig } from "./config.js";
|
|
3
|
+
export declare const DEFAULT_PROVIDER_ORDER: readonly ProviderCode[];
|
|
4
|
+
/**
|
|
5
|
+
* The order the configured ids ask for, completed by the built in order.
|
|
6
|
+
*
|
|
7
|
+
* An id this version does not know, and an id named twice, are both dropped
|
|
8
|
+
* rather than rejected. This runs on every redraw of somebody else's status
|
|
9
|
+
* row; a stale provider id left in a configuration file is not a reason to
|
|
10
|
+
* stop drawing the providers that are still there.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveProviderOrder(configured: readonly string[]): readonly ProviderCode[];
|
|
13
|
+
export type MeterClass = "session" | "daily" | "weekly" | "monthly" | "credits" | "other";
|
|
14
|
+
/**
|
|
15
|
+
* Which kind of window a reading measures.
|
|
16
|
+
*
|
|
17
|
+
* The window's own duration decides it wherever a connector states one,
|
|
18
|
+
* because a duration is a fact and a name is a label. The name table is the
|
|
19
|
+
* fallback for the connectors that state a window kind and no length.
|
|
20
|
+
*/
|
|
21
|
+
export declare function meterClass(snapshot: Snapshot): MeterClass;
|
|
22
|
+
export interface StatuslineCell {
|
|
23
|
+
/** The cell with no escape codes, which is what the width budget measures. */
|
|
24
|
+
readonly plain: string;
|
|
25
|
+
/** The cell as printed, painted when colour is allowed. */
|
|
26
|
+
readonly painted: string;
|
|
27
|
+
/** The reading behind it, which decides what survives when cells are shed. */
|
|
28
|
+
readonly percent: number;
|
|
29
|
+
}
|
|
30
|
+
/** Two spaces between cells, so a reader can tell one cell from the next. */
|
|
31
|
+
export declare const CELL_GAP = " ";
|
|
32
|
+
/**
|
|
33
|
+
* One cell per provider, or one per meter.
|
|
34
|
+
*
|
|
35
|
+
* The filter is the same one the advice policy uses, so the cells and the
|
|
36
|
+
* reason code leading them are always describing the same set of readings. A
|
|
37
|
+
* provider with several meters shows its worst one, because a status row exists
|
|
38
|
+
* to tell you what is about to stop you, and the meter closest to its cap is
|
|
39
|
+
* that. The full set is one `openlimiter snapshot` away, and `meters all` puts
|
|
40
|
+
* it back in the row for anyone who wants it there.
|
|
41
|
+
*
|
|
42
|
+
* `wide` is the terminal's 256 colour claim, handed straight to the shared bar
|
|
43
|
+
* so a statusline cell and a table row draw the same reading in the same band
|
|
44
|
+
* colour. Left off, the bar reads the environment for itself.
|
|
45
|
+
*/
|
|
46
|
+
export declare function statuslineCells(snapshots: readonly Snapshot[], now: string, order: readonly ProviderCode[], meters: StatuslineConfig["meters"], color: boolean, wide?: boolean): readonly StatuslineCell[];
|
|
47
|
+
/**
|
|
48
|
+
* Everything that is not a cell.
|
|
49
|
+
*
|
|
50
|
+
* The reason code, the recommendation and the unknown list are the same three
|
|
51
|
+
* fields the 0.1.0 line carried and they carry the same words, moved to the
|
|
52
|
+
* front so that the row can end wherever the cells end. They are never wrapped
|
|
53
|
+
* and never dropped: a row of bars with no verdict in front of it is a picture,
|
|
54
|
+
* not a status.
|
|
55
|
+
*/
|
|
56
|
+
export declare function statuslineHead(advice: Advice): string;
|
|
57
|
+
export interface StatuslineLayoutInput {
|
|
58
|
+
advice: Advice;
|
|
59
|
+
snapshots: readonly Snapshot[];
|
|
60
|
+
now: string;
|
|
61
|
+
config: StatuslineConfig;
|
|
62
|
+
color: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the host terminal claims a 256 colour palette.
|
|
65
|
+
*
|
|
66
|
+
* Only the orange band at 80 depends on it, and only a test ever states it.
|
|
67
|
+
* Left off, the bar asks the environment, which is the right answer for every
|
|
68
|
+
* caller that is a real terminal or a statusline host standing in for one.
|
|
69
|
+
*/
|
|
70
|
+
wide?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** What the statusline says when it has nothing bounded to say. */
|
|
73
|
+
export declare const STATUSLINE_UNKNOWN = "OpenLimiter UNKNOWN";
|
|
74
|
+
/**
|
|
75
|
+
* Draw the whole line, stacking it rather than truncating it.
|
|
76
|
+
*
|
|
77
|
+
* The budget is a budget, not a terminal measurement: nothing here asks the
|
|
78
|
+
* operating system how wide the window is, because a statusline host runs this
|
|
79
|
+
* command with no terminal attached and the answer would be an invention. The
|
|
80
|
+
* number in the configuration is the number that is honoured.
|
|
81
|
+
*/
|
|
82
|
+
export declare function renderStatuslineLayout(input: StatuslineLayoutInput): string;
|
|
83
|
+
/**
|
|
84
|
+
* Whether this render may paint.
|
|
85
|
+
*
|
|
86
|
+
* `NO_COLOR` wins over every setting, including `always`. It is the one
|
|
87
|
+
* convention a person can set once, for every tool on the machine, and a
|
|
88
|
+
* configuration file inside one tool is not the place to overrule it.
|
|
89
|
+
*/
|
|
90
|
+
export declare function statuslineColor(setting: StatuslineColor, environment: Readonly<Record<string, string | undefined>>, automatic: boolean): boolean;
|
|
91
|
+
//# sourceMappingURL=statusline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../src/statusline.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA2CrE,eAAO,MAAM,sBAAsB,EAAE,SAAS,YAAY,EAGzD,CAAC;AAIF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,SAAS,YAAY,EAAE,CAazB;AAID,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,OAAO,GACP,QAAQ,GACR,SAAS,GACT,SAAS,GACT,OAAO,CAAC;AAyCZ;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,UAAU,CAazD;AAID,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,6EAA6E;AAC7E,eAAO,MAAM,QAAQ,OAAO,CAAC;AA4C7B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE,OAAO,GACb,SAAS,cAAc,EAAE,CAwB3B;AAID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAQrD;AA8ED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CA2B3E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,eAAe,EACxB,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,EACzD,SAAS,EAAE,OAAO,GACjB,OAAO,CAKT"}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { PROVIDER_CODES, floorFixed, freshness } from "@openlimiter/core";
|
|
2
|
+
import { STATUSLINE_BAR_SEGMENTS, meterBar } from "./render.js";
|
|
3
|
+
/**
|
|
4
|
+
* Statusline layout, version two.
|
|
5
|
+
*
|
|
6
|
+
* Version one was one plain line of provider names and percentages, and every
|
|
7
|
+
* person who wanted to see pressure at a glance ended up drawing their own bar
|
|
8
|
+
* around it. This module draws it instead: one compact cell per provider,
|
|
9
|
+
* `NAME bar percent`, with the overall reason code leading the row, and a
|
|
10
|
+
* second row when the first one runs out of columns.
|
|
11
|
+
*
|
|
12
|
+
* The old line is still here. `bars false` returns it byte for byte through the
|
|
13
|
+
* adapter that has always produced it, because a format somebody wrote a script
|
|
14
|
+
* against is a promise, and the way to change a promise is to leave the old one
|
|
15
|
+
* reachable.
|
|
16
|
+
*
|
|
17
|
+
* Nothing in this file decides a number. Percentages, freshness, the reason
|
|
18
|
+
* code and the recommendation all arrive already decided by the engine; this is
|
|
19
|
+
* ordering, measuring and wrapping.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Which half of the row a provider belongs in.
|
|
23
|
+
*
|
|
24
|
+
* A subscription is a window that refills on a clock, so its percentage is
|
|
25
|
+
* about how much of today is left. A credit or API balance is money, so its
|
|
26
|
+
* percentage is about how much of the plan is left forever. Reading the two
|
|
27
|
+
* kinds in one undifferentiated row invites treating a spent balance like a
|
|
28
|
+
* window that will come back, so the subscriptions come first and the priced
|
|
29
|
+
* meters sit at the end where they read as a different kind of fact.
|
|
30
|
+
*
|
|
31
|
+
* The table is exhaustive over the provider codes on purpose: a provider added
|
|
32
|
+
* to the engine will not compile until somebody says which half it is in.
|
|
33
|
+
*/
|
|
34
|
+
const PROVIDER_CLASS = {
|
|
35
|
+
CLAUDE: "subscription",
|
|
36
|
+
CODEX: "subscription",
|
|
37
|
+
ANTIGRAVITY: "subscription",
|
|
38
|
+
OPENCODE: "subscription",
|
|
39
|
+
MANUAL: "subscription",
|
|
40
|
+
OPENROUTER: "api"
|
|
41
|
+
};
|
|
42
|
+
export const DEFAULT_PROVIDER_ORDER = [
|
|
43
|
+
...PROVIDER_CODES.filter((code) => PROVIDER_CLASS[code] === "subscription"),
|
|
44
|
+
...PROVIDER_CODES.filter((code) => PROVIDER_CLASS[code] === "api")
|
|
45
|
+
];
|
|
46
|
+
const providerCodes = new Set(PROVIDER_CODES);
|
|
47
|
+
/**
|
|
48
|
+
* The order the configured ids ask for, completed by the built in order.
|
|
49
|
+
*
|
|
50
|
+
* An id this version does not know, and an id named twice, are both dropped
|
|
51
|
+
* rather than rejected. This runs on every redraw of somebody else's status
|
|
52
|
+
* row; a stale provider id left in a configuration file is not a reason to
|
|
53
|
+
* stop drawing the providers that are still there.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveProviderOrder(configured) {
|
|
56
|
+
const listed = [];
|
|
57
|
+
for (const id of configured) {
|
|
58
|
+
const code = id.toUpperCase();
|
|
59
|
+
if (!providerCodes.has(code))
|
|
60
|
+
continue;
|
|
61
|
+
const provider = code;
|
|
62
|
+
if (listed.includes(provider))
|
|
63
|
+
continue;
|
|
64
|
+
listed.push(provider);
|
|
65
|
+
}
|
|
66
|
+
return [
|
|
67
|
+
...listed,
|
|
68
|
+
...DEFAULT_PROVIDER_ORDER.filter((code) => !listed.includes(code))
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
/** Shortest window first, then money, then anything unrecognised. */
|
|
72
|
+
const METER_CLASS_RANK = {
|
|
73
|
+
session: 0,
|
|
74
|
+
daily: 1,
|
|
75
|
+
weekly: 2,
|
|
76
|
+
monthly: 3,
|
|
77
|
+
credits: 4,
|
|
78
|
+
other: 5
|
|
79
|
+
};
|
|
80
|
+
const SIX_HOURS = 21_600;
|
|
81
|
+
const ONE_DAY = 86_400;
|
|
82
|
+
const SEVEN_DAYS = 604_800;
|
|
83
|
+
const THIRTY_ONE_DAYS = 2_678_400;
|
|
84
|
+
/**
|
|
85
|
+
* The classes a meter name maps to when its window states no duration.
|
|
86
|
+
*
|
|
87
|
+
* Only names this project already emits, plus the obvious synonyms a hand
|
|
88
|
+
* written manual document reaches for. Anything else is `other` and sorts
|
|
89
|
+
* last, which is the honest answer: an unrecognised name is not evidence of a
|
|
90
|
+
* short window.
|
|
91
|
+
*/
|
|
92
|
+
const METER_NAME_CLASS = {
|
|
93
|
+
SESSION: "session",
|
|
94
|
+
PRIMARY: "session",
|
|
95
|
+
FIVE_HOUR: "session",
|
|
96
|
+
HOURLY: "session",
|
|
97
|
+
DAILY: "daily",
|
|
98
|
+
DAY: "daily",
|
|
99
|
+
WEEKLY: "weekly",
|
|
100
|
+
WEEK: "weekly",
|
|
101
|
+
SEVEN_DAY: "weekly",
|
|
102
|
+
MONTHLY: "monthly",
|
|
103
|
+
MONTH: "monthly",
|
|
104
|
+
CREDITS: "credits",
|
|
105
|
+
BALANCE: "credits"
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Which kind of window a reading measures.
|
|
109
|
+
*
|
|
110
|
+
* The window's own duration decides it wherever a connector states one,
|
|
111
|
+
* because a duration is a fact and a name is a label. The name table is the
|
|
112
|
+
* fallback for the connectors that state a window kind and no length.
|
|
113
|
+
*/
|
|
114
|
+
export function meterClass(snapshot) {
|
|
115
|
+
if (snapshot.unit === "CREDITS" || snapshot.window.kind === "lifetime") {
|
|
116
|
+
return "credits";
|
|
117
|
+
}
|
|
118
|
+
const duration = snapshot.window.durationSeconds;
|
|
119
|
+
if (duration !== undefined) {
|
|
120
|
+
if (duration <= SIX_HOURS)
|
|
121
|
+
return "session";
|
|
122
|
+
if (duration <= ONE_DAY)
|
|
123
|
+
return "daily";
|
|
124
|
+
if (duration <= SEVEN_DAYS)
|
|
125
|
+
return "weekly";
|
|
126
|
+
if (duration <= THIRTY_ONE_DAYS)
|
|
127
|
+
return "monthly";
|
|
128
|
+
return "other";
|
|
129
|
+
}
|
|
130
|
+
return METER_NAME_CLASS[snapshot.meter] ?? "other";
|
|
131
|
+
}
|
|
132
|
+
/** Two spaces between cells, so a reader can tell one cell from the next. */
|
|
133
|
+
export const CELL_GAP = " ";
|
|
134
|
+
function buildCell(label, snapshot, state, color, wide) {
|
|
135
|
+
const percent = floorFixed(snapshot.value, 1) + "%";
|
|
136
|
+
const bar = (paint) => meterBar(snapshot.value, state, paint, STATUSLINE_BAR_SEGMENTS, wide);
|
|
137
|
+
return {
|
|
138
|
+
plain: label + " " + bar(false) + " " + percent,
|
|
139
|
+
painted: label + " " + bar(color) + " " + percent,
|
|
140
|
+
percent: snapshot.value
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function readingsFor(snapshots, provider, now) {
|
|
144
|
+
return snapshots
|
|
145
|
+
.filter((snapshot) => snapshot.provider === provider && snapshot.unit === "PERCENT")
|
|
146
|
+
.map((snapshot) => ({
|
|
147
|
+
snapshot,
|
|
148
|
+
state: freshness(snapshot.observedAt, snapshot.expiresAt, now)
|
|
149
|
+
}))
|
|
150
|
+
.filter((reading) => reading.state !== "unknown")
|
|
151
|
+
.sort((left, right) => {
|
|
152
|
+
const rank = METER_CLASS_RANK[meterClass(left.snapshot)] -
|
|
153
|
+
METER_CLASS_RANK[meterClass(right.snapshot)];
|
|
154
|
+
if (rank !== 0)
|
|
155
|
+
return rank;
|
|
156
|
+
return left.snapshot.meter < right.snapshot.meter ? -1 : 1;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* One cell per provider, or one per meter.
|
|
161
|
+
*
|
|
162
|
+
* The filter is the same one the advice policy uses, so the cells and the
|
|
163
|
+
* reason code leading them are always describing the same set of readings. A
|
|
164
|
+
* provider with several meters shows its worst one, because a status row exists
|
|
165
|
+
* to tell you what is about to stop you, and the meter closest to its cap is
|
|
166
|
+
* that. The full set is one `openlimiter snapshot` away, and `meters all` puts
|
|
167
|
+
* it back in the row for anyone who wants it there.
|
|
168
|
+
*
|
|
169
|
+
* `wide` is the terminal's 256 colour claim, handed straight to the shared bar
|
|
170
|
+
* so a statusline cell and a table row draw the same reading in the same band
|
|
171
|
+
* colour. Left off, the bar reads the environment for itself.
|
|
172
|
+
*/
|
|
173
|
+
export function statuslineCells(snapshots, now, order, meters, color, wide) {
|
|
174
|
+
const cells = [];
|
|
175
|
+
for (const provider of order) {
|
|
176
|
+
const readings = readingsFor(snapshots, provider, now);
|
|
177
|
+
if (readings.length === 0)
|
|
178
|
+
continue;
|
|
179
|
+
if (meters === "all") {
|
|
180
|
+
for (const reading of readings) {
|
|
181
|
+
cells.push(buildCell(provider + ":" + reading.snapshot.meter, reading.snapshot, reading.state, color, wide));
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
let worst = readings[0];
|
|
186
|
+
for (const reading of readings.slice(1)) {
|
|
187
|
+
if (reading.snapshot.value > worst.snapshot.value)
|
|
188
|
+
worst = reading;
|
|
189
|
+
}
|
|
190
|
+
cells.push(buildCell(provider, worst.snapshot, worst.state, color, wide));
|
|
191
|
+
}
|
|
192
|
+
return cells;
|
|
193
|
+
}
|
|
194
|
+
/* ------------------------------------------------------------------ layout */
|
|
195
|
+
/**
|
|
196
|
+
* Everything that is not a cell.
|
|
197
|
+
*
|
|
198
|
+
* The reason code, the recommendation and the unknown list are the same three
|
|
199
|
+
* fields the 0.1.0 line carried and they carry the same words, moved to the
|
|
200
|
+
* front so that the row can end wherever the cells end. They are never wrapped
|
|
201
|
+
* and never dropped: a row of bars with no verdict in front of it is a picture,
|
|
202
|
+
* not a status.
|
|
203
|
+
*/
|
|
204
|
+
export function statuslineHead(advice) {
|
|
205
|
+
const recommendation = advice.recommendation.code === "PREFER"
|
|
206
|
+
? " PREFER " + advice.recommendation.provider
|
|
207
|
+
: " NONE";
|
|
208
|
+
const unknown = advice.unknownProviders.length === 0
|
|
209
|
+
? ""
|
|
210
|
+
: " UNKNOWN " + advice.unknownProviders.join(",");
|
|
211
|
+
return "OpenLimiter " + advice.reason + recommendation + unknown;
|
|
212
|
+
}
|
|
213
|
+
function moreCell(dropped) {
|
|
214
|
+
const text = "+" + String(dropped) + " more";
|
|
215
|
+
return { plain: text, painted: text, percent: Number.NEGATIVE_INFINITY };
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Fill the rows left to right and report where it got to.
|
|
219
|
+
*
|
|
220
|
+
* A cell is placed whole or not at all, so the break always lands between two
|
|
221
|
+
* cells. Nothing is ever cut in half, because half a bar reads as a reading and
|
|
222
|
+
* is not one.
|
|
223
|
+
*/
|
|
224
|
+
function packed(head, cells, width, rows) {
|
|
225
|
+
const laid = [];
|
|
226
|
+
let index = 0;
|
|
227
|
+
for (let row = 0; row < rows; row += 1) {
|
|
228
|
+
const current = [];
|
|
229
|
+
let used = row === 0 ? head.length : 0;
|
|
230
|
+
while (index < cells.length) {
|
|
231
|
+
const cell = cells[index];
|
|
232
|
+
const needed = used === 0
|
|
233
|
+
? cell.plain.length
|
|
234
|
+
: used + CELL_GAP.length + cell.plain.length;
|
|
235
|
+
if (needed > width)
|
|
236
|
+
break;
|
|
237
|
+
used = needed;
|
|
238
|
+
current.push(cell);
|
|
239
|
+
index += 1;
|
|
240
|
+
}
|
|
241
|
+
laid.push(current);
|
|
242
|
+
}
|
|
243
|
+
return laid;
|
|
244
|
+
}
|
|
245
|
+
function fittedCount(rows) {
|
|
246
|
+
return rows.reduce((total, row) => total + row.length, 0);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* The worst cells, restored to display order.
|
|
250
|
+
*
|
|
251
|
+
* When the row cannot hold everything, what it drops has to be the readings
|
|
252
|
+
* that matter least, and the reading that matters least is the one furthest
|
|
253
|
+
* from its cap. Ties keep display order, so the choice is stable between
|
|
254
|
+
* redraws of the same data.
|
|
255
|
+
*/
|
|
256
|
+
function worstCells(cells, keep) {
|
|
257
|
+
return cells
|
|
258
|
+
.map((cell, index) => ({ cell, index }))
|
|
259
|
+
.sort((left, right) => right.cell.percent - left.cell.percent || left.index - right.index)
|
|
260
|
+
.slice(0, keep)
|
|
261
|
+
.sort((left, right) => left.index - right.index)
|
|
262
|
+
.map((entry) => entry.cell);
|
|
263
|
+
}
|
|
264
|
+
function paintRows(head, rows) {
|
|
265
|
+
const lines = [];
|
|
266
|
+
rows.forEach((row, index) => {
|
|
267
|
+
const body = row.map((cell) => cell.painted).join(CELL_GAP);
|
|
268
|
+
if (index === 0) {
|
|
269
|
+
lines.push(body === "" ? head : head + CELL_GAP + body);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (body !== "")
|
|
273
|
+
lines.push(body);
|
|
274
|
+
});
|
|
275
|
+
return lines.join("\n");
|
|
276
|
+
}
|
|
277
|
+
/** What the statusline says when it has nothing bounded to say. */
|
|
278
|
+
export const STATUSLINE_UNKNOWN = "OpenLimiter UNKNOWN";
|
|
279
|
+
/**
|
|
280
|
+
* Draw the whole line, stacking it rather than truncating it.
|
|
281
|
+
*
|
|
282
|
+
* The budget is a budget, not a terminal measurement: nothing here asks the
|
|
283
|
+
* operating system how wide the window is, because a statusline host runs this
|
|
284
|
+
* command with no terminal attached and the answer would be an invention. The
|
|
285
|
+
* number in the configuration is the number that is honoured.
|
|
286
|
+
*/
|
|
287
|
+
export function renderStatuslineLayout(input) {
|
|
288
|
+
const { advice, config } = input;
|
|
289
|
+
if (!advice.inject || advice.reason === "UNKNOWN")
|
|
290
|
+
return STATUSLINE_UNKNOWN;
|
|
291
|
+
const cells = statuslineCells(input.snapshots, input.now, resolveProviderOrder(config.order), config.meters, input.color, input.wide);
|
|
292
|
+
if (cells.length === 0)
|
|
293
|
+
return STATUSLINE_UNKNOWN;
|
|
294
|
+
const head = statuslineHead(advice);
|
|
295
|
+
const whole = packed(head, cells, config.width, config.rows);
|
|
296
|
+
if (fittedCount(whole) === cells.length)
|
|
297
|
+
return paintRows(head, whole);
|
|
298
|
+
/*
|
|
299
|
+
* Something has to go. Give up one cell at a time, worst kept first, until
|
|
300
|
+
* what is left plus the count of what was dropped fits inside the rows. The
|
|
301
|
+
* search starts at the number that already fitted, which is an upper bound,
|
|
302
|
+
* and it is bounded below by zero, which is the row with only a head on it.
|
|
303
|
+
*/
|
|
304
|
+
for (let keep = fittedCount(whole); keep >= 0; keep -= 1) {
|
|
305
|
+
const shown = [...worstCells(cells, keep), moreCell(cells.length - keep)];
|
|
306
|
+
const attempt = packed(head, shown, config.width, config.rows);
|
|
307
|
+
if (fittedCount(attempt) === shown.length)
|
|
308
|
+
return paintRows(head, attempt);
|
|
309
|
+
}
|
|
310
|
+
return head;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Whether this render may paint.
|
|
314
|
+
*
|
|
315
|
+
* `NO_COLOR` wins over every setting, including `always`. It is the one
|
|
316
|
+
* convention a person can set once, for every tool on the machine, and a
|
|
317
|
+
* configuration file inside one tool is not the place to overrule it.
|
|
318
|
+
*/
|
|
319
|
+
export function statuslineColor(setting, environment, automatic) {
|
|
320
|
+
if (environment["NO_COLOR"] !== undefined)
|
|
321
|
+
return false;
|
|
322
|
+
if (setting === "never")
|
|
323
|
+
return false;
|
|
324
|
+
if (setting === "always")
|
|
325
|
+
return true;
|
|
326
|
+
return automatic;
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=statusline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusline.js","sourceRoot":"","sources":["../src/statusline.ts"],"names":[],"mappings":"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"}
|