@leo-alvarenga/pi-zen-frame 0.15.0 → 0.16.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
@@ -6,8 +6,8 @@ A [`pi-coding-agent`](https://github.com/earendil-works/pi) extension that gives
6
6
 
7
7
  ## Features
8
8
 
9
- - **Clean Frame**: Clean editor frame with segments placed above and below it to easily see relevant info
10
- - **Header Box**: Optional Claude-style welcome panel displaying a logo, model/provider info, current working directory, and Git status
9
+ - **Clean Frame**: Clean editor frame with status segments rendered below the editor
10
+ - **Header Box**: Optional welcome panel with a logo and "Welcome back!" heading, model name, current working directory, Git status, and a random tip
11
11
  - **Working Messages**: Randomized loading status messages during response generation, updated on a configurable timer
12
12
  - **Native Theme Support**: Uses pi `ThemeColor` tokens to automatically align with your active theme
13
13
 
@@ -18,7 +18,7 @@ A [`pi-coding-agent`](https://github.com/earendil-works/pi) extension that gives
18
18
  Run the following command:
19
19
 
20
20
  ```bash
21
- pi install @leo-alvarenga/pi-zen-frame
21
+ pi install npm:@leo-alvarenga/pi-zen-frame
22
22
  ```
23
23
 
24
24
  Or add `pi-zen-frame` to your pi packages list in `~/.pi/agent/settings.json`:
@@ -42,14 +42,15 @@ Configuration is loaded from `~/.pi/agent/pi-zen-frame.json`. All properties are
42
42
  // Fallback accent color for frame border and active segments
43
43
  "accentColor": "accent",
44
44
 
45
- // Master mute: renders all segments in muted tones except agent-mode
46
- "zenMode": true,
45
+ // Master mute: renders all segments in muted tones except agent-mode (default: false)
46
+ "zenMode": false,
47
47
 
48
48
  // Editor-frame renderer by registered name (built-in: "blocky")
49
49
  "editorFrame": "blocky",
50
50
 
51
51
  "header": {
52
- "enable": true,
52
+ // Welcome panel is opt-in (default: false)
53
+ "enable": false,
53
54
  // Header renderer by registered name (built-in: "basic")
54
55
  "type": "basic",
55
56
  },
@@ -73,6 +74,7 @@ Configuration is loaded from `~/.pi/agent/pi-zen-frame.json`. All properties are
73
74
  "workingMessage": {
74
75
  "enable": true,
75
76
  "intervalMs": 3000,
77
+ // Omit to use the built-in message pool
76
78
  "messages": [
77
79
  "Exploring the seas",
78
80
  "Tinkering with strange objects",
@@ -86,16 +88,16 @@ Configuration is loaded from `~/.pi/agent/pi-zen-frame.json`. All properties are
86
88
 
87
89
  ## Structure & Status Segments
88
90
 
89
- The editor window features status indicators embedded along the frame borders:
91
+ The editor frame shows status indicators in two rows below the editor:
90
92
 
91
- | Location | Segment | Description |
92
- | ---------------- | -------------- | --------------------------------------------------------------------- |
93
- | **Top Left** | **model** | Active model name and provider |
94
- | | **reasoning** | Current reasoning effort level, tinted with pi thinking tokens |
95
- | | **spinner** | Active phase indicator (`thinking`, `outputting`, `toolcall`, `exec`) |
96
- | **Top Right** | **ctx** | Context usage percentage and token counts with color alerts |
97
- | **Bottom Left** | **agent mode** | Active agent indicator pill from `pi-agent-manager` |
98
- | **Bottom Right** | **cwd** | Shortened path, active Git branch, and uncommitted file counts |
93
+ | Location | Segment | Description |
94
+ | ------------------ | -------------- | --------------------------------------------------------------------- |
95
+ | Editor band (left) | **agent mode** | Active agent indicator pill from `pi-agent-manager` |
96
+ | | **model** | Active model name and provider |
97
+ | | **reasoning** | Current reasoning effort level, tinted with pi thinking tokens |
98
+ | | **spinner** | Active phase indicator (`thinking`, `outputting`, `toolcall`, `exec`); replaces the row while streaming when `showSpinner` is enabled (default off) |
99
+ | Footer (left) | **cwd** | Shortened path, active Git branch, and uncommitted file counts |
100
+ | Footer (right) | **ctx** | Context usage percentage and token counts with color alerts |
99
101
 
100
102
  ---
101
103
 
@@ -122,9 +124,9 @@ Run `/reload` after modifying your keybindings.
122
124
  extensions/
123
125
  ├── index.ts # Extension entry point: config initialization and events
124
126
  ├── config/ # Types, defaults, and settings normalization
125
- ├── components/ # Header (BasicHeader), frame border, and status segment modules
127
+ ├── components/ # Status segments, header, and frame layout helpers
126
128
  ├── editor/ # BlockyEditor editor-frame renderer
127
129
  ├── renderers/ # Renderer registry + swap-in API (blocky / basic)
128
- └── utils/ # Helpers for Git status, paths, and agent modes
130
+ └── utils/ # Helpers for Git status, paths, agent modes, and token usage
129
131
 
130
132
  ```
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(
@@ -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;
@@ -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.15.0",
5
+ "version": "0.16.1",
6
6
  "license": "MIT",
7
7
  "repository": "github.com/leo-alvarenga/pi-mono",
8
8
  "publishConfig": {