@leo-alvarenga/pi-zen-frame 0.16.1 → 0.18.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.
Files changed (33) hide show
  1. package/README.md +13 -15
  2. package/extensions/components/agent-mode.ts +1 -2
  3. package/extensions/components/cwd.ts +2 -2
  4. package/extensions/components/header.ts +8 -18
  5. package/extensions/components/notify.ts +13 -0
  6. package/extensions/components/reasoning.ts +1 -1
  7. package/extensions/components/registry.ts +2 -0
  8. package/extensions/components/spinner.ts +2 -2
  9. package/extensions/components/token-count.ts +2 -3
  10. package/extensions/components/types.ts +4 -4
  11. package/extensions/config/constants/contracts.ts +7 -9
  12. package/extensions/config/constants/defaults.ts +1 -1
  13. package/extensions/config/constants/index.ts +1 -6
  14. package/extensions/config/constants/visuals.ts +10 -7
  15. package/extensions/config/settings.ts +2 -2
  16. package/extensions/config/types.ts +12 -15
  17. package/extensions/editor/blocky-editor.ts +69 -116
  18. package/extensions/editor/minimalist-editor.ts +99 -0
  19. package/extensions/editor/palette/index.ts +4 -0
  20. package/extensions/editor/palette/input.ts +85 -0
  21. package/extensions/editor/palette/items.ts +58 -0
  22. package/extensions/editor/palette/render.ts +81 -0
  23. package/extensions/editor/palette/types.ts +13 -0
  24. package/extensions/editor/render.ts +79 -0
  25. package/extensions/editor/spinner.ts +42 -0
  26. package/extensions/index.ts +19 -35
  27. package/extensions/renderers/registry.ts +14 -5
  28. package/extensions/renderers/types.ts +7 -17
  29. package/extensions/utils/git.ts +1 -1
  30. package/extensions/utils/index.ts +0 -1
  31. package/extensions/utils/path.ts +1 -1
  32. package/extensions/utils/string.ts +1 -1
  33. package/package.json +1 -1
package/README.md CHANGED
@@ -10,6 +10,7 @@ A [`pi-coding-agent`](https://github.com/earendil-works/pi) extension that gives
10
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
+ - **Command Palette**: `alt+p` opens a filterable list of built-in and registered slash commands
13
14
 
14
15
  ---
15
16
 
@@ -42,10 +43,7 @@ Configuration is loaded from `~/.pi/agent/pi-zen-frame.json`. All properties are
42
43
  // Fallback accent color for frame border and active segments
43
44
  "accentColor": "accent",
44
45
 
45
- // Master mute: renders all segments in muted tones except agent-mode (default: false)
46
- "zenMode": false,
47
-
48
- // Editor-frame renderer by registered name (built-in: "blocky")
46
+ // Editor-frame renderer by registered name (built-in: "blocky", "minimalist")
49
47
  "editorFrame": "blocky",
50
48
 
51
49
  "header": {
@@ -90,27 +88,27 @@ Configuration is loaded from `~/.pi/agent/pi-zen-frame.json`. All properties are
90
88
 
91
89
  The editor frame shows status indicators in two rows below the editor:
92
90
 
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 |
91
+ | Location | Segment | Description |
92
+ | ------------------ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | Editor band (left) | **agent mode** | Active agent indicator pill from `pi-agent-manager` |
94
+ | | **model** | Active model name and provider |
95
+ | | **reasoning** | Current reasoning effort level, tinted with pi thinking tokens |
98
96
  | | **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 |
97
+ | Footer (left) | **cwd** | Shortened path, active Git branch, and uncommitted file counts |
98
+ | Footer (right) | **ctx** | Context usage percentage and token counts with color alerts |
101
99
 
102
100
  ---
103
101
 
104
102
  ## Commands & Keybindings
105
103
 
106
- - **Toggle Command**: `/zen_mode` — Toggles Zen mode on and off.
107
- - **Default Keybinding**: `ctrl+shift+z`
104
+ - **Command Palette**: `alt+p` — filter and insert built-in or registered slash commands.
105
+ - **Palette Keys**: `↑`/`↓` navigate, `Enter` inserts the command, `Esc` closes.
108
106
 
109
107
  To rebind or disable the hotkey, update `~/.pi/agent/keybindings.json`:
110
108
 
111
109
  ```jsonc
112
110
  {
113
- "piZenFrame.zenMode": "ctrl+shift+z", // Set to [] to disable the shortcut
111
+ "piZenFrame.palette": "alt+p", // Set to [] to disable the shortcut
114
112
  }
115
113
  ```
116
114
 
@@ -125,7 +123,7 @@ extensions/
125
123
  ├── index.ts # Extension entry point: config initialization and events
126
124
  ├── config/ # Types, defaults, and settings normalization
127
125
  ├── components/ # Status segments, header, and frame layout helpers
128
- ├── editor/ # BlockyEditor editor-frame renderer
126
+ ├── editor/ # Editor-frame renderers (blocky / minimalist) + palette
129
127
  ├── renderers/ # Renderer registry + swap-in API (blocky / basic)
130
128
  └── utils/ # Helpers for Git status, paths, agent modes, and token usage
131
129
 
@@ -3,7 +3,7 @@ import { capitalize } from "../utils";
3
3
  import type { SegmentDef } from "./types";
4
4
 
5
5
  /** Pseudo-footer (left): the current Agent Mode from pi-agent-manager
6
- * (when installed). Rendered as a pill using the agent's own color/icon. */
6
+ * (when installed). Rendered as a pill using the agent's own color/icon */
7
7
  export const agentModeSegment: SegmentDef = {
8
8
  id: "agent-mode",
9
9
  slot: "topLeft",
@@ -13,7 +13,6 @@ export const agentModeSegment: SegmentDef = {
13
13
  const m = d.agentMode;
14
14
  if (!m) return "";
15
15
 
16
- // Agent's own color wins; accent fills in. Zen never mutes the agent.
17
16
  const color = m.color ?? d.accentColor;
18
17
  const label = ` ${capitalize(m.name)} `;
19
18
 
@@ -15,11 +15,11 @@ export const cwdSegment: SegmentDef = {
15
15
  let git = ` ${icons.gitBranch} ${d.gitBranch}`;
16
16
  text +=
17
17
  theme.fg("dim", " ·") +
18
- theme.fg(d.zenMode ? "muted" : d.accentColor, git);
18
+ theme.fg(d.accentColor, git);
19
19
 
20
20
  if (d.gitDirty > 0) {
21
21
  text += theme.fg(
22
- d.zenMode ? "muted" : "error",
22
+ "error",
23
23
  ` ${icons.gitDirty} ${d.gitDirty}`,
24
24
  );
25
25
  }
@@ -6,10 +6,7 @@ import type {
6
6
  ThemeColor,
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
 
9
- import {
10
- DEFAULT_ICONS,
11
- HEADER_TIPS,
12
- } from "../config/constants";
9
+ import { DEFAULT_ICONS, HEADER_TIPS } from "../config/constants";
13
10
  import type { Settings } from "../config/types";
14
11
  import { fitFrameRow } from "./frame";
15
12
  import { getShortCwd } from "../utils";
@@ -19,12 +16,7 @@ const MIN_BOX_WIDTH = 20;
19
16
  const LEFT_COL_RATIO = 0.4; // logo column width / total width
20
17
 
21
18
  /** BasicHeader preset styling — self-contained, not user-configurable. */
22
- const LOGO_LINES = [
23
- "█████████ ",
24
- "███ ███ ",
25
- "██████ ",
26
- "███ ███",
27
- ];
19
+ const LOGO_LINES = ["█████████ ", "███ ███ ", "██████ ", "███ ███"];
28
20
  const LOGO_COLOR: ThemeColor = "text";
29
21
  const HEADING = "Welcome back!";
30
22
  const SUBHEADING =
@@ -160,17 +152,15 @@ export function createHeader(
160
152
  "",
161
153
  "",
162
154
 
163
- ...wrapLines(
164
- [HEADING],
165
- leftW - 2,
166
- ).map((line) => theme.bold(theme.fg("muted", line))),
155
+ ...wrapLines([HEADING], leftW - 2).map((line) =>
156
+ theme.bold(theme.fg("muted", line)),
157
+ ),
167
158
 
168
159
  "",
169
160
 
170
- ...wrapLines(
171
- [SUBHEADING],
172
- leftW - 2,
173
- ).map((line) => theme.italic(theme.fg("muted", line))),
161
+ ...wrapLines([SUBHEADING], leftW - 2).map((line) =>
162
+ theme.italic(theme.fg("muted", line)),
163
+ ),
174
164
  ];
175
165
 
176
166
  // Too narrow for a box → fall back to a plain centered logo.
@@ -0,0 +1,13 @@
1
+ import type { SegmentDef } from "./types";
2
+
3
+ /** Box bottom (left): pi-notify bell, right before the model segment.
4
+ * Accent when notifications are on, muted bell-off otherwise */
5
+ export const notifySegment: SegmentDef = {
6
+ id: "notify",
7
+ slot: "topLeft",
8
+ render: (d, { icons, theme }) => {
9
+ const color = d.notifyEnabled ? d.accentColor : "muted";
10
+
11
+ return theme.fg(color, ` ${d.notifyEnabled ? icons.bell : icons.bellOff} `);
12
+ },
13
+ };
@@ -16,7 +16,7 @@ export const reasoningSegment: SegmentDef = {
16
16
  "thinkingText") as ThemeColor;
17
17
 
18
18
  return theme.fg(
19
- d.zenMode ? "muted" : token,
19
+ token,
20
20
  ` ${icons.thinking} ${d.thinkingLevel} `,
21
21
  );
22
22
  },
@@ -5,6 +5,7 @@
5
5
  * The frame calls `segmentsFor(slot, ...)` and handles all width/joining.
6
6
  */
7
7
  import { modelSegment } from "./model";
8
+ import { notifySegment } from "./notify";
8
9
  import { reasoningSegment } from "./reasoning";
9
10
  import { spinnerSegment } from "./spinner";
10
11
  import { tokenCountSegment } from "./token-count";
@@ -14,6 +15,7 @@ import type { FrameData, SegmentContext, SegmentDef, Slot } from "./types";
14
15
 
15
16
  export const segments: SegmentDef[] = [
16
17
  agentModeSegment,
18
+ notifySegment,
17
19
  modelSegment,
18
20
  reasoningSegment,
19
21
  spinnerSegment,
@@ -12,9 +12,9 @@ export const spinnerSegment: SegmentDef = {
12
12
  render: (d, { theme }) => {
13
13
  let phase = d.spinnerPhase ?? "thinking";
14
14
 
15
- // No frame.colors key for the spinner; zen mutes it, else accent.
15
+ // No frame.colors key for the spinner; accent only.
16
16
  const spinner = theme.fg(
17
- d.zenMode ? "muted" : d.accentColor,
17
+ d.accentColor,
18
18
  ` ${d.spinnerFrame} `,
19
19
  );
20
20
  if (phase === "idle") return spinner;
@@ -44,13 +44,12 @@ export const tokenCountSegment: SegmentDef = {
44
44
  }
45
45
  }
46
46
 
47
- const color2 = d.zenMode ? "muted" : color;
48
47
  const used = tokens === null ? "?" : formatTokens(tokens);
49
48
 
50
49
  return (
51
- theme.fg(color2, ` ${icons.context} ctx ${pct} `) +
50
+ theme.fg(color, ` ${icons.context} ctx ${pct} `) +
52
51
  theme.fg("dim", "·") +
53
- theme.fg(color2, ` ${used}/${formatWindow(window)} `) +
52
+ theme.fg(color, ` ${used}/${formatWindow(window)} `) +
54
53
  theme.fg("dim", "·") +
55
54
  theme.fg(
56
55
  "dim",
@@ -49,13 +49,13 @@ export interface FrameData {
49
49
  spinnerPhase: SpinnerPhase | null;
50
50
  thinkingLevel: string | undefined;
51
51
 
52
- /** Master mute: when true, every segment except agentMode renders muted. */
53
- zenMode: boolean;
54
-
55
52
  /** To render the agent mode (from @leo-alvarenga/pi-agent-manager) segment, if any */
56
53
  agentMode: AgentMode;
57
54
 
58
55
  context: TokenUsage | null;
56
+
57
+ /** pi-notify toggle state (true = notifications enabled). */
58
+ notifyEnabled: boolean;
59
59
  }
60
60
 
61
61
  /** Data + rendering helpers handed to every segment. */
@@ -85,7 +85,6 @@ export interface SegmentDef {
85
85
  export interface ExternalData {
86
86
  cwd: string;
87
87
  gitDirty: number;
88
- zenMode: boolean;
89
88
  agentMode: AgentMode;
90
89
  theme: Theme | undefined;
91
90
  gitBranch: string | undefined;
@@ -94,4 +93,5 @@ export interface ExternalData {
94
93
  modelProvider: string | undefined;
95
94
  thinkingLevel: string | undefined;
96
95
  spinnerPhase: SpinnerPhase | null;
96
+ notifyEnabled: boolean;
97
97
  }
@@ -1,12 +1,10 @@
1
- /** External contract IDs: the zen-mode keybinding and the optional
2
- * pi-agent-manager integration (no hard dependency).
3
- */
4
- /** pi-agent-manager integration (optional — no hard dependency). */
1
+ /** pi-agent-manager integration (optional: no hard dependency) */
5
2
  export const PI_AGENT_MANAGER_AGENT_EVENT = "pi-agent-manager:agent-changed";
6
3
  export const PI_AGENT_MANAGER_AGENT_DATA_KEY = "pi-agent-manager-agent";
7
4
 
8
- /** Keybinding id users bind in keybindings.json to toggle zen mode. */
9
- export const ZEN_MODE_SHORTCUT_ID = "piZenFrame.zenMode";
10
- /** Default key when the user hasn't bound one (override or disable via
11
- * keybindings.json; `[]` disables the shortcut, `/zen_mode` still works). */
12
- export const ZEN_MODE_DEFAULT_KEY = "ctrl+shift+z";
5
+ /** pi-notify integration (optional: no hard dependency) */
6
+ export const PI_NOTIFY_TOGGLE_EVENT = "pi-notify:toggle-changed";
7
+
8
+ /** Keybinding id users bind in keybindings.json to open the command palett. */
9
+ export const PALETTE_SHORTCUT_ID = "piZenFrame.palette";
10
+ export const PALETTE_DEFAULT_KEY = "alt+p";
@@ -1,7 +1,7 @@
1
1
  import type { Settings } from "../types";
2
2
  import { WORKING_MESSAGES } from "./messages";
3
+
3
4
  export const DEFAULT_SETTINGS: Settings = {
4
- zenMode: false,
5
5
  accentColor: "accent",
6
6
  editorFrame: "blocky",
7
7
 
@@ -1,10 +1,5 @@
1
- /**
2
- * Constants, grouped by concern and re-exported from a single barrel.
3
- * Import everything with `import { ... } from "../config/constants"` —
4
- * resolution keeps working now that this is a directory.
5
- */
6
1
  export * from "./file";
7
2
  export * from "./contracts";
8
3
  export * from "./visuals";
9
4
  export * from "./messages";
10
- export * from "./defaults";
5
+ export * from "./defaults";
@@ -1,6 +1,7 @@
1
1
  import type { ThemeColor } from "@earendil-works/pi-coding-agent";
2
2
  import type { FrameIcons, SpinnerPhase } from "../types";
3
- /** Spinner animation frames per phase (pi-editor-shell style). */
3
+
4
+ /** Spinner animation frames per phase (pi-editor-shell style) */
4
5
  export const SPINNER_FRAMES: Record<SpinnerPhase, string[]> = {
5
6
  idle: ["⠃", "⠞", "⡵", "⠿", "⢹", "⠄"],
6
7
  outputting: ["⠋", "⠙", "⠸", "⠴", "⠦", "⠇", "⠏"],
@@ -11,7 +12,7 @@ export const SPINNER_FRAMES: Record<SpinnerPhase, string[]> = {
11
12
  // Sample ["░", "▒", "▓", "█", "▓", "▒"],
12
13
  };
13
14
 
14
- /** Nerd Font glyphs used by the blocky editor preset. */
15
+ /** Nerd Font glyphs used by the blocky editor preset */
15
16
  export const DEFAULT_ICONS: FrameIcons = {
16
17
  model: "󰣖",
17
18
  folder: " ",
@@ -21,17 +22,19 @@ export const DEFAULT_ICONS: FrameIcons = {
21
22
  gitDirty: "",
22
23
  thinking: "󰌶",
23
24
  gitBranch: "",
25
+ bell: "󰂚",
26
+ bellOff: "󰂛",
24
27
  };
25
28
 
26
- /** Thinking level → theme token, mirroring pi's own border-color mapping. */
29
+ /** Thinking level → theme token, mirroring pi's own border-color mapping */
27
30
  export const THINKING_TOKEN: Record<string, string> = {
28
- off: "thinkingOff",
29
- minimal: "thinkingMinimal",
30
31
  low: "thinkingLow",
31
- medium: "thinkingMedium",
32
+ max: "thinkingMax",
33
+ off: "thinkingOff",
32
34
  high: "thinkingHigh",
33
35
  xhigh: "thinkingXhigh",
34
- max: "thinkingMax",
36
+ medium: "thinkingMedium",
37
+ minimal: "thinkingMinimal",
35
38
  };
36
39
 
37
40
  export const THEME_COLORS: Record<ThemeColor, true> = {
@@ -19,7 +19,7 @@ function loadSettingsFile(): Promise<string> {
19
19
  return readFile(getResolvedSettingsFilePath(), "utf8");
20
20
  }
21
21
 
22
- /** Validate a raw (possibly malformed) config against the schema shapes. */
22
+ /** Validate a raw (possibly malformed) config against the schema shapes */
23
23
  function normalize(raw: unknown): Settings {
24
24
  if (!raw || typeof raw !== "object") return DEFAULT_SETTINGS;
25
25
 
@@ -37,7 +37,6 @@ function normalize(raw: unknown): Settings {
37
37
  const out: Settings = {
38
38
  ...DEFAULT_SETTINGS,
39
39
  frame: { ...DEFAULT_SETTINGS.frame },
40
- zenMode: bool(r.zenMode, d.zenMode ?? true),
41
40
  editorFrame:
42
41
  typeof r.editorFrame === "string" ? r.editorFrame : d.editorFrame,
43
42
  };
@@ -95,6 +94,7 @@ function normalize(raw: unknown): Settings {
95
94
  export async function loadSettings(): Promise<Settings> {
96
95
  try {
97
96
  const raw = JSON.parse(await loadSettingsFile()) as unknown;
97
+
98
98
  return normalize(raw);
99
99
  } catch {
100
100
  return DEFAULT_SETTINGS;
@@ -1,6 +1,6 @@
1
1
  import type { ThemeColor } from "@earendil-works/pi-coding-agent";
2
2
 
3
- /** Streaming phase used to drive the status-animation spinner. */
3
+ /** Streaming phase used to drive the status-animation spinner */
4
4
  export type SpinnerPhase =
5
5
  "thinking" | "outputting" | "toolcall" | "exec" | "idle";
6
6
 
@@ -9,7 +9,7 @@ export interface BannerTip {
9
9
  text: string;
10
10
  }
11
11
 
12
- /** Nerd-font / glyph icons shown in the band and status rows. Overridable. */
12
+ /** Nerd-font / glyph icons shown in the band and status rows. Overridable */
13
13
  export interface FrameIcons {
14
14
  folder: string;
15
15
  model: string;
@@ -19,12 +19,14 @@ export interface FrameIcons {
19
19
  thinking: string;
20
20
  gitDirty: string;
21
21
  gitBranch: string;
22
+ bell: string;
23
+ bellOff: string;
22
24
  }
23
25
 
24
26
  /**
25
- * ``frame`` — the editor frame preset. Styling and layout (prefix, colors,
27
+ * ``frame``: the editor frame preset. Styling and layout (prefix, colors,
26
28
  * margins, paddings) are owned by the renderer; only these behavioral
27
- * toggles are configurable.
29
+ * toggles are configurable
28
30
  */
29
31
  export interface FrameSettings {
30
32
  enable?: boolean;
@@ -42,7 +44,7 @@ export interface FrameSettings {
42
44
  showSpinner?: boolean;
43
45
  }
44
46
 
45
- /** `header` — the top-line header, which can show a logo or other text */
47
+ /** `header`: the top-line header, which can show a logo or other text */
46
48
  export interface HeaderSettings {
47
49
  enable: boolean;
48
50
 
@@ -50,15 +52,15 @@ export interface HeaderSettings {
50
52
  type?: string;
51
53
  }
52
54
 
53
- /** `workingMessage` — rotating messages in pi's built-in working loader. */
55
+ /** `workingMessage`: rotating messages in pi's built-in working loader */
54
56
  export interface WorkingMessageSettings {
55
- /** Toggle the rotating messages. Default true. */
57
+ /** Toggle the rotating messages. Default true */
56
58
  enable?: boolean;
57
59
 
58
- /** How often (ms) the message is replaced. Default 3000. */
60
+ /** How often (ms) the message is replaced. Default 3000 */
59
61
  intervalMs?: number;
60
62
 
61
- /** Custom message pool; replaces the default 30. */
63
+ /** Custom message pool; replaces the default 30 */
62
64
  messages?: string[];
63
65
  }
64
66
 
@@ -67,14 +69,9 @@ export interface Settings {
67
69
  header?: HeaderSettings;
68
70
  workingMessage?: WorkingMessageSettings;
69
71
 
70
- /** Editor-frame renderer by registered name. Default "blocky". */
72
+ /** Editor-frame renderer by registered name. Default "blocky" */
71
73
  editorFrame?: string;
72
74
 
73
- /** Master mute: every segment except agent-mode renders muted.
74
- * Default true. Toggle at runtime with `/zen_mode` or the `piZenFrame.zenMode`
75
- * keybinding (default `ctrl+shift+z`). */
76
- zenMode?: boolean;
77
-
78
75
  /** Accent color for segment highlights; Defaults to 'accent' */
79
76
  accentColor?: ThemeColor;
80
77
  }