@leo-alvarenga/pi-zen-frame 0.14.0 → 0.16.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/docs/preview.png CHANGED
Binary file
@@ -13,7 +13,9 @@ export const cwdSegment: SegmentDef = {
13
13
 
14
14
  if (d.gitBranch) {
15
15
  let git = ` ${icons.gitBranch} ${d.gitBranch}`;
16
- text += theme.fg(d.zenMode ? "muted" : d.accentColor, git);
16
+ text +=
17
+ theme.fg("dim", " ·") +
18
+ theme.fg(d.zenMode ? "muted" : d.accentColor, git);
17
19
 
18
20
  if (d.gitDirty > 0) {
19
21
  text += theme.fg(
@@ -43,21 +43,13 @@ export function composeBand(
43
43
  autocomplete: string[],
44
44
  opts: BandOptions,
45
45
  ): string[] {
46
- const {
47
- padX,
48
- paint,
49
- width,
50
- prefix,
51
- marginX,
52
- boxBottom,
53
- paddingTop,
54
- paddingBottom,
55
- } = opts;
46
+ const { padX, paint, width, prefix, boxBottom, paddingTop, paddingBottom } =
47
+ opts;
56
48
 
57
49
  const lead = Math.max(0, padX - visibleWidth(prefix));
58
50
 
59
51
  const inset = (row: string) =>
60
- paint(prefix + " ".repeat(lead) + row + " ".repeat(padX));
52
+ prefix + paint(" ".repeat(lead) + row + " ".repeat(padX));
61
53
 
62
54
  const fill = () => inset(paint(" ".repeat(Math.max(0, width - 2 * padX))));
63
55
 
@@ -5,9 +5,9 @@ export const modelSegment: SegmentDef = {
5
5
  id: "model",
6
6
  slot: "topLeft",
7
7
  enabled: (_d, cfg) => cfg.showModel !== false,
8
- render: (d, { theme }) => {
8
+ render: (d, { icons, theme }) => {
9
9
  if (!d.modelName) return "";
10
10
 
11
- return ` ${theme.fg("text", d.modelName)} ${theme.fg("muted", `(${d.modelProvider})`)} `;
11
+ return ` ${theme.fg(d.accentColor, `${icons.model} ${d.modelName}`)} ${theme.fg("muted", `(${d.modelProvider})`)} `;
12
12
  },
13
13
  };
@@ -4,12 +4,13 @@ import type { SegmentDef } from "./types";
4
4
 
5
5
  function trimFixed1(n: number): string {
6
6
  const t = n.toFixed(1);
7
+
7
8
  return t.endsWith(".0") ? t.slice(0, -2) : t;
8
9
  }
9
10
 
10
11
  function formatTokens(n: number): string {
11
12
  if (n >= 1_000_000) return `${trimFixed1(n / 1_000_000)}M`;
12
- if (n >= 1_000) return `${(n / 1_000).toFixed(0)}k`;
13
+ if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
13
14
 
14
15
  return `${n}`;
15
16
  }
@@ -28,13 +29,15 @@ export const tokenCountSegment: SegmentDef = {
28
29
  const c = d.context;
29
30
  if (!c) return "";
30
31
 
31
- const pct = c.percent === null ? "?" : `${Math.round(c.percent)}%`;
32
+ const { input, output, percent, tokens, window } = c;
33
+
34
+ const pct = percent === null ? "?" : `${Math.round(percent)}%`;
32
35
  let color: ThemeColor = "muted";
33
36
 
34
- if (c.percent !== null) {
35
- if (c.percent >= 80) {
37
+ if (percent !== null) {
38
+ if (percent >= 80) {
36
39
  color = "error";
37
- } else if (c.percent >= 50) {
40
+ } else if (percent >= 50) {
38
41
  color = "warning";
39
42
  } else {
40
43
  color = "success";
@@ -42,12 +45,17 @@ export const tokenCountSegment: SegmentDef = {
42
45
  }
43
46
 
44
47
  const color2 = d.zenMode ? "muted" : color;
45
- const used = c.tokens === null ? "?" : formatTokens(c.tokens);
48
+ const used = tokens === null ? "?" : formatTokens(tokens);
46
49
 
47
50
  return (
48
51
  theme.fg(color2, ` ${icons.context} ctx ${pct} `) +
49
52
  theme.fg("dim", "·") +
50
- theme.fg(color2, ` ${used}/${formatWindow(c.window)} `)
53
+ theme.fg(color2, ` ${used}/${formatWindow(window)} `) +
54
+ theme.fg("dim", "·") +
55
+ theme.fg(
56
+ "dim",
57
+ ` ${icons.tokenIn} ${formatTokens(input)} ${icons.tokenOut} ${formatTokens(output)} `,
58
+ )
51
59
  );
52
60
  },
53
61
  };
@@ -11,6 +11,7 @@
11
11
  import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
12
12
 
13
13
  import type { FrameIcons, FrameSettings, SpinnerPhase } from "../config/types";
14
+ import { TokenUsage } from "../utils/token";
14
15
 
15
16
  /** Where a segment renders. "top*" → the box-bottom row inside the band,
16
17
  * "bottom*" → the pseudo-footer row below it (names kept from the old
@@ -54,11 +55,7 @@ export interface FrameData {
54
55
  /** To render the agent mode (from @leo-alvarenga/pi-agent-manager) segment, if any */
55
56
  agentMode: AgentMode;
56
57
 
57
- context: {
58
- window: number;
59
- tokens: number | null;
60
- percent: number | null;
61
- } | null;
58
+ context: TokenUsage | null;
62
59
  }
63
60
 
64
61
  /** Data + rendering helpers handed to every segment. */
@@ -15,7 +15,9 @@ export const SPINNER_FRAMES: Record<SpinnerPhase, string[]> = {
15
15
  export const DEFAULT_ICONS: FrameIcons = {
16
16
  model: "󰣖",
17
17
  folder: " ",
18
+ tokenIn: "",
18
19
  context: "",
20
+ tokenOut: "",
19
21
  gitDirty: "",
20
22
  thinking: "󰌶",
21
23
  gitBranch: "",
@@ -32,7 +34,6 @@ export const THINKING_TOKEN: Record<string, string> = {
32
34
  max: "thinkingMax",
33
35
  };
34
36
 
35
-
36
37
  export const THEME_COLORS: Record<ThemeColor, true> = {
37
38
  accent: true,
38
39
  border: true,
@@ -14,6 +14,8 @@ export interface FrameIcons {
14
14
  folder: string;
15
15
  model: string;
16
16
  context: string;
17
+ tokenIn: string;
18
+ tokenOut: string;
17
19
  thinking: string;
18
20
  gitDirty: string;
19
21
  gitBranch: string;
@@ -103,7 +103,8 @@ export class BlockyEditor extends CustomEditor {
103
103
  let prefix = "┃";
104
104
 
105
105
  if (ext.theme?.fg) {
106
- const prefixColor: ThemeColor = ext.agentMode?.color ?? "text";
106
+ const prefixColor: ThemeColor =
107
+ ext.agentMode?.color ?? this.opts.accentColor ?? "text";
107
108
  prefix = ext.theme.fg(prefixColor, prefix);
108
109
  }
109
110
 
@@ -37,6 +37,7 @@ import {
37
37
  type GitInfo,
38
38
  } from "./utils";
39
39
  import { type HeaderEnv } from "./components/header";
40
+ import { getUsage } from "./utils/token";
40
41
 
41
42
  let editor: EditorFrameRenderer | null = null;
42
43
  let currentCtx: ExtensionContext | null = null;
@@ -84,7 +85,6 @@ function getHeaderEnv(pi: ExtensionAPI): HeaderEnv {
84
85
 
85
86
  function provideExternal(pi: ExtensionAPI): ExternalData {
86
87
  const ctx = currentCtx;
87
- const usage = ctx?.getContextUsage?.();
88
88
  const model = ctx?.model;
89
89
 
90
90
  return {
@@ -95,16 +95,10 @@ function provideExternal(pi: ExtensionAPI): ExternalData {
95
95
  gitDirty: git.dirty,
96
96
  gitBranch: git.branch,
97
97
  theme: ctx?.ui.theme,
98
+ context: getUsage(ctx ?? undefined),
98
99
  thinkingLevel: pi.getThinkingLevel(),
99
100
  modelName: model?.name ?? model?.id ?? "Unknown",
100
101
  modelProvider: capitalize(model?.provider ?? "unknown"),
101
- context: usage
102
- ? {
103
- tokens: usage.tokens,
104
- window: usage.contextWindow,
105
- percent: usage.percent,
106
- }
107
- : null,
108
102
  };
109
103
  }
110
104
 
@@ -232,9 +226,7 @@ export default async function (pi: ExtensionAPI) {
232
226
  {
233
227
  frame: settings.frame ?? DEFAULT_SETTINGS.frame!,
234
228
  accentColor:
235
- settings.accentColor ??
236
- DEFAULT_SETTINGS.accentColor ??
237
- "accent",
229
+ settings.accentColor ?? DEFAULT_SETTINGS.accentColor ?? "accent",
238
230
  },
239
231
  ...args,
240
232
  );
@@ -0,0 +1,49 @@
1
+ import { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ export type TokenUsage = TokenThroughput & {
4
+ tokens: number;
5
+ window: number;
6
+ percent: number;
7
+ };
8
+
9
+ export type TokenThroughput = {
10
+ input: number;
11
+ output: number;
12
+ };
13
+
14
+ type EntryUsage = {
15
+ usage?: TokenThroughput;
16
+ };
17
+
18
+ function getUsageFromEntries(ctx: ExtensionContext): TokenThroughput {
19
+ let input = 0,
20
+ output = 0;
21
+
22
+ for (const entry of ctx.sessionManager.getEntries()) {
23
+ const usage =
24
+ entry.type === "compaction" || entry.type === "branch_summary"
25
+ ? (entry as EntryUsage).usage
26
+ : (entry as { message?: EntryUsage }).message?.usage;
27
+
28
+ if (!usage) continue;
29
+
30
+ input += usage.input ?? 0;
31
+ output += usage.output ?? 0;
32
+ }
33
+
34
+ return { input, output };
35
+ }
36
+
37
+ export function getUsage(ctx?: ExtensionContext): TokenUsage | null {
38
+ if (!ctx) return null;
39
+
40
+ const usage = ctx.getContextUsage();
41
+ if (!usage) return null;
42
+
43
+ return {
44
+ ...getUsageFromEntries(ctx),
45
+ tokens: usage?.tokens ?? 0,
46
+ percent: usage?.percent ?? 0,
47
+ window: usage?.contextWindow ?? 0,
48
+ };
49
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@leo-alvarenga/pi-zen-frame",
3
3
  "description": "A pi-coding-agent extension that gives the TUI editor a simple yet polished look and feel",
4
4
  "author": "Leonardo A. Alvarenga",
5
- "version": "0.14.0",
5
+ "version": "0.16.0",
6
6
  "license": "MIT",
7
7
  "repository": "github.com/leo-alvarenga/pi-mono",
8
8
  "publishConfig": {