pi-tropos 0.9.0 → 0.10.1

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/README.md CHANGED
@@ -21,9 +21,13 @@ Personal pi customization bundle. One package, installable anywhere.
21
21
  - **`kaapi` theme** — filter-coffee dark: deep espresso base (`#15100D`), latte-foam text
22
22
  (`#E8DCCB`), caramel accent (`#D9984A`), matcha success, terracotta error. Warm and
23
23
  caffeinated. Select via `/settings` or `"theme": "kaapi"`.
24
- - **Clean footer** — one line: `project branch` left; `↑in ↓out Rcache Wcache $cost` plus
25
- the dumbzone gauge right. Zero-valued counters hide; no full paths, no cache-hit %,
26
- no session titles. Auto-named sessions are sanitized (no markdown table junk).
24
+ - **Clean footer** — one line: `project branch` left; `in · out · cached · stored · $cost`
25
+ plus the dumbzone `ctx` gauge right. Zero-valued counters hide; no full paths, no
26
+ cache-hit %, no session titles. Auto-named sessions are sanitized (no markdown junk).
27
+ `ctx` is the live context snapshot; the others are cumulative session billing counters
28
+ (`cached`/`stored` = provider prompt-cache reads/writes, re-read every turn). The
29
+ counter with the highest share of session spend is highlighted in the accent color —
30
+ typically `cached`, which is why keeping ctx small (the dumb-zone rule) also saves money.
27
31
  - **`/zen` mode** — reading mode for brainstorming: collapses all tool output and shrinks
28
32
  the footer to `$cost` + the ctx gauge. `/zen` again restores your previous state.
29
33
  - **`/peek <path>`** — transient code viewer: syntax-highlighted, line-numbered overlay.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tropos",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Personal pi customization bundle — context-health indicator, session handoff, session browser",
5
5
  "type": "module",
6
6
  "keywords": [
package/src/footer.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * (Zen lives here because the footer owns the chrome it minimizes.)
11
11
  */
12
12
 
13
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
13
+ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
14
14
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
15
15
 
16
16
  interface Usage {
@@ -18,7 +18,7 @@ interface Usage {
18
18
  output: number;
19
19
  cacheRead: number;
20
20
  cacheWrite: number;
21
- cost: { total: number };
21
+ cost: { total: number; input: number; output: number; cacheRead: number; cacheWrite: number };
22
22
  }
23
23
 
24
24
  interface Totals {
@@ -27,6 +27,8 @@ interface Totals {
27
27
  cacheRead: number;
28
28
  cacheWrite: number;
29
29
  cost: number;
30
+ /** Per-component spend, so the dominant cost driver can be highlighted. */
31
+ costParts: { input: number; output: number; cacheRead: number; cacheWrite: number };
30
32
  }
31
33
 
32
34
  function fmt(n: number) {
@@ -36,7 +38,14 @@ function fmt(n: number) {
36
38
  }
37
39
 
38
40
  function totals(ctx: ExtensionContext): Totals {
39
- const t: Totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
41
+ const t: Totals = {
42
+ input: 0,
43
+ output: 0,
44
+ cacheRead: 0,
45
+ cacheWrite: 0,
46
+ cost: 0,
47
+ costParts: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
48
+ };
40
49
  for (const e of ctx.sessionManager.getBranch()) {
41
50
  if (e.type !== "message" || e.message.role !== "assistant") continue;
42
51
  const u = (e.message as { usage?: Usage }).usage;
@@ -46,17 +55,33 @@ function totals(ctx: ExtensionContext): Totals {
46
55
  t.cacheRead += u.cacheRead ?? 0;
47
56
  t.cacheWrite += u.cacheWrite ?? 0;
48
57
  t.cost += u.cost?.total ?? 0;
58
+ t.costParts.input += u.cost?.input ?? 0;
59
+ t.costParts.output += u.cost?.output ?? 0;
60
+ t.costParts.cacheRead += u.cost?.cacheRead ?? 0;
61
+ t.costParts.cacheWrite += u.cost?.cacheWrite ?? 0;
49
62
  }
50
63
  return t;
51
64
  }
52
65
 
53
- function telemetryLine(t: Totals) {
66
+ /**
67
+ * Build styled telemetry segments. The component with the highest share
68
+ * of session spend is highlighted in the accent color — that's where
69
+ * your money is going (usually cache reads: the whole conversation is
70
+ * re-read from cache every turn, so small ctx = smaller bill).
71
+ */
72
+ function telemetryLine(t: Totals, theme: Theme) {
54
73
  if (t.input === 0) return "";
55
- const parts = [`↑${fmt(t.input)}`, `↓${fmt(t.output)}`];
56
- if (t.cacheRead > 0) parts.push(`R${fmt(t.cacheRead)}`);
57
- if (t.cacheWrite > 0) parts.push(`W${fmt(t.cacheWrite)}`);
58
- parts.push(`$${t.cost.toFixed(2)}`);
59
- return parts.join(" ");
74
+ const segments: { text: string; cost: number }[] = [
75
+ { text: `in ${fmt(t.input)}`, cost: t.costParts.input },
76
+ { text: `out ${fmt(t.output)}`, cost: t.costParts.output },
77
+ ];
78
+ if (t.cacheRead > 0) segments.push({ text: `cached ${fmt(t.cacheRead)}`, cost: t.costParts.cacheRead });
79
+ if (t.cacheWrite > 0) segments.push({ text: `stored ${fmt(t.cacheWrite)}`, cost: t.costParts.cacheWrite });
80
+
81
+ const top = segments.reduce((a, b) => (b.cost > a.cost ? b : a));
82
+ const styled = segments.map((s) => theme.fg(s === top && s.cost > 0 ? "accent" : "dim", s.text));
83
+ styled.push(theme.fg("dim", `$${t.cost.toFixed(2)}`));
84
+ return styled.join(theme.fg("dim", " "));
60
85
  }
61
86
 
62
87
  let zen = false;
@@ -102,7 +127,7 @@ export default function footer(pi: ExtensionAPI) {
102
127
  const dir = ctx.cwd.split("/").pop() ?? ctx.cwd;
103
128
  const branch = footerData.getGitBranch();
104
129
  const left = theme.fg("dim", branch ? `${dir} ${branch}` : dir);
105
- const metrics = theme.fg("dim", telemetryLine(t));
130
+ const metrics = telemetryLine(t, theme);
106
131
  const right = [metrics, statuses].filter(Boolean).join(" ");
107
132
 
108
133
  const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right)));
package/src/peek.ts CHANGED
@@ -24,7 +24,8 @@ export default function peek(pi: ExtensionAPI) {
24
24
  ctx.ui.notify("/peek needs interactive mode", "error");
25
25
  return;
26
26
  }
27
- const target = args.trim();
27
+ // Strip surrounding quotes — users habitually paste 'paths' or "paths".
28
+ const target = args.trim().replace(/^['"]|['"]$/g, "");
28
29
  if (!target) {
29
30
  ctx.ui.notify("Usage: /peek <path>", "info");
30
31
  return;