omp-uwu 0.5.0 → 0.6.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/README.md CHANGED
@@ -25,7 +25,7 @@ Restart omp. Defaults are **on**, **rewrite**, **mid**, **auto**, with colors **
25
25
  - `/uwu display` explicitly opts into **experimental, ANSI-TUI-only** display styling; never changes history or adds a prompt
26
26
  - `/uwu level low` / `/uwu level mid` / `/uwu level max` selects light, standard or stronger intensity
27
27
  - `/uwu locale auto` / `/uwu locale en` / `/uwu locale tr` selects critical-word protection and prompt language guidance
28
- - `/uwu status` reports settings and observed capabilities (unknown/pending is not a support guarantee)
28
+ - `/uwu status` opens a compact, host-themed TUI dashboard: **Controls** has switches, preset chips, an intensity meter and a live sample; **Compatibility** reports observed capabilities, not a support guarantee. ↑/↓ focuses a control, ←/→ or Enter/Space edits it, and Tab switches tabs. **Enter on Save** applies the draft once; **Esc cancels** without changing settings, colors or history. Navigation never saves. Other modes or hosts without custom UI keep the original plain summary.
29
29
  - `/uwu preview <text>` shows a deterministic sample with its original case, without changing any settings—even if uwu is off
30
30
  - `/uwu colors on` / `/uwu colors off` enables or disables the optional kawaii palette (off by default; preference persists)
31
31
 
@@ -80,7 +80,7 @@ Capability fallback is explicit: `/uwu status` distinguishes a missing `Containe
80
80
 
81
81
  ```sh
82
82
  omp plugin install omp-uwu # from npm (recommended)
83
- omp plugin install omp-uwu@0.5.0 # pin a version
83
+ omp plugin install omp-uwu@0.6.0 # pin a version
84
84
  omp plugin uninstall omp-uwu # remove
85
85
  ```
86
86
 
@@ -98,7 +98,7 @@ omp -e ./src/index.ts # or load it for a single run
98
98
 
99
99
  The full-module integration tests exercise real omp Markdown/Assistant components, narrow wrapping, inline/fenced code preservation, unchanged raw messages, cache invalidation without colors, and host-transform precedence. They restore patched prototypes after each case. An additional installed-host test automatically uses `~/.bun/install/global/node_modules/@oh-my-pi/pi-tui` if present; set `OMP_UWU_HOST_TUI` to a different pi-tui package directory to test another installation (otherwise that case is skipped).
100
100
 
101
- For an interactive smoke check, start `omp -e ./src/index.ts` in an isolated test agent directory, run `/uwu colors off`, `/uwu display`, then `/uwu status`. Ask for ordinary prose plus inline/fenced code and narrow the terminal; prose should be styled and rewrapped while code stays exact. Change `/uwu level low` to `max`, switch locales and `/uwu off` without enabling colors: existing assistant paragraphs should refresh. Reopen the saved transcript/export to confirm raw text is unchanged, and verify the next model prompt has no uwu instruction. On a native client, expect unchanged prose, not fallback prompt styling.
101
+ For an interactive smoke check, start `omp -e ./src/index.ts` in an isolated test agent directory, run `/uwu colors off`, `/uwu display`, then `/uwu status`. Edit the draft and switch tabs: the sample should update while existing messages/settings stay unchanged. Esc should discard it; reopen, focus Save and press Enter to persist it and refresh colors, the badge and discovered assistant paragraphs. Narrow the terminal to check wrapping. Ask for ordinary prose plus inline/fenced code: prose should be styled and rewrapped while code stays exact. Change intensity, locale and mode without enabling colors: existing assistant paragraphs should refresh after Save. Reopen the saved transcript/export to confirm raw text is unchanged, and verify the next model prompt has no uwu instruction in display mode. Prompt-style samples are deterministic approximations, not predictions of model output; display samples use prose-only styling, and mode off shows the original sample. On a native client, expect unchanged prose, not fallback prompt styling.
102
102
 
103
103
  ### Re-recording the demo
104
104
 
@@ -124,8 +124,8 @@ CI (`.github/workflows/ci.yml`) runs `check` and the tests on pushes to `main` a
124
124
 
125
125
  ```sh
126
126
  # bump "version" in package.json, commit, then:
127
- git tag v0.5.0
128
- git push origin main v0.5.0
127
+ git tag v0.6.0
128
+ git push origin main v0.6.0
129
129
  ```
130
130
 
131
131
  The workflow uses npm [trusted publishing](https://docs.npmjs.com/trusted-publishers/), so the repository has no npm token. npm only allows trusted publishing on a package that already exists, so the first version is published by hand with `npm publish --access public`. After that, go to the package's Settings → Trusted publishing on npmjs.com and add GitHub Actions with user `NaC-L`, repository `omp-uwu`, and workflow `publish.yml`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-uwu",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "omp extension for playful uwu chat styling, markdown-safe rewrites, and kawaii kaomoji",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -2,8 +2,9 @@ import { readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { KawaiiTheme } from "./kawaii.ts";
4
4
  import { type ContainerClass, installSparkles, sparkle, themePaint } from "./sparkle.ts";
5
+ import { createStatusCard, type UwuSettings, type UwuStatus } from "./status.ts";
5
6
 
6
- import type { ExtensionAPI, Theme } from "@oh-my-pi/pi-coding-agent";
7
+ import type { ExtensionAPI, ExtensionUIContext, Theme } from "@oh-my-pi/pi-coding-agent";
7
8
  import { type UwuLevel, type UwuLocale, uwufy, uwufyProse } from "./uwufy.ts";
8
9
 
9
10
  export const UWU_PROMPT = `
@@ -57,6 +58,7 @@ type UiContext = {
57
58
  notify(message: string, level: "info"): void;
58
59
  setStatus?(key: string, text: string | undefined): void;
59
60
  theme?: Theme;
61
+ custom?: ExtensionUIContext["custom"];
60
62
  };
61
63
  };
62
64
 
@@ -114,6 +116,18 @@ export default function uwuExtension(pi: ExtensionAPI) {
114
116
  else kawaiiTheme.disable(ctx.ui.theme);
115
117
  };
116
118
 
119
+ const persistSettings = async (ctx: UiContext) => {
120
+ try {
121
+ await saveState();
122
+ } catch {
123
+ ctx.ui?.notify("uwu setting could not be saved; it may reset next session", "info");
124
+ }
125
+ renderAllowed = ctx.mode === "tui";
126
+ syncColors(ctx);
127
+ rendering?.refresh();
128
+ updateBadge(ctx);
129
+ };
130
+
117
131
  pi.registerCommand("uwu", {
118
132
  description: "UwU style, intensity, locale and experimental display (use /uwu status)",
119
133
  handler: async (args, rawCtx) => {
@@ -127,7 +141,25 @@ export default function uwuExtension(pi: ExtensionAPI) {
127
141
  const value = option.toLowerCase();
128
142
  if (arg === "status" && !option) {
129
143
  const display = !host.Container ? "unavailable (no Container)" : rendering?.isSupported() ? "ANSI hook detected" : "pending component discovery";
130
- ctx.ui?.notify(`uwu ${enabled ? "on" : "off"}; style=${style}; level=${level}; locale=${locale}; colors=${colorsEnabled ? "on" : "off"}; rewrite=${hookSupport === undefined ? "unknown" : hookSupport ? "detected" : "not observed"}; display=${display}; native/client display=unsupported; prompt fallback=${enabled && style === "rewrite" && hookSupport !== true ? "on" : "off"}`, "info");
144
+ const status: UwuStatus = {
145
+ enabled, style, level, locale, colorsEnabled,
146
+ rewrite: hookSupport === undefined ? "unknown" : hookSupport ? "detected" : "not observed",
147
+ display,
148
+ promptFallback: enabled && style === "rewrite" && hookSupport !== true,
149
+ };
150
+ if (ctx.mode === "tui" && typeof ctx.ui?.custom === "function") {
151
+ const settings = await ctx.ui.custom<UwuSettings | undefined>((tui, theme, _keybindings, done) =>
152
+ createStatusCard(status, theme, done, () => tui.requestRender()), {
153
+ overlay: true,
154
+ overlayOptions: { width: 68, anchor: "center", margin: 1 },
155
+ });
156
+ if (settings) {
157
+ ({ enabled, style, level, locale, colorsEnabled } = settings);
158
+ await persistSettings(ctx);
159
+ }
160
+ } else {
161
+ ctx.ui?.notify(`uwu ${enabled ? "on" : "off"}; style=${style}; level=${level}; locale=${locale}; colors=${colorsEnabled ? "on" : "off"}; rewrite=${hookSupport === undefined ? "unknown" : hookSupport ? "detected" : "not observed"}; display=${display}; native/client display=unsupported; prompt fallback=${enabled && style === "rewrite" && hookSupport !== true ? "on" : "off"}`, "info");
162
+ }
131
163
  return;
132
164
  }
133
165
  if (arg === "preview") {
@@ -154,15 +186,7 @@ export default function uwuExtension(pi: ExtensionAPI) {
154
186
  ctx.ui?.notify(USAGE, "info");
155
187
  return;
156
188
  }
157
- try {
158
- await saveState();
159
- } catch {
160
- ctx.ui?.notify("uwu setting could not be saved; it may reset next session", "info");
161
- }
162
- renderAllowed = ctx.mode === "tui";
163
- syncColors(ctx);
164
- rendering?.refresh();
165
- updateBadge(ctx);
189
+ await persistSettings(ctx);
166
190
  if (arg === "colors") {
167
191
  ctx.ui?.notify(`kawaii chat colors ${colorsEnabled ? "on" : "off"}${colorsEnabled && !enabled ? " (active when uwu mode is on)" : ""}`, "info");
168
192
  } else {
package/src/status.ts ADDED
@@ -0,0 +1,154 @@
1
+ import type { ExtensionUiComponent, Theme } from "@oh-my-pi/pi-coding-agent";
2
+ import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
3
+ import { type UwuLevel, type UwuLocale, uwufy, uwufyProse } from "./uwufy.ts";
4
+
5
+ export type UwuSettings = {
6
+ enabled: boolean;
7
+ style: "rewrite" | "prompt" | "display";
8
+ level: UwuLevel;
9
+ locale: UwuLocale;
10
+ colorsEnabled: boolean;
11
+ };
12
+
13
+ export type UwuStatus = UwuSettings & {
14
+ rewrite: "unknown" | "detected" | "not observed";
15
+ display: "unavailable (no Container)" | "ANSI hook detected" | "pending component discovery";
16
+ promptFallback: boolean;
17
+ };
18
+
19
+ export function createStatusCard(
20
+ status: UwuStatus,
21
+ theme: Theme,
22
+ done: (settings: UwuSettings | undefined) => void,
23
+ requestRender: () => void,
24
+ ): ExtensionUiComponent {
25
+ const draft: UwuSettings = {
26
+ enabled: status.enabled, style: status.style, level: status.level,
27
+ locale: status.locale, colorsEnabled: status.colorsEnabled,
28
+ };
29
+ const styles: UwuSettings["style"][] = ["rewrite", "prompt", "display"];
30
+ const levels: UwuLevel[] = ["low", "mid", "max"];
31
+ const locales: UwuLocale[] = ["auto", "en", "tr"];
32
+ const sample = "Really lovely progress. Merhaba, bugün beraber çalışalım. Keep `raw_code` unchanged.";
33
+ let tab: "controls" | "compatibility" = "controls";
34
+ let focus = 0;
35
+ let closed = false;
36
+ const close = (settings?: UwuSettings) => {
37
+ if (closed) return;
38
+ closed = true;
39
+ done(settings);
40
+ };
41
+ const cycle = <T,>(values: T[], current: T, direction: number): T =>
42
+ values[(values.indexOf(current) + direction + values.length) % values.length];
43
+
44
+ return {
45
+ render(width: number): string[] {
46
+ if (width < 1) return [];
47
+ const cardWidth = Math.min(68, Math.floor(width));
48
+ const bordered = cardWidth >= 4;
49
+ const padding = cardWidth >= 24 ? 1 : 0;
50
+ const contentWidth = cardWidth - (bordered ? 2 : 0) - padding * 2;
51
+ const lines: string[] = [];
52
+ const border = (text: string) => theme.fg("borderMuted", text);
53
+ const rule = (left: string, right: string) => {
54
+ if (bordered) lines.push(border(`${left}${"─".repeat(cardWidth - 2)}${right}`));
55
+ };
56
+ const row = (text: string) => {
57
+ for (const wrapped of wrapTextWithAnsi(text, contentWidth)) {
58
+ const line = truncateToWidth(wrapped, contentWidth, "");
59
+ const padded = `${" ".repeat(padding)}${line}${" ".repeat(Math.max(0, contentWidth - visibleWidth(line)) + padding)}`;
60
+ lines.push(bordered ? `${border("│")}${padded}${border("│")}` : padded);
61
+ }
62
+ };
63
+ const chip = (label: string, selected: boolean) => selected
64
+ ? theme.bg("selectedBg", theme.fg("accent", theme.bold(`[ ${label} ]`)))
65
+ : theme.fg("dim", `[ ${label} ]`);
66
+ const presets = (values: string[], selected: string) => values.map((value) => chip(value, value === selected)).join(" ");
67
+ const control = (index: number, label: string, value: string) => {
68
+ const cursor = theme.fg("accent", focus === index ? "› " : " ");
69
+ const name = theme.fg(focus === index ? "accent" : "muted", label);
70
+ if (contentWidth >= 42) row(`${cursor}${name}${" ".repeat(Math.max(1, 14 - label.length))}${value}`);
71
+ else {
72
+ row(`${cursor}${name}`);
73
+ row(` ${value}`);
74
+ }
75
+ };
76
+ const setting = (label: string, value: string) => {
77
+ const key = contentWidth >= 34 ? label.padEnd(16) : `${label}: `;
78
+ row(`${theme.fg("muted", key)}${value}`);
79
+ };
80
+
81
+ rule("╭", "╮");
82
+ const title = theme.fg("accent", theme.bold("UwU"));
83
+ const badge = theme.fg(draft.enabled ? "success" : "dim", theme.bold(draft.enabled ? "[ON]" : "[OFF]"));
84
+ row(`${title}${" ".repeat(Math.max(1, contentWidth - visibleWidth(title) - visibleWidth(badge)))}${badge}`);
85
+ row(`${chip("Controls", tab === "controls")} ${chip("Compatibility", tab === "compatibility")}`);
86
+ rule("├", "┤");
87
+ if (tab === "controls") {
88
+ control(0, "Mode", `${chip(draft.enabled ? "● ON" : "ON", draft.enabled)} ${chip(draft.enabled ? "OFF" : "OFF ●", !draft.enabled)}`);
89
+ control(1, "Style", presets(styles, draft.style));
90
+ const filled = (levels.indexOf(draft.level) + 1) * 2;
91
+ control(2, "Intensity", `${presets(levels, draft.level)} ${theme.fg("accent", "▰".repeat(filled))}${theme.fg("dim", "▱".repeat(6 - filled))}`);
92
+ control(3, "Locale", presets(locales, draft.locale));
93
+ control(4, "Kawaii colors", `${chip(draft.colorsEnabled ? "● ON" : "ON", draft.colorsEnabled)} ${chip(draft.colorsEnabled ? "OFF" : "OFF ●", !draft.colorsEnabled)}`);
94
+ row("");
95
+ row(`${theme.fg("accent", focus === 5 ? "› " : " ")}${chip("Save", focus === 5)}`);
96
+ const changes = (Object.keys(draft) as (keyof UwuSettings)[]).filter((key) => draft[key] !== status[key]).length;
97
+ row(theme.fg(changes ? "warning" : "dim", changes ? `${changes} unsaved ${changes === 1 ? "change" : "changes"}` : "Draft unchanged"));
98
+ rule("├", "┤");
99
+ const previewKind = !draft.enabled ? "mode off" : draft.style === "display" ? "prose only · experimental" : draft.style === "prompt" ? "approximation" : "deterministic";
100
+ row(theme.fg("accent", theme.bold(`Preview · ${previewKind}`)));
101
+ const preview = !draft.enabled ? sample : draft.style === "display"
102
+ ? uwufyProse(sample, { level: draft.level, locale: draft.locale })
103
+ : uwufy(sample, { level: draft.level, locale: draft.locale });
104
+ row(theme.fg("text", preview));
105
+ rule("├", "┤");
106
+ row(theme.fg("muted", "↑↓ focus · ←→ / Enter / Space edit"));
107
+ row(theme.fg("muted", "Tab tabs · Enter on Save · Esc cancel"));
108
+ } else {
109
+ row(theme.fg("accent", theme.bold("Observed capabilities")));
110
+ setting("Rewrite", theme.fg(status.rewrite === "detected" ? "success" : "muted", status.rewrite));
111
+ setting("ANSI display", theme.fg(status.display === "ANSI hook detected" ? "success" : "muted", status.display));
112
+ setting("Native/client", theme.fg("warning", "unsupported"));
113
+ setting("Prompt fallback", theme.fg(status.promptFallback ? "success" : "dim", status.promptFallback ? "on" : "off"));
114
+ row("");
115
+ row(theme.fg("dim", `Fallback reflects saved ${status.style}/${status.enabled ? "on" : "off"} settings, not the draft.`));
116
+ row(theme.fg("warning", "Display is experimental: ANSI prose only, not native/client rendering."));
117
+ row(theme.fg("dim", "Unknown/pending is not a support guarantee. Display never adds a prompt or changes history."));
118
+ rule("├", "┤");
119
+ row(theme.fg("muted", "Tab controls · Esc cancel"));
120
+ }
121
+ rule("╰", "╯");
122
+ return lines;
123
+ },
124
+ invalidate() {},
125
+ handleInput(data: string) {
126
+ if (closed) return;
127
+ if (matchesKey(data, Key.escape)) {
128
+ close();
129
+ return;
130
+ }
131
+ if (matchesKey(data, Key.tab)) {
132
+ tab = tab === "controls" ? "compatibility" : "controls";
133
+ } else if (tab === "controls" && matchesKey(data, Key.up)) {
134
+ focus = (focus + 5) % 6;
135
+ } else if (tab === "controls" && matchesKey(data, Key.down)) {
136
+ focus = (focus + 1) % 6;
137
+ } else if (tab === "controls" && focus === 5 && matchesKey(data, Key.enter)) {
138
+ close({ ...draft });
139
+ return;
140
+ } else if (tab === "controls" && focus < 5 && (
141
+ matchesKey(data, Key.left) || matchesKey(data, Key.right) ||
142
+ matchesKey(data, Key.enter) || matchesKey(data, Key.space)
143
+ )) {
144
+ const direction = matchesKey(data, Key.left) ? -1 : 1;
145
+ if (focus === 0) draft.enabled = !draft.enabled;
146
+ else if (focus === 1) draft.style = cycle(styles, draft.style, direction);
147
+ else if (focus === 2) draft.level = cycle(levels, draft.level, direction);
148
+ else if (focus === 3) draft.locale = cycle(locales, draft.locale, direction);
149
+ else draft.colorsEnabled = !draft.colorsEnabled;
150
+ } else return;
151
+ requestRender();
152
+ },
153
+ };
154
+ }