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/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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"}