@leo-alvarenga/pi-zen-frame 0.11.0 → 0.12.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
@@ -1,63 +1,75 @@
1
- # pi-zen-frame
1
+ # @leo-alvarenga/pi-zen-frame
2
2
 
3
- A [pi-coding-agent](https://github.com/earendil-works/pi) extension that gives the TUI editor a simple yet polished look and feel.
4
-
5
- - **Frame** — a box (`╭ ╮ ╰ ╯ │ ─`) with live status segments embedded in the top/bottom border.
6
- - **Header** — an optional Claude-welcome-style top box: logo (left, 40%) + model/provider and cwd/git info (right, 60%, split in two halves).
7
- - **Working messages** — randomized messages in pi's built-in working loader, swapped on a configurable interval (on by default).
8
- - **Theme native** — every color is a pi `ThemeColor`, so it follows your active pi theme.
3
+ A [`pi-coding-agent`](https://github.com/earendil-works/pi) extension that gives the TUI editor a clean, minimalist layout with customizable status indicators and frames.
9
4
 
10
5
  ![Preview](./docs/preview.png)
11
6
 
12
- ## Install
7
+ ## Features
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
11
+ - **Working Messages**: Randomized loading status messages during response generation, updated on a configurable timer
12
+ - **Native Theme Support**: Uses pi `ThemeColor` tokens to automatically align with your active theme
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ Run the following command:
19
+
20
+ ```bash
21
+ pi install @leo-alvarenga/pi-zen-frame
22
+ ```
13
23
 
14
- It's distributed as a pi package. Add it to `settings.json` → `packages`, e.g. install from
15
- a local checkout or registry:
24
+ Or add `pi-zen-frame` to your pi packages list in `~/.pi/agent/settings.json`:
16
25
 
17
26
  ```jsonc
18
- // ~/.pi/agent/settings.json
19
27
  {
20
- "packages": ["npm:@leo-alvarenga/pi-zen-frame", "path/to/pi-zen-frame"],
28
+ "packages": ["npm:@leo-alvarenga/pi-zen-frame"],
21
29
  }
22
30
  ```
23
31
 
24
- Restart pi (or `/reload`).
32
+ Then, restart pi or run `/reload` in the console.
25
33
 
26
- ## Config
34
+ ---
27
35
 
28
- zen editor reads `~/.pi/agent/pi-zen-frame.json`. All keys are optional; defaults are shown.
36
+ ## Configuration
37
+
38
+ Configuration is loaded from `~/.pi/agent/pi-zen-frame.json`. All properties are optional.
29
39
 
30
40
  ```jsonc
31
41
  {
32
- // Accent used by segments/frame when a more specific color isn't set.
42
+ // Fallback accent color for frame border and active segments
33
43
  "accentColor": "accent",
34
44
 
35
- // Master mute: every segment except agent-mode renders muted.
36
- // Toggle at runtime with /zen_mode or the piZenFrame.zenMode keybinding.
45
+ // Master mute: renders all segments in muted tones except agent-mode
37
46
  "zenMode": true,
38
47
 
39
48
  "header": {
40
49
  "enable": true,
41
- "logo": [" ", "██████████", "███ ███ ", "██████ ", ...],
42
- "heading": "Zen Pi",
43
- "subheading": "A pi-coding-agent powered terminal editor",
50
+ "logo": ["█████████ ", "███ ███ ", "██████ ", "███ ███"],
51
+ "heading": "Welcome back!",
52
+ "subheading": "Ready for your next session? Terminal warm, context clean, tools ready to execute",
44
53
  "logoColor": "text",
45
- "accentColor": "customMessageLabel"
54
+ "accentColor": "customMessageLabel",
46
55
  },
47
56
 
48
57
  "frame": {
49
58
  "enable": true,
50
- // Below this terminal width the box is skipped (plain editor).
59
+
60
+ // Minimum terminal width required to render the border frame
51
61
  "minWidth": 20,
52
62
 
53
- // Blank lines inside the box (padding) / outside it (margin).
63
+ // Padding inside the frame
54
64
  "paddingTop": 1,
55
65
  "paddingBottom": 1,
56
66
  "paddingX": 1,
67
+
68
+ // Outer margin around the frame
57
69
  "marginTop": 0,
58
70
  "marginBottom": 0,
59
71
 
60
- // Border segments on/off.
72
+ // Toggle individual status segments
61
73
  "showCwd": true,
62
74
  "showModel": true,
63
75
  "showContext": true,
@@ -65,84 +77,80 @@ zen editor reads `~/.pi/agent/pi-zen-frame.json`. All keys are optional; default
65
77
  "showSpinner": false,
66
78
  "showAgentMode": true,
67
79
 
68
- // Defaults to accentColor, then "border"; Can also be set to "agentMode" to match the current agent's color
80
+ // Color options: "border", "accentColor", or "agentMode"
69
81
  "borderColor": "border",
70
82
 
71
- // Glyph shown before the editor content. Default "❯".
83
+ // Input prompt prefix glyph and color settings
72
84
  "prefix": "❯",
73
-
74
- // Prefix color source: "agentMode" (current agent), "frameBorder" (frame
75
- // border color), or any ThemeColor. Default: text color.
76
- "prefixColor": "muted",
77
-
78
- // Override the Nerd-Font glyphs (folder, model, context, thinking,
79
- // gitDirty, gitBranch).
80
- "icons": {},
81
-
82
- // Per-segment fg overrides (any subset; supersede the built-in colors,
83
- // e.g. the ctx traffic-light). Keys: model, thinking, context, cwd,
84
- // agentMode. agentMode only applies when zenMode is off and no agent
85
- // color is set.
86
- "colors": { "model": "accent", "cwd": "accent" }
85
+ "prefixColor": "muted", // Options: "agentMode", "frameBorder", or any ThemeColor
86
+
87
+ // Override default Nerd-Font glyphs
88
+ "icons": {
89
+ // Keys: folder, model, context, thinking, gitDirty, gitBranch
90
+ },
91
+
92
+ // Per-segment foreground color overrides (supersedes default theme colors)
93
+ "colors": {
94
+ "model": "accent",
95
+ "cwd": "accent",
96
+ },
87
97
  },
88
98
 
89
- // Randomized messages shown in pi's built-in working loader (the
90
- // "Working..." line while streaming). A new random message is picked
91
- // every `intervalMs`; no repeats until the pool is exhausted. Default on.
99
+ // Randomized status messages shown while streaming responses
92
100
  "workingMessage": {
93
101
  "enable": true,
94
102
  "intervalMs": 3000,
95
- "messages": ["Exploring the seas", "Tinkering with strange objects", "..."]
96
- }
97
- }
98
- ```
99
-
100
- ## Commands & keybindings
101
-
102
- - `/zen_mode` — toggle zen mode (all segments muted except agent-mode).
103
- - Toggle keybinding: `piZenFrame.zenMode`, default `ctrl+shift+z`. Rebind or
104
- disable it in `~/.pi/agent/keybindings.json`:
105
-
106
- ```jsonc
107
- // ~/.pi/agent/keybindings.json
108
- {
109
- "piZenFrame.zenMode": "ctrl+shift+z", // or [] to disable; /zen_mode still works
103
+ "messages": [
104
+ "Exploring the seas",
105
+ "Tinkering with strange objects",
106
+ "Analyzing patterns",
107
+ ],
108
+ },
110
109
  }
111
110
  ```
112
111
 
113
- After editing `keybindings.json`, run `/reload` to apply.
112
+ ---
114
113
 
115
- ## The frame
114
+ ## Structure & Status Segments
116
115
 
117
- The editor's content is boxed with rounded corner glyphs and two live status rails painted into the top and bottom borders.
116
+ The editor window features status indicators embedded along the frame borders:
118
117
 
119
- ### Top border (left)
118
+ | Location | Segment | Description |
119
+ | ---------------- | -------------- | --------------------------------------------------------------------- |
120
+ | **Top Left** | **model** | Active model name and provider |
121
+ | | **reasoning** | Current reasoning effort level, tinted with pi thinking tokens |
122
+ | | **spinner** | Active phase indicator (`thinking`, `outputting`, `toolcall`, `exec`) |
123
+ | **Top Right** | **ctx** | Context usage percentage and token counts with color alerts |
124
+ | **Bottom Left** | **agent mode** | Active agent indicator pill from `pi-agent-manager` |
125
+ | **Bottom Right** | **cwd** | Shortened path, active Git branch, and uncommitted file counts |
120
126
 
121
- - **model** — active model name + provider, accented.
122
- - **reasoning** — current thinking level, tinted with pi's own thinking token (`thinkingLow` … `thinkingXhigh`).
123
- - **spinner** — streaming phase (`thinking` / `outputting` / `toolcall` / `exec`), replaces the left slot while active.
127
+ ---
124
128
 
125
- ### Top border (right)
129
+ ## Commands & Keybindings
126
130
 
127
- - **ctx** — context window usage: percentage + `used/window` tokens. Color winds traffic light (green → warning at ≥50% → red at ≥80%).
131
+ - **Toggle Command**: `/zen_mode` — Toggles Zen mode on and off.
132
+ - **Default Keybinding**: `ctrl+shift+z`
128
133
 
129
- ### Bottom border (left)
134
+ To rebind or disable the hotkey, update `~/.pi/agent/keybindings.json`:
130
135
 
131
- - **agent mode** — current pi-agent-manager agent as a colored pill (optional, no hard dependency).
132
-
133
- ### Bottom border (right)
136
+ ```jsonc
137
+ {
138
+ "piZenFrame.zenMode": "ctrl+shift+z", // Set to [] to disable the shortcut
139
+ }
140
+ ```
134
141
 
135
- - **cwd** — working directory (shortened) + git branch and dirty file count.
142
+ Run `/reload` after modifying your keybindings.
136
143
 
137
- All of these are colored with pi `ThemeColor`s, so they follow kanagawa, dark, etc.
144
+ ---
138
145
 
139
- ## Source layout
146
+ ## Directory Layout
140
147
 
141
148
  ```
142
149
  extensions/
143
- index.ts entry: config, events, editor install
144
- config/ types, constants, settings loading/normalization
145
- components/ header, frame, segments, registry (one file per segment)
146
- editor/ FrameEditor (frame + spinner rendering)
147
- utils/ agent-mode, git, path, string helpers
150
+ ├── index.ts # Extension entry point: config initialization and events
151
+ ├── config/ # Types, defaults, and settings normalization
152
+ ├── components/ # Header, frame border, and status segment modules
153
+ ├── editor/ # FrameEditor component for layout rendering
154
+ └── utils/ # Helpers for Git status, paths, and agent modes
155
+
148
156
  ```
@@ -32,6 +32,17 @@ export interface HeaderEnv {
32
32
  const RANDOM_TIP =
33
33
  HEADER_TIPS[Math.floor(Math.random() * HEADER_TIPS.length)]?.text ?? "";
34
34
 
35
+ function wrapLines(
36
+ lines: string[],
37
+ maxLen: number,
38
+ style?: (line: string) => string,
39
+ ): string[] {
40
+ const wrapped = lines.map((l) => wrapTextWithAnsi(l, maxLen)).flat();
41
+ if (style) return wrapped.map((l) => (l.length ? style(l) : l));
42
+
43
+ return wrapped;
44
+ }
45
+
35
46
  export function createHeader(
36
47
  _tui: TUI,
37
48
  theme: Theme,
@@ -57,29 +68,6 @@ export function createHeader(
57
68
  color: settings.header?.logoColor ?? DEFAULT_SETTINGS.header?.logoColor,
58
69
  };
59
70
 
60
- const leftLines = [
61
- ...logo.lines.map((l) => theme.fg(logo.color, l)),
62
-
63
- "",
64
- "",
65
-
66
- theme.bold(
67
- theme.fg(
68
- "muted",
69
- settings.header?.heading ?? DEFAULT_SETTINGS.header?.heading,
70
- ),
71
- ),
72
-
73
- "",
74
-
75
- theme.italic(
76
- theme.fg(
77
- "muted",
78
- settings.header?.subheading ?? DEFAULT_SETTINGS.header?.subheading,
79
- ),
80
- ),
81
- ];
82
-
83
71
  const border = (s: string, fg?: ThemeColor) => theme.fg(fg ?? accentColor, s);
84
72
 
85
73
  /** Space-pad `text` so it sits horizontally centered within `inner` cols. */
@@ -106,7 +94,7 @@ export function createHeader(
106
94
  }
107
95
 
108
96
  /** Right column halves: top = model(+provider), bottom = cwd + git. */
109
- function infoRows(env: HeaderEnv): string[] {
97
+ function infoRows(env: HeaderEnv, width: number): string[] {
110
98
  const icons = DEFAULT_ICONS;
111
99
  const parts: string[] = [];
112
100
 
@@ -123,46 +111,74 @@ export function createHeader(
123
111
 
124
112
  return [
125
113
  theme.bold(border("Model Info")),
126
- env.modelName ? theme.fg("muted", `${icons.model} ${env.modelName}`) : "",
114
+
115
+ ...wrapLines(
116
+ [
117
+ env.modelName
118
+ ? theme.fg("muted", `${icons.model} ${env.modelName}`)
119
+ : "",
120
+ ],
121
+ width,
122
+ (str) => theme.fg("muted", str),
123
+ ),
124
+
127
125
  "",
126
+
128
127
  theme.bold(border("Current Directory")),
129
128
  parts.length ? parts.join(" · ") : "",
129
+
130
130
  "",
131
+
131
132
  theme.bold(border("Tip")),
132
- theme.fg("muted", RANDOM_TIP),
133
+ ...wrapLines([RANDOM_TIP], width, (str) => theme.fg("muted", str)),
133
134
  ];
134
135
  }
135
136
 
136
137
  return {
137
138
  render(width: number): string[] {
138
- // Too narrow for a box → fall back to a plain centered logo.
139
- if (width < MIN_BOX_WIDTH) {
140
- return leftLines;
141
- }
142
-
143
139
  const env = getEnv(pi);
144
- const rightRows = infoRows(env);
145
140
 
146
141
  const inner = Math.max(0, width - 2);
147
142
  const leftW = Math.floor(inner * LEFT_COL_RATIO);
148
- const rightW = inner - leftW - 1; // divider column
143
+
144
+ const rightW = inner - leftW - 1;
145
+
146
+ const rightLines = infoRows(env, rightW);
147
+ const leftLines = [
148
+ ...logo.lines.map((l) => theme.fg(logo.color, l)),
149
+
150
+ "",
151
+ "",
152
+
153
+ ...wrapLines(
154
+ [settings.header?.heading ?? DEFAULT_SETTINGS.header?.heading ?? ""],
155
+ leftW,
156
+ ).map((line) => theme.bold(theme.fg("muted", line))),
157
+
158
+ "",
159
+
160
+ ...wrapLines(
161
+ [
162
+ settings.header?.subheading ??
163
+ DEFAULT_SETTINGS.header?.subheading ??
164
+ "",
165
+ ],
166
+ leftW,
167
+ ).map((line) => theme.italic(theme.fg("muted", line))),
168
+ ];
169
+
170
+ // Too narrow for a box → fall back to a plain centered logo.
171
+ if (width < MIN_BOX_WIDTH) {
172
+ return leftLines;
173
+ }
149
174
 
150
175
  // No truncation: overflow wraps onto following lines until it all fits.
151
- const leftWrapped = leftLines
152
- .map((l) => wrapTextWithAnsi(l, leftW))
153
- .flat();
154
- const rightWrapped = rightRows
155
- .map((r) => wrapTextWithAnsi(r, Math.max(1, rightW - 2))) // 2-col indent
156
- .flat();
157
-
158
- const height = Math.max(leftWrapped.length, rightWrapped.length);
159
- const logoTop = Math.max(
160
- 0,
161
- Math.floor((height - leftWrapped.length) / 2),
162
- );
176
+ const height = Math.max(leftLines.length, rightLines.length);
177
+ const logoTop = Math.max(0, Math.floor((height - leftLines.length) / 2));
178
+
163
179
  const rightTop = Math.max(
164
180
  0,
165
- Math.floor((height - rightWrapped.length) / 2),
181
+ Math.floor((height - rightLines.length) / 2),
166
182
  );
167
183
 
168
184
  const lines: string[] = [""];
@@ -172,12 +188,10 @@ export function createHeader(
172
188
 
173
189
  for (let i = 0; i < height; i++) {
174
190
  const l = i - logoTop;
175
- const leftLine =
176
- l >= 0 && l < leftWrapped.length ? leftWrapped[l]! : "";
191
+ const leftLine = l >= 0 && l < leftLines.length ? leftLines[l]! : "";
177
192
 
178
193
  const r = i - rightTop;
179
- const rightLine =
180
- r >= 0 && r < rightWrapped.length ? rightWrapped[r]! : "";
194
+ const rightLine = r >= 0 && r < rightLines.length ? rightLines[r]! : "";
181
195
 
182
196
  lines.push(splitRow(leftW, rightW, leftLine, rightLine));
183
197
  }
package/package.json CHANGED
@@ -2,13 +2,14 @@
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.11.0",
5
+ "version": "0.12.1",
6
6
  "license": "MIT",
7
7
  "repository": "github.com/leo-alvarenga/pi-mono",
8
8
  "publishConfig": {
9
9
  "access": "public"
10
10
  },
11
11
  "keywords": [
12
+ "pi-extension",
12
13
  "pi-package",
13
14
  "pi"
14
15
  ],