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 +5 -5
- package/package.json +1 -1
- package/src/index.ts +35 -11
- package/src/status.ts +154 -0
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`
|
|
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.
|
|
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
|
|
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.
|
|
128
|
-
git push origin main v0.
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|