@bismawy/pi-arnative 0.2.10 → 0.2.12
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/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/extensions/footer.ts +96 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.12] - 2026-10-02
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- The footer is a fixed 3-row grid: row 1 pairs the cwd line with the model, rows 2-3 pair the extension status (mcp first) with the metrics (tok/s · cache · token) — each side wraps inside its half of the terminal, the right cells stay right-aligned, and a row where both sides ran out is dropped. Replaces the adaptive 2→3 reflow; no click toggle (`extensions/footer.ts`, `layoutFooterGrid()` + `pairRow()`).
|
|
7
|
+
|
|
8
|
+
## [0.2.11] - 2026-10-01
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- The footer status area now reflows: 2 lines stay the default while everything fits, but on a narrow terminal the statuses and the metrics (tok/s, cache, context) move onto a 3rd line instead of being truncated. `extensions/footer.ts` `layoutStatusLines()` (`rows` = line budget of the whole footer, `maxRows` caps the growth); self-check covers the wide/narrow/cap cases.
|
|
12
|
+
|
|
3
13
|
## [0.2.10] - 2026-10-01
|
|
4
14
|
|
|
5
15
|
### Added
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ pi-arnative replaces pi’s default terminal chrome with a unified, cohesive vis
|
|
|
15
15
|
- Warm Themes: Cohesive, contrast-checked color palettes generated without drift.
|
|
16
16
|
- Tabbed Header: Greeting, version line, and a compact box menu over Directory, Model, Context, Skills, Extensions, Themes, and Shortcut.
|
|
17
17
|
- Rounded Tool Boxes: Clear status, spinner, and duration for every tool call.
|
|
18
|
-
- Enhanced Footer:
|
|
18
|
+
- Enhanced Footer: fixed 3-row grid — directory/git line, then extension status paired with token metrics.
|
|
19
19
|
- Transcript Clock: Timestamps for messages with clean bubble backgrounds.
|
|
20
20
|
- Usage Dashboard: /usage command to track tokens and session costs.
|
|
21
21
|
|
|
@@ -68,7 +68,7 @@ Add to `~/.pi/agent/keybindings.json` to bind `app.session.new`:
|
|
|
68
68
|
| --- | --- |
|
|
69
69
|
| `extensions/tools.ts` | Rounded tool boxes, spinners, and duration |
|
|
70
70
|
| `extensions/section-headers.ts` | Tabbed header and resource box |
|
|
71
|
-
| `extensions/footer.ts` |
|
|
71
|
+
| `extensions/footer.ts` | 3-row status grid footer and boxed editor |
|
|
72
72
|
| `extensions/timestamps.ts` | Message clock and bubble background fixes |
|
|
73
73
|
| `extensions/ui-render-tweaks.ts` | Contrast tweaks, selection style, and UI polish |
|
|
74
74
|
| `extensions/usage.ts` | `/usage` token dashboard |
|
package/extensions/footer.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Arnative footer (
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Arnative footer (fixed 3-row grid, no click toggle).
|
|
3
|
+
* Row 1: cwd | duration | branch/tag/status ...... model + thinking level.
|
|
4
|
+
* Rows 2-3: extension status (mcp first) ......... tok/s · cache · token,
|
|
5
|
+
* each side wrapped inside its half of the width and paired row by row.
|
|
5
6
|
*
|
|
6
7
|
* Transcript clock & bubble bg: extensions/timestamps.ts
|
|
7
8
|
* /new header: extensions/section-headers.ts
|
|
@@ -176,7 +177,69 @@ export function boxEditorLines(
|
|
|
176
177
|
return out;
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
/**
|
|
181
|
+
* The footer as a fixed 3-row grid. Row 1 pairs the head cells (cwd line, model);
|
|
182
|
+
* rows 2-3 pair the wrapped left cells (extension status, mcp first) with the
|
|
183
|
+
* wrapped right cells (tok/s · cache · token), each side wrapping inside its half
|
|
184
|
+
* of the terminal so the columns never collide. Right cells stay right-aligned.
|
|
185
|
+
* A side that runs out of cells leaves its half blank, and a row where both sides
|
|
186
|
+
* ran out is dropped, so a quiet footer stays 1-3 rows tall.
|
|
187
|
+
*/
|
|
188
|
+
export function layoutFooterGrid(
|
|
189
|
+
headLeft: string,
|
|
190
|
+
headRight: string,
|
|
191
|
+
left: readonly string[],
|
|
192
|
+
right: readonly string[],
|
|
193
|
+
leftSep: string,
|
|
194
|
+
rightSep: string,
|
|
195
|
+
width: number,
|
|
196
|
+
vw: (s: string) => number = visibleWidth,
|
|
197
|
+
): string[] {
|
|
198
|
+
const half = Math.max(1, Math.floor(width / 2) - 1);
|
|
199
|
+
// Wrap each side at half width; the first break happens at most once (rows 2-3),
|
|
200
|
+
// after that the rest stays on the last row and the pairing truncates it.
|
|
201
|
+
const wrap = (cells: readonly string[], sep: string): string[] => {
|
|
202
|
+
const rows: string[] = [];
|
|
203
|
+
let line = "";
|
|
204
|
+
for (const cell of cells) {
|
|
205
|
+
const candidate = line ? line + sep + cell : cell;
|
|
206
|
+
if (line && rows.length === 0 && vw(candidate) > half) {
|
|
207
|
+
rows.push(line);
|
|
208
|
+
line = cell;
|
|
209
|
+
} else {
|
|
210
|
+
line = candidate;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (line) rows.push(line);
|
|
214
|
+
return rows;
|
|
215
|
+
};
|
|
216
|
+
const leftRows = wrap(left, leftSep);
|
|
217
|
+
const rightRows = wrap(right, rightSep);
|
|
218
|
+
const out = [pairRow(headLeft, headRight, width, vw)];
|
|
219
|
+
for (let i = 0; i < Math.max(leftRows.length, rightRows.length); i++) {
|
|
220
|
+
const l = leftRows[i];
|
|
221
|
+
const r = rightRows[i];
|
|
222
|
+
if (l === undefined && r === undefined) break;
|
|
223
|
+
out.push(pairRow(l ?? "", r ?? "", width, vw));
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** One grid row: `left` at the start, `right` flush to the right edge. */
|
|
229
|
+
function pairRow(left: string, right: string, width: number, vw: (s: string) => number): string {
|
|
230
|
+
if (!right) return truncateToWidth(left, width);
|
|
231
|
+
const rightWidth = Math.min(vw(right), width);
|
|
232
|
+
if (!left) return " ".repeat(width - rightWidth) + truncateToWidth(right, width);
|
|
233
|
+
if (vw(left) + 1 + rightWidth <= width) {
|
|
234
|
+
return left + " ".repeat(width - vw(left) - rightWidth) + right;
|
|
235
|
+
}
|
|
236
|
+
// Too wide together: the right cell keeps its columns, the left one is cut.
|
|
237
|
+
const room = Math.max(1, width - rightWidth - 1);
|
|
238
|
+
return truncateToWidth(left, room) + " " + truncateToWidth(right, width - room - 1);
|
|
239
|
+
}
|
|
240
|
+
|
|
179
241
|
function parseOptimizer(raw: string | undefined): string | null {
|
|
242
|
+
|
|
180
243
|
if (!raw) return null;
|
|
181
244
|
const m = raw.match(/([A-Za-z0-9_-]+)\s+cache\s+(\d+\/\d+)·[^\s]+\s+([\d.]+%)/);
|
|
182
245
|
return m ? `${m[1]} ${m[2]} (${m[3]})` : null;
|
|
@@ -401,8 +464,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
401
464
|
left1 = `${acc("\uf07b")} ${dim(cwdShort)}${sep}${pDuration}${sep}${pBranch}${sep}${pTag}${sep}${pState}`;
|
|
402
465
|
}
|
|
403
466
|
const right1 = formatModelName(currentModel, currentThinkingLevel, acc, tint, dim);
|
|
404
|
-
const pad1 = " ".repeat(Math.max(1, width - visibleWidth(left1) - visibleWidth(right1)));
|
|
405
|
-
const lines = [truncateToWidth(left1 + pad1 + right1, width)];
|
|
406
467
|
|
|
407
468
|
const statuses: ReadonlyMap<string, string> = (() => {
|
|
408
469
|
try {
|
|
@@ -412,7 +473,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
412
473
|
}
|
|
413
474
|
})();
|
|
414
475
|
const rawCache = statuses.get("pi-cache-stats");
|
|
415
|
-
const segs: string[] = [];
|
|
416
476
|
const cleanStatus = (s: string) => {
|
|
417
477
|
if (s.includes("MCP:")) {
|
|
418
478
|
let clean = stripAnsi(s);
|
|
@@ -450,12 +510,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
450
510
|
return tint(clean.trim());
|
|
451
511
|
};
|
|
452
512
|
|
|
513
|
+
const groups: string[] = [];
|
|
453
514
|
const mcp = statuses.get("mcp");
|
|
454
|
-
if (mcp !== undefined)
|
|
515
|
+
if (mcp !== undefined) groups.push(cleanStatus(mcp));
|
|
455
516
|
for (const [k, s] of statuses) {
|
|
456
|
-
if (k !== "mcp" && k !== "pi-cache-stats")
|
|
517
|
+
if (k !== "mcp" && k !== "pi-cache-stats") groups.push(cleanStatus(s));
|
|
457
518
|
}
|
|
458
|
-
const left2 = segs.join(sep);
|
|
459
519
|
|
|
460
520
|
const opt = parseOptimizer(rawCache);
|
|
461
521
|
let usageStr = "";
|
|
@@ -465,15 +525,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
465
525
|
// fallback
|
|
466
526
|
}
|
|
467
527
|
const speedStr = latestSpeed !== null && latestSpeed > 0 ? `${acc("\udb81\udcc5")} ${tint(`${latestSpeed.toFixed(1)} tok/s`)}` : "";
|
|
468
|
-
const
|
|
528
|
+
const metricCells = [speedStr, opt ? `${acc("\udb80\udf5b")} ${tint(opt)}` : "", usageStr].filter(Boolean);
|
|
469
529
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
const pad2 = " ".repeat(Math.max(1, width - visibleWidth(left2) - visibleWidth(right2)));
|
|
474
|
-
lines.push(truncateToWidth(left2 + pad2 + right2, width));
|
|
475
|
-
}
|
|
476
|
-
return lines;
|
|
530
|
+
// Fixed 3-row grid: head (cwd/model), then statuses and metrics each
|
|
531
|
+
// wrapped inside their half of the width — no click toggle.
|
|
532
|
+
return layoutFooterGrid(left1, right1, groups, metricCells, sep, ` ${dim("·")} `, width);
|
|
477
533
|
},
|
|
478
534
|
};
|
|
479
535
|
});
|
|
@@ -664,6 +720,29 @@ if (isMain(import.meta.url)) {
|
|
|
664
720
|
assert(formatDuration(65_000) === "1m 5s", "minutes + seconds");
|
|
665
721
|
assert(formatDuration(3_700_000) === "1h 1m", "hours + minutes");
|
|
666
722
|
|
|
723
|
+
// Self-check: the footer grid — head row, then statuses/metrics paired per row.
|
|
724
|
+
// truncateToWidth appends ANSI resets, so measure through stripAnsi (the real
|
|
725
|
+
// renderer measures with visibleWidth, which ignores them the same way).
|
|
726
|
+
const vw = (s: string) => stripAnsi(s).length;
|
|
727
|
+
const g = (n: number) => "g".repeat(n);
|
|
728
|
+
// Head row: cwd line left, model right-aligned; groups and metrics pair below.
|
|
729
|
+
const grid = layoutFooterGrid("H".repeat(10), "M".repeat(6), [g(10), g(10), g(10)], ["s".repeat(10), "u".repeat(10)], " | ", " · ", 40, vw);
|
|
730
|
+
assert(grid.length === 3, "grid: head + two paired rows");
|
|
731
|
+
assert(grid[0]!.startsWith("H".repeat(10)) && grid[0]!.endsWith("M".repeat(6)), "grid: head pairs cwd and model");
|
|
732
|
+
assert(grid[0]!.length === 40, "grid: head right-aligns the model");
|
|
733
|
+
assert(grid[1]!.includes("g") && grid[1]!.endsWith("s".repeat(10)), "grid: row 2 pairs statuses and metrics");
|
|
734
|
+
assert(grid.every((l) => vw(l) <= 40), "grid: every row fits the terminal");
|
|
735
|
+
// Each side wraps inside its half: 3 groups + 2 metrics at width 20 -> 3 rows.
|
|
736
|
+
const tight = layoutFooterGrid("h", "m", [g(6), g(6), g(6)], [g(6), g(6)], " | ", " · ", 20, vw);
|
|
737
|
+
assert(tight.length === 3, "grid: wraps inside the half width");
|
|
738
|
+
assert(tight.every((l) => vw(l) <= 20), "grid: wrapped rows still fit");
|
|
739
|
+
// The right cell survives an oversized left cell (the left one is truncated).
|
|
740
|
+
const cut = layoutFooterGrid("h", "m", [g(30)], ["R".repeat(4)], " | ", " · ", 12, vw);
|
|
741
|
+
assert(cut[1]!.endsWith("R".repeat(4)) && vw(cut[1]!) === 12, "grid: right cell survives a wide left cell");
|
|
742
|
+
// A side that runs out leaves its half blank; rows where both ran out drop.
|
|
743
|
+
const blank = layoutFooterGrid("h", "m", [], [], " | ", " · ", 20, vw);
|
|
744
|
+
assert(blank.length === 1, "grid: empty sides drop their rows");
|
|
745
|
+
|
|
667
746
|
const plain = (s: string) => s;
|
|
668
747
|
const D = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"; // 8 columns = content width in this test
|
|
669
748
|
const boxed = boxEditorLines([D, " hi ", " lo ", D, " /mo "], 2, plain);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bismawy/pi-arnative",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Arnative look for Pi: warm-neutral themes, rounded tool boxes, tabbed header, compact footer, transcript clock, and a /usage dashboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|