quotacap 0.0.13 → 0.0.14
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/format/table.js +5 -5
- package/dist/src/format/table.js +5 -5
- package/dist/src/version.js +1 -1
- package/dist/tests/format/table.test.js +21 -16
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/format/table.js
CHANGED
|
@@ -18,15 +18,15 @@ export function renderQuotasTable(quotas, advisories = []) {
|
|
|
18
18
|
const daysLeft = a?.daysLeft != null ? a.daysLeft.toFixed(1) : "—";
|
|
19
19
|
const ideal = a?.idealRate != null ? `${Math.round(a.idealRate)}%/day` : "—";
|
|
20
20
|
const burn = a?.burnMeasured ? `${a.burnRate.toFixed(1)}%/day` : "—";
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
const icon = a?.status === "at risk" ? "
|
|
21
|
+
// Renderers that count emoji as one cell but paint two (Kimi, Claude
|
|
22
|
+
// Code) shift every pipe after a wide glyph by +1; the header carries
|
|
23
|
+
// the same wide glyph (🚦) so header and body shift together.
|
|
24
|
+
const icon = a?.status === "at risk" ? "⚠️" : a != null ? "✅" : "—";
|
|
25
25
|
const waste = a?.wastePct != null ? `${Math.round(a.wastePct)}%` : "—";
|
|
26
26
|
return `| ${q.provider} | ${used}% | ${100 - used}% | ${resets} | ${daysLeft} | ${ideal} | ${burn} | ${waste} | ${icon} |`;
|
|
27
27
|
});
|
|
28
28
|
return [
|
|
29
|
-
"| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Burn rate | Waste if unused |
|
|
29
|
+
"| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Burn rate | Waste if unused | 🚦 |",
|
|
30
30
|
"|---|---|---|---|---|---|---|---|---|",
|
|
31
31
|
...rows,
|
|
32
32
|
].join("\n");
|
package/dist/src/format/table.js
CHANGED
|
@@ -18,15 +18,15 @@ export function renderQuotasTable(quotas, advisories = []) {
|
|
|
18
18
|
const daysLeft = a?.daysLeft != null ? a.daysLeft.toFixed(1) : "—";
|
|
19
19
|
const ideal = a?.idealRate != null ? `${Math.round(a.idealRate)}%/day` : "—";
|
|
20
20
|
const burn = a?.burnMeasured ? `${a.burnRate.toFixed(1)}%/day` : "—";
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
const icon = a?.status === "at risk" ? "
|
|
21
|
+
// Renderers that count emoji as one cell but paint two (Kimi, Claude
|
|
22
|
+
// Code) shift every pipe after a wide glyph by +1; the header carries
|
|
23
|
+
// the same wide glyph (🚦) so header and body shift together.
|
|
24
|
+
const icon = a?.status === "at risk" ? "⚠️" : a != null ? "✅" : "—";
|
|
25
25
|
const waste = a?.wastePct != null ? `${Math.round(a.wastePct)}%` : "—";
|
|
26
26
|
return `| ${q.provider} | ${used}% | ${100 - used}% | ${resets} | ${daysLeft} | ${ideal} | ${burn} | ${waste} | ${icon} |`;
|
|
27
27
|
});
|
|
28
28
|
return [
|
|
29
|
-
"| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Burn rate | Waste if unused |
|
|
29
|
+
"| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Burn rate | Waste if unused | 🚦 |",
|
|
30
30
|
"|---|---|---|---|---|---|---|---|---|",
|
|
31
31
|
...rows,
|
|
32
32
|
].join("\n");
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// generated by scripts/build-embed.mjs — do not edit
|
|
2
|
-
export const VERSION = "0.0.
|
|
2
|
+
export const VERSION = "0.0.14";
|
|
@@ -11,9 +11,9 @@ describe("format table", () => {
|
|
|
11
11
|
{ provider: "claude", wastePct: 52.9, daysLeft: 5.5, idealRate: 11.6, burnRate: 2, burnMeasured: false, status: "on track" },
|
|
12
12
|
];
|
|
13
13
|
const table = renderQuotasTable(quotas, advisories);
|
|
14
|
-
expect(table).toMatch(/\| Provider \| Used \| Remaining \| Resets \| Days left \| Ideal daily burn \| Burn rate \| Waste if unused \|
|
|
15
|
-
expect(table).toMatch(/\| kimi \| 16% \| 84% \|.*\| 3.0 \| 28%\/day \| 40.0%\/day \| 78% \|
|
|
16
|
-
expect(table).toMatch(/\| claude \| 37% \| 63% \|.*\| 5.5 \| 12%\/day \| — \| 53% \|
|
|
14
|
+
expect(table).toMatch(/\| Provider \| Used \| Remaining \| Resets \| Days left \| Ideal daily burn \| Burn rate \| Waste if unused \| 🚦 \|/);
|
|
15
|
+
expect(table).toMatch(/\| kimi \| 16% \| 84% \|.*\| 3.0 \| 28%\/day \| 40.0%\/day \| 78% \| ⚠️ \|/);
|
|
16
|
+
expect(table).toMatch(/\| claude \| 37% \| 63% \|.*\| 5.5 \| 12%\/day \| — \| 53% \| ✅ \|/);
|
|
17
17
|
});
|
|
18
18
|
it("renders quota-only rows with placeholders when no advisories exist", () => {
|
|
19
19
|
const quotas = [{ provider: "kimi", usedPct: 16, resetsAt: "2026-09-01T09:02:00+10:00" }];
|
|
@@ -27,7 +27,7 @@ describe("format table", () => {
|
|
|
27
27
|
expect(table).toMatch(/Sept?[^|]*2026/);
|
|
28
28
|
expect(table).not.toMatch(/9\/1\/2026/);
|
|
29
29
|
});
|
|
30
|
-
it("keeps
|
|
30
|
+
it("keeps header and data pipes aligned under both observed width models", () => {
|
|
31
31
|
const quotas = [
|
|
32
32
|
{ provider: "kimi", usedPct: 16, resetsAt: "2026-09-01T09:02:00+10:00" },
|
|
33
33
|
{ provider: "claude", usedPct: 37, resetsAt: "2026-09-03T21:00:00+10:00" },
|
|
@@ -38,20 +38,25 @@ describe("format table", () => {
|
|
|
38
38
|
];
|
|
39
39
|
const table = renderQuotasTable(quotas, advisories);
|
|
40
40
|
const lines = table.split("\n");
|
|
41
|
-
|
|
42
|
-
// count. Any cell where paint > naive-count shifts that row's pipes.
|
|
43
|
-
const paintWidth = (ch) => {
|
|
41
|
+
const emoji = (ch) => {
|
|
44
42
|
const c = ch.codePointAt(0);
|
|
45
|
-
return
|
|
43
|
+
return (c >= 0x2600 && c <= 0x27BF) || c >= 0x1F000;
|
|
46
44
|
};
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
const paint = (ch) => (emoji(ch) ? 2 : 1);
|
|
46
|
+
// Model A: Kimi/Claude Code — emoji count 1, paint 2.
|
|
47
|
+
// Model B: OpenCode — emoji count 2, paint 2.
|
|
48
|
+
const countA = (ch) => (emoji(ch) ? 1 : 1);
|
|
49
|
+
const countB = (ch) => (emoji(ch) ? 2 : 1);
|
|
50
|
+
const gap = (cell, count) => [...cell].reduce((w, ch) => w + paint(ch), 0) - [...cell].reduce((w, ch) => w + count(ch), 0);
|
|
51
|
+
// The divider row (---) cannot carry a wide glyph, so it is excluded —
|
|
52
|
+
// renderers draw it from the separator and its gap is inherently 0.
|
|
53
|
+
const rows = lines.filter((l, i) => i !== 1);
|
|
54
|
+
const columns = rows.map((l) => l.split("|").slice(1, -1));
|
|
55
|
+
for (let col = 0; col < columns[0].length; col++) {
|
|
56
|
+
const gapsA = columns.map((r) => gap(r[col], countA));
|
|
57
|
+
const gapsB = columns.map((r) => gap(r[col], countB));
|
|
58
|
+
expect(new Set(gapsA).size).toBe(1);
|
|
59
|
+
expect(new Set(gapsB).size).toBe(1);
|
|
55
60
|
}
|
|
56
61
|
});
|
|
57
62
|
});
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// generated by scripts/build-embed.mjs — do not edit
|
|
2
|
-
export const VERSION = "0.0.
|
|
2
|
+
export const VERSION = "0.0.14";
|