@xynogen/pix-optimizer 1.1.8 → 1.1.10

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
@@ -4,10 +4,10 @@ Token-optimization suite for Pi Coding Agent. Four tools wired into one
4
4
  extension via `src/index.ts`, fronted by a single `/optimizer` command and one
5
5
  shared status-bar cell:
6
6
 
7
- - **Caveman** (`󰜐`) — terse-output system prompt
8
- - **RTK** (`󰓥`) — prefixes shell commands with `rtk` + injects RTK prompt
9
- - **TOON** (`󰗀`) — jq + TOON guidance for dense JSON (skill lives in pix-skills)
10
- - **Ponytail** (`󰆐`) — lazy-senior-dev system prompt (minimal code, YAGNI)
7
+ - **Caveman** (`Cv`) — terse-output system prompt
8
+ - **RTK** (`Rk`) — prefixes shell commands with `rtk` + injects RTK prompt
9
+ - **TOON** (`Tn`) — jq + TOON guidance for dense JSON (skill lives in pix-skills)
10
+ - **Ponytail** (`Pt`) — lazy-senior-dev system prompt (minimal code, YAGNI)
11
11
 
12
12
  ## Command
13
13
 
@@ -25,12 +25,31 @@ State persists to `~/.pi/agent/optimizer.json` (caveman/rtk/toon/ponytail).
25
25
 
26
26
  ## Status bar
27
27
 
28
- A single cell always shows the enabled icons in a fixed order (`󰜐 󰓥 󰗀 󰆐`), color-
29
- coded by state: **accent** when the tool is enabled, **dim** when disabled.
28
+ A single cell always shows all four tool icons in a fixed order, color-coded by
29
+ state: **accent** when the tool is enabled, **dim** when disabled.
30
+
31
+ ### Icon style (Nerd Font, Unicode, or ASCII)
32
+
33
+ The default glyphs (Nerd Font PUA codepoints) are **Nerd Font** symbols and require a patched
34
+ font (e.g. MesloLGS NF). Terminals without one render them as missing-glyph
35
+ “tofu” boxes. Two font-independent fallbacks are available:
36
+
37
+ | Mode | Glyphs | Needs Nerd Font? |
38
+ |---|---|---|
39
+ | `nerd` (default) | Nerd Font PUA glyphs | yes |
40
+ | `unicode` | `♤ ♡ ♢ ♧` (outline card suits) | no |
41
+ | `ascii` | `Cv Rk Tn Pt` | no |
42
+
43
+ Switch the style live from the **`/optimizer` overlay** — a fifth `icons` row
44
+ at the bottom cycles `nerd → unicode → ascii` with `←→`; the choice persists to
45
+ `~/.pi/agent/optimizer.json`.
46
+
47
+ Icons follow the **global** `pix-pretty` mode — set via `/pretty` or `PRETTY_ICONS`
48
+ env var. The optimizer no longer has its own toggle.
30
49
 
31
50
  ## Features
32
51
 
33
- ### Caveman Mode (`󰜐`)
52
+ ### Caveman Mode (`Cv`)
34
53
 
35
54
  Cuts ~75% of output tokens while keeping full technical accuracy.
36
55
 
@@ -44,7 +63,7 @@ Cuts ~75% of output tokens while keeping full technical accuracy.
44
63
  The `/optimizer` overlay opens a settings dialog when needed. Default level
45
64
  for new sessions is restored from `~/.pi/agent/optimizer.json`.
46
65
 
47
- ### RTK Tool Rewriting (`󰓥`)
66
+ ### RTK Tool Rewriting (`Rk`)
48
67
 
49
68
  Two layers, both active automatically:
50
69
 
@@ -65,7 +84,7 @@ Two layers, both active automatically:
65
84
  cargo install rtk-ai
66
85
  ```
67
86
 
68
- ### TOON / JSON Compression (`󰗀`)
87
+ ### TOON / JSON Compression (`Tn`)
69
88
 
70
89
  Guidance for handling information-dense JSON via `jq` (query/reshape) and
71
90
  `toon` (compress). The system-prompt nudge is injected **only when the user
@@ -82,7 +101,7 @@ The `toon-json` skill (full workflow + when-NOT-to-use guidance) is bundled in
82
101
  npm i -g @toon-format/cli
83
102
  ```
84
103
 
85
- ### Ponytail Mode (`󰆐`)
104
+ ### Ponytail Mode (`Pt`)
86
105
 
87
106
  "Lazy senior dev" mode. Governs **what** the agent builds (minimal code,
88
107
  YAGNI), orthogonal to Caveman which governs **how** it talks — they pair. Before
@@ -117,7 +136,7 @@ pi install npm:@xynogen/pix-optimizer
117
136
  |-------------------|-----------------------------------------------------------|
118
137
  | `src/index.ts` | Wires the four tools + shared status, registers `/optimizer` |
119
138
  | `src/opt.ts` | The `/optimizer` overlay UI (keyboard nav + cycling) |
120
- | `src/status.ts` | Shared status-bar cell + `OptimizerHandle` contract |
139
+ | `src/status.ts` | Shared status-bar cell (`toolIcon()` → shared `pix-pretty` catalog) |
121
140
  | `src/caveman.ts` | Caveman logic, levels, prompt |
122
141
  | `src/rtk.ts` | RTK prompt + bash command rewriting |
123
142
  | `src/json.ts` | jq+TOON guidance, heuristics, system-prompt injection |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-optimizer",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + jq/TOON JSON compression + ponytail lazy-dev mode",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -7,9 +7,10 @@
7
7
  * - toon: jq + TOON guidance for dense JSON (+ bundled skill)
8
8
  * - ponytail: lazy-senior-dev system prompt (minimal code, YAGNI)
9
9
  *
10
- * They share ONE status-bar cell (󰜐 󰓥 󰗀 󰆐, all four always shown — dimmed when off, accented when on) and ONE
11
- * command (/optimizer — an interactive overlay). index.ts wires lifecycle hooks
12
- * via each module, then registers the overlay command from their handles.
10
+ * They share ONE status-bar cell (all four icons always shown — dimmed when
11
+ * off, accented when on; icon style is nerd/unicode/ascii, set via /optimizer)
12
+ * and ONE command (/optimizer — an interactive overlay). index.ts wires
13
+ * lifecycle hooks via each module, then registers the overlay command.
13
14
  */
14
15
 
15
16
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -26,6 +27,7 @@ import {
26
27
  import { filterModelWarnings } from "./tool-result-filter.ts";
27
28
 
28
29
  export default function optimizer(pi: ExtensionAPI) {
30
+ // Icons follow the global pix-pretty mode (set via /pretty); no local state.
29
31
  const status = new OptimizerStatus();
30
32
 
31
33
  // Each module registers its own lifecycle hooks and returns a handle the
package/src/opt.ts CHANGED
@@ -16,12 +16,12 @@
16
16
 
17
17
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
18
  import { frameLines } from "@xynogen/pix-pretty/modal-frame";
19
- import {
20
- type OptimizerHandle,
21
- type OptimizerStatus,
22
- type OptimizerTool,
23
- TOOL_ICONS,
19
+ import type {
20
+ OptimizerHandle,
21
+ OptimizerStatus,
22
+ OptimizerTool,
24
23
  } from "./status.ts";
24
+ import { toolIcon } from "./status.ts";
25
25
 
26
26
  /** Min/max content width for the overlay box (excludes 4 cols of chrome). */
27
27
  const MIN_CONTENT = 28;
@@ -150,7 +150,7 @@ export function registerOptCommand(
150
150
  const on = handles[tool].current() !== "off";
151
151
  const sel = i === selected;
152
152
  const cursor = sel ? theme.fg("accent", "→") : " ";
153
- const icon = theme.fg(on ? "accent" : "dim", TOOL_ICONS[tool]);
153
+ const glyph = theme.fg(on ? "accent" : "dim", toolIcon(tool));
154
154
  const name = theme.fg(
155
155
  sel ? "accent" : on ? "text" : "muted",
156
156
  tool.padEnd(nameWidth),
@@ -164,7 +164,7 @@ export function registerOptCommand(
164
164
  on ? "accent" : "dim",
165
165
  levelBar(cur, handles[tool].values),
166
166
  );
167
- return `${cursor} ${icon} ${name} ${value} ${bar}`;
167
+ return `${cursor} ${glyph} ${name} ${value} ${bar}`;
168
168
  });
169
169
  const lines = [
170
170
  theme.fg("accent", theme.bold("󱎫 Optimizer")),
@@ -3,12 +3,18 @@ import {
3
3
  OptimizerStatus,
4
4
  renderStatus,
5
5
  STATUS_KEY,
6
- TOOL_ICONS,
6
+ toolIcon,
7
7
  } from "./status.ts";
8
8
 
9
9
  /** Tagging colorizer: <accent>X</accent> / <dim>X</dim> for assertions. */
10
10
  const tag = (c: string, t: string) => `<${c}>${t}</${c}>`;
11
11
 
12
+ // Icons resolve against the global pix-pretty mode (default "nerd" in tests).
13
+ const CV = toolIcon("caveman");
14
+ const RK = toolIcon("rtk");
15
+ const TN = toolIcon("toon");
16
+ const PT = toolIcon("ponytail");
17
+
12
18
  describe("renderStatus", () => {
13
19
  it("shows ALL icons in order, accent when enabled", () => {
14
20
  expect(
@@ -17,7 +23,7 @@ describe("renderStatus", () => {
17
23
  tag,
18
24
  ),
19
25
  ).toBe(
20
- `<accent>${TOOL_ICONS.caveman}</accent> <accent>${TOOL_ICONS.rtk}</accent> <accent>${TOOL_ICONS.toon}</accent> <accent>${TOOL_ICONS.ponytail}</accent> `,
26
+ `<accent>${CV}</accent> <accent>${RK}</accent> <accent>${TN}</accent> <accent>${PT}</accent> `,
21
27
  );
22
28
  });
23
29
 
@@ -28,21 +34,26 @@ describe("renderStatus", () => {
28
34
  tag,
29
35
  ),
30
36
  ).toBe(
31
- `<dim>${TOOL_ICONS.caveman}</dim> <accent>${TOOL_ICONS.rtk}</accent> <accent>${TOOL_ICONS.toon}</accent> <accent>${TOOL_ICONS.ponytail}</accent> `,
37
+ `<dim>${CV}</dim> <accent>${RK}</accent> <accent>${TN}</accent> <accent>${PT}</accent> `,
32
38
  );
33
39
  });
34
40
 
35
41
  it("all dim when nothing enabled (cell never empty)", () => {
36
42
  expect(renderStatus({}, tag)).toBe(
37
- `<dim>${TOOL_ICONS.caveman}</dim> <dim>${TOOL_ICONS.rtk}</dim> <dim>${TOOL_ICONS.toon}</dim> <dim>${TOOL_ICONS.ponytail}</dim> `,
43
+ `<dim>${CV}</dim> <dim>${RK}</dim> <dim>${TN}</dim> <dim>${PT}</dim> `,
38
44
  );
39
45
  });
40
46
 
41
47
  it("preserves fixed order regardless of insertion order", () => {
42
48
  expect(renderStatus({ toon: true, caveman: true }, tag)).toBe(
43
- `<accent>${TOOL_ICONS.caveman}</accent> <dim>${TOOL_ICONS.rtk}</dim> <accent>${TOOL_ICONS.toon}</accent> <dim>${TOOL_ICONS.ponytail}</dim> `,
49
+ `<accent>${CV}</accent> <dim>${RK}</dim> <accent>${TN}</accent> <dim>${PT}</dim> `,
44
50
  );
45
51
  });
52
+
53
+ it("resolves icons via the shared catalog (non-empty glyphs)", () => {
54
+ // toolIcon delegates to pix-pretty's icon(); every tool has a glyph.
55
+ expect([CV, RK, TN, PT].every((g) => g.length > 0)).toBe(true);
56
+ });
46
57
  });
47
58
 
48
59
  describe("OptimizerStatus", () => {
@@ -67,7 +78,7 @@ describe("OptimizerStatus", () => {
67
78
  expect(last.key).toBe(STATUS_KEY);
68
79
  // caveman + toon + ponytail still unset (dim), rtk accent.
69
80
  expect(last.text).toBe(
70
- `<dim>${TOOL_ICONS.caveman}</dim> <accent>${TOOL_ICONS.rtk}</accent> <dim>${TOOL_ICONS.toon}</dim> <dim>${TOOL_ICONS.ponytail}</dim> `,
81
+ `<dim>${CV}</dim> <accent>${RK}</accent> <dim>${TN}</dim> <dim>${PT}</dim> `,
71
82
  );
72
83
  });
73
84
 
@@ -78,7 +89,7 @@ describe("OptimizerStatus", () => {
78
89
  status.set("toon", true, ctx as never);
79
90
  const last = ctx.calls.at(-1)!;
80
91
  expect(last.text).toBe(
81
- `<accent>${TOOL_ICONS.caveman}</accent> <dim>${TOOL_ICONS.rtk}</dim> <accent>${TOOL_ICONS.toon}</accent> <dim>${TOOL_ICONS.ponytail}</dim> `,
92
+ `<accent>${CV}</accent> <dim>${RK}</dim> <accent>${TN}</accent> <dim>${PT}</dim> `,
82
93
  );
83
94
  });
84
95
 
@@ -89,7 +100,7 @@ describe("OptimizerStatus", () => {
89
100
  status.set("rtk", false, ctx as never);
90
101
  const last = ctx.calls.at(-1)!;
91
102
  expect(last.text).toBe(
92
- `<dim>${TOOL_ICONS.caveman}</dim> <dim>${TOOL_ICONS.rtk}</dim> <dim>${TOOL_ICONS.toon}</dim> <dim>${TOOL_ICONS.ponytail}</dim> `,
103
+ `<dim>${CV}</dim> <dim>${RK}</dim> <dim>${TN}</dim> <dim>${PT}</dim> `,
93
104
  );
94
105
  });
95
106
  });
package/src/status.ts CHANGED
@@ -17,6 +17,11 @@ import type {
17
17
  ExtensionContext,
18
18
  ThemeColor,
19
19
  } from "@earendil-works/pi-coding-agent";
20
+ import {
21
+ type IconKey,
22
+ icon,
23
+ onIconModeChange,
24
+ } from "@xynogen/pix-pretty/icon-catalog";
20
25
 
21
26
  /**
22
27
  * Each optimizer tool (caveman/rtk/toon/ponytail) exposes a handle so the
@@ -42,13 +47,24 @@ export const STATUS_KEY = "pix-optimizer";
42
47
  /** Tools that participate in the shared indicator, in render order. */
43
48
  export type OptimizerTool = "caveman" | "rtk" | "toon" | "ponytail";
44
49
 
45
- export const TOOL_ICONS: Record<OptimizerTool, string> = {
46
- caveman: "󰜐",
47
- rtk: "󰓥",
48
- toon: "󰗀",
49
- ponytail: "󰆐",
50
+ /**
51
+ * Icons come from the shared pix-pretty catalog and follow the ONE global icon
52
+ * mode (set via /pretty). The optimizer no longer owns a mode: each tool maps
53
+ * to a semantic catalog key, and the cell renders whatever the active mode
54
+ * resolves — nerd / unicode / ascii — with no per-package knob.
55
+ */
56
+ const TOOL_ICON_KEY: Record<OptimizerTool, IconKey> = {
57
+ caveman: "opt.caveman",
58
+ rtk: "opt.rtk",
59
+ toon: "opt.toon",
60
+ ponytail: "opt.ponytail",
50
61
  };
51
62
 
63
+ /** Current glyph for a tool, resolved against the active global icon mode. */
64
+ export function toolIcon(tool: OptimizerTool): string {
65
+ return icon(TOOL_ICON_KEY[tool]);
66
+ }
67
+
52
68
  /** Fixed left-to-right order of icons in the cell. */
53
69
  const TOOL_ORDER: readonly OptimizerTool[] = [
54
70
  "caveman",
@@ -68,7 +84,8 @@ export type Colorize = (color: ThemeColor, text: string) => string;
68
84
  /**
69
85
  * Build the colored status string for a set of tool states. ALL tool icons are
70
86
  * always shown, in TOOL_ORDER; each is accent-colored when its tool is enabled
71
- * and dim when disabled. `color` applies the theme color (e.g. theme.fg).
87
+ * and dim when disabled. Glyphs resolve against the active global icon mode
88
+ * (see /pretty). `color` applies the theme color (e.g. theme.fg).
72
89
  *
73
90
  * Pure + exported for tests (pass a tagging colorizer to assert per-icon color).
74
91
  * A trailing space separates the cell from the next status segment.
@@ -78,7 +95,7 @@ export function renderStatus(
78
95
  color: Colorize,
79
96
  ): string {
80
97
  return `${TOOL_ORDER.map((t) =>
81
- color(states[t] === true ? ENABLED_COLOR : DISABLED_COLOR, TOOL_ICONS[t]),
98
+ color(states[t] === true ? ENABLED_COLOR : DISABLED_COLOR, toolIcon(t)),
82
99
  ).join(" ")} `;
83
100
  }
84
101
 
@@ -89,6 +106,17 @@ export function renderStatus(
89
106
  */
90
107
  export class OptimizerStatus {
91
108
  private states: Partial<Record<OptimizerTool, boolean>> = {};
109
+ /** Latest ui ctx seen, so an icon-mode change can repaint without a toggle. */
110
+ private lastCtx: Pick<ExtensionContext, "ui"> | undefined;
111
+
112
+ constructor() {
113
+ // The cell is a pushed status (setStatus), so it does NOT auto-refresh
114
+ // when /pretty flips the global icon mode. Repaint it on mode change
115
+ // using the last-seen ctx (no-op until the cell has painted once).
116
+ onIconModeChange(() => {
117
+ if (this.lastCtx) this.paint(this.lastCtx);
118
+ });
119
+ }
92
120
 
93
121
  /** Current enabled state for one tool (undefined until first set). */
94
122
  get(tool: OptimizerTool): boolean | undefined {
@@ -107,6 +135,7 @@ export class OptimizerStatus {
107
135
 
108
136
  /** Repaint the shared cell from current states. */
109
137
  paint(ctx: Pick<ExtensionContext, "ui">): void {
138
+ this.lastCtx = ctx;
110
139
  const text = renderStatus(this.states, (c, t) => ctx.ui.theme.fg(c, t));
111
140
  ctx.ui.setStatus(STATUS_KEY, text);
112
141
  }