@codexstar/pi-pompom 1.0.0 → 1.2.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/CHANGELOG.md +51 -0
- package/LICENSE +21 -0
- package/README.md +110 -40
- package/extensions/pompom-extension.ts +88 -71
- package/extensions/pompom.ts +46 -16
- package/package.json +24 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [1.1.0] - 2026-03-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Dance state with sparkle particles (`/pompom dance`, Alt+x)
|
|
12
|
+
- Treat command with extra hunger boost (`/pompom treat`, Alt+t)
|
|
13
|
+
- Hug command with energy restore (`/pompom hug`, Alt+h)
|
|
14
|
+
- `/pompom help` with full command reference and platform-aware shortcut labels
|
|
15
|
+
- `/pompom status` showing mood, hunger/energy bars, active theme
|
|
16
|
+
- Random idle speech (12 phrases) for personality
|
|
17
|
+
- Kitty keyboard protocol support (Ghostty, Kitty, WezTerm)
|
|
18
|
+
- Ghostty keybind configuration for reliable macOS shortcuts
|
|
19
|
+
- Cross-platform status bar labels (Option on macOS, Alt on Windows/Linux)
|
|
20
|
+
- Crash isolation: all rendering and input handling wrapped in try/catch
|
|
21
|
+
- Namespaced widget ID (`codexstar-pompom-companion`) to prevent conflicts
|
|
22
|
+
- Dark body outline for better visibility against backgrounds
|
|
23
|
+
- Larger eyes, nose, mouth for clarity at low resolution
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Upgraded anti-aliasing from 2x2 to 4x4 supersampling (16 samples per pixel)
|
|
27
|
+
- Compact single-line status bar (was 4-line box)
|
|
28
|
+
- Descriptive state messages ("Pompom is starving! Drop a treat with ⌥f")
|
|
29
|
+
- Moved creature closer to ground (posY 0 → 0.15)
|
|
30
|
+
- Camera offset (VIEW_OFFSET_Y 0.1 → 0.18) for better framing
|
|
31
|
+
- Replaced emoji particle characters with ASCII to prevent TUI width overflow
|
|
32
|
+
- Speech bubble strips multi-width characters before rendering to grid
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- "Rendered line exceeds terminal width" crash from emoji in particles/speech
|
|
36
|
+
- Extension dropped on other package installs (now published to npm)
|
|
37
|
+
|
|
38
|
+
## [1.0.0] - 2026-03-14
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- 3D raymarched virtual pet with physics simulation
|
|
42
|
+
- 10 interactive states: idle, walk, flip, sleep, excited, chasing, fetching, singing, offscreen, peek
|
|
43
|
+
- Keyboard shortcuts via macOS Option key and Windows/Linux Alt key
|
|
44
|
+
- `/pompom` command with on/off/pet/feed/ball/music/color/sleep/wake/flip/hide
|
|
45
|
+
- Day/night sky cycle based on system clock
|
|
46
|
+
- Particle effects: sparkles, music notes, rain, crumbs, sleep Zs
|
|
47
|
+
- Speech bubbles with contextual messages
|
|
48
|
+
- Firefly companion, ball fetch physics, food dropping
|
|
49
|
+
- Hunger and energy needs system
|
|
50
|
+
- 4 color themes: Cloud, Cotton Candy, Mint Drop, Sunset Gold
|
|
51
|
+
- Floor with wood grain pattern and character reflections
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 codexstar69
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,58 +1,128 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/hero.png" alt="pi-pompom" width="720">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">pi-pompom</h1>
|
|
6
|
+
<p align="center"><strong>A 3D raymarched virtual pet that lives in your terminal.</strong></p>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<!-- BADGES:START -->
|
|
9
|
+
<a href="https://www.npmjs.com/package/@codexstar/pi-pompom"><img src="https://img.shields.io/npm/v/@codexstar/pi-pompom.svg" alt="npm version"></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/@codexstar/pi-pompom"><img src="https://img.shields.io/npm/dm/@codexstar/pi-pompom.svg" alt="npm downloads"></a>
|
|
11
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
12
|
+
<img src="https://img.shields.io/badge/TypeScript-5.x-blue.svg" alt="TypeScript">
|
|
13
|
+
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey.svg" alt="Platform">
|
|
14
|
+
<!-- BADGES:END -->
|
|
15
|
+
</p>
|
|
16
|
+
<p align="center">
|
|
17
|
+
<a href="#install">Install</a> ·
|
|
18
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
19
|
+
<a href="#commands">Commands</a> ·
|
|
20
|
+
<a href="#keyboard-shortcuts">Shortcuts</a> ·
|
|
21
|
+
<a href="#features">Features</a>
|
|
22
|
+
</p>
|
|
4
23
|
|
|
5
|
-
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
Pompom is an interactive companion for [Pi CLI](https://github.com/mariozechner/pi-coding-agent). It renders a real-time 3D raymarched creature above your editor using Unicode half-block characters with 4x4 supersampled anti-aliasing. Pompom walks, sleeps, chases fireflies, plays fetch, dances, and reacts to your commands.
|
|
6
27
|
|
|
7
28
|
## Install
|
|
8
29
|
|
|
9
30
|
```bash
|
|
10
|
-
pi install @codexstar/pi-
|
|
31
|
+
pi install @codexstar/pi-pompom
|
|
11
32
|
```
|
|
12
33
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
Lumo appears automatically above the editor. Interact with single-key commands when the editor is empty:
|
|
16
|
-
|
|
17
|
-
| Key | Action |
|
|
18
|
-
|-----|--------|
|
|
19
|
-
| `⌥p` | Pet |
|
|
20
|
-
| `⌥f` | Feed |
|
|
21
|
-
| `⌥b` | Ball |
|
|
22
|
-
| `⌥m` | Music |
|
|
23
|
-
| `⌥c` | Color |
|
|
24
|
-
| `⌥s` | Sleep |
|
|
25
|
-
| `⌥w` | Wake |
|
|
26
|
-
| `⌥d` | Flip |
|
|
27
|
-
| `⌥o` | Hide |
|
|
34
|
+
## Quick Start
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
Pompom appears automatically when you start Pi. Toggle it with:
|
|
30
37
|
|
|
31
38
|
```
|
|
32
|
-
/
|
|
33
|
-
/
|
|
34
|
-
/lumo pet — pet Lumo
|
|
35
|
-
/lumo feed — drop food
|
|
36
|
-
/lumo ball — throw a ball
|
|
37
|
-
/lumo music — sing a song
|
|
38
|
-
/lumo color — cycle color theme
|
|
39
|
-
/lumo sleep — take a nap
|
|
40
|
-
/lumo wake — wake up
|
|
41
|
-
/lumo flip — do a flip
|
|
42
|
-
/lumo hide — wander offscreen
|
|
39
|
+
/pompom on
|
|
40
|
+
/pompom off
|
|
43
41
|
```
|
|
44
42
|
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
| Command | What it does |
|
|
46
|
+
|---------|-------------|
|
|
47
|
+
| `/pompom` | Toggle companion on/off |
|
|
48
|
+
| `/pompom help` | Show all commands and shortcuts |
|
|
49
|
+
| `/pompom status` | Check mood, hunger, energy, theme |
|
|
50
|
+
| `/pompom pet` | Pet Pompom |
|
|
51
|
+
| `/pompom feed` | Drop food |
|
|
52
|
+
| `/pompom treat` | Special treat (extra hunger boost) |
|
|
53
|
+
| `/pompom hug` | Give a hug (restores energy) |
|
|
54
|
+
| `/pompom ball` | Throw a ball |
|
|
55
|
+
| `/pompom dance` | Dance with sparkle particles |
|
|
56
|
+
| `/pompom music` | Sing a song |
|
|
57
|
+
| `/pompom theme` | Cycle color theme |
|
|
58
|
+
| `/pompom sleep` | Nap on a pillow |
|
|
59
|
+
| `/pompom wake` | Wake up |
|
|
60
|
+
| `/pompom flip` | Do a backflip |
|
|
61
|
+
| `/pompom hide` | Wander offscreen |
|
|
62
|
+
|
|
63
|
+
## Keyboard Shortcuts
|
|
64
|
+
|
|
65
|
+
Pompom responds to Alt/Option + key while the companion is active.
|
|
66
|
+
|
|
67
|
+
| macOS | Windows/Linux | Action |
|
|
68
|
+
|-------|--------------|--------|
|
|
69
|
+
| `⌥p` | `Alt+p` | Pet |
|
|
70
|
+
| `⌥f` | `Alt+f` | Feed |
|
|
71
|
+
| `⌥t` | `Alt+t` | Treat |
|
|
72
|
+
| `⌥h` | `Alt+h` | Hug |
|
|
73
|
+
| `⌥b` | `Alt+b` | Ball |
|
|
74
|
+
| `⌥x` | `Alt+x` | Dance |
|
|
75
|
+
| `⌥m` | `Alt+m` | Music |
|
|
76
|
+
| `⌥c` | `Alt+c` | Theme |
|
|
77
|
+
| `⌥s` | `Alt+s` | Sleep |
|
|
78
|
+
| `⌥w` | `Alt+w` | Wake |
|
|
79
|
+
| `⌥d` | `Alt+d` | Flip |
|
|
80
|
+
| `⌥o` | `Alt+o` | Hide |
|
|
81
|
+
|
|
82
|
+
Shortcuts work across macOS (Option key), Windows (Alt key), and Linux (Alt key). Four input methods are supported: Ghostty keybinds, ESC prefix, macOS Unicode, and Kitty keyboard protocol.
|
|
83
|
+
|
|
45
84
|
## Features
|
|
46
85
|
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
86
|
+
- 3D raymarched body with real-time lighting, shadows, and floor reflections
|
|
87
|
+
- 4x4 supersampled anti-aliasing (16 samples per pixel)
|
|
88
|
+
- Natural blinking, breathing, ear wiggling, and tail wagging
|
|
89
|
+
- Day/night sky cycle based on system clock
|
|
90
|
+
- Particle effects: sparkles, music notes, rain, crumbs, sleep Zs
|
|
91
|
+
- Speech bubbles with random idle chatter (12 phrases)
|
|
92
|
+
- Firefly companion that Pompom chases
|
|
93
|
+
- Hunger and energy needs system with visual status bars
|
|
94
|
+
- Ball physics with bouncing and fetch behavior
|
|
95
|
+
- 4 color themes: Cloud, Cotton Candy, Mint Drop, Sunset Gold
|
|
96
|
+
- Dark body outline for visibility against any background
|
|
97
|
+
- Compact status bar with live state messages
|
|
98
|
+
- Crash-isolated rendering: errors never take down the host TUI
|
|
99
|
+
- Namespaced widget ID to prevent conflicts with other extensions
|
|
100
|
+
|
|
101
|
+
## How It Works
|
|
102
|
+
|
|
103
|
+
The renderer is a software raymarcher running in your terminal. Each frame:
|
|
104
|
+
|
|
105
|
+
1. Physics simulation updates position, particles, and state machines (60fps sub-stepping)
|
|
106
|
+
2. Scene objects (body, ears, paws, tail, antenna, ball, food) are built with rotation and oscillation
|
|
107
|
+
3. For each character cell, 16 rays are cast (4x4 grid) and the colors averaged for anti-aliasing
|
|
108
|
+
4. Object hits are shaded with diffuse + wrap lighting, ambient occlusion, specular highlights, and firefly point light
|
|
109
|
+
5. The shaded pixels are encoded as ANSI true-color escape sequences using `▀` half-block characters
|
|
110
|
+
6. Speech bubbles and particle overlays are composited on top
|
|
111
|
+
|
|
112
|
+
The widget re-renders at ~7 FPS via a 150ms `setInterval`.
|
|
113
|
+
|
|
114
|
+
## Configuration
|
|
115
|
+
|
|
116
|
+
Set `POMPOM_NO_KITTY=1` to force half-block rendering on terminals that report Kitty support but don't handle it well.
|
|
117
|
+
|
|
118
|
+
## Contributing
|
|
119
|
+
|
|
120
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
121
|
+
|
|
122
|
+
## Security
|
|
123
|
+
|
|
124
|
+
See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
|
|
55
125
|
|
|
56
126
|
## License
|
|
57
127
|
|
|
58
|
-
MIT
|
|
128
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* pi-pompom — Pompom Companion Extension for Pi CLI.
|
|
3
3
|
*
|
|
4
4
|
* A 3D raymarched virtual pet that lives above the editor.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Install as a standalone extension — no dependencies on pi-voice.
|
|
5
|
+
* Hardened against conflicts with other extensions.
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
8
|
import type {
|
|
@@ -14,6 +12,9 @@ import type {
|
|
|
14
12
|
|
|
15
13
|
import { renderPompom, resetPompom, pompomSetTalking, pompomKeypress, pompomStatus } from "./pompom";
|
|
16
14
|
|
|
15
|
+
// Namespaced widget ID — prevents collision with any other extension
|
|
16
|
+
const WIDGET_ID = "codexstar-pompom-companion";
|
|
17
|
+
|
|
17
18
|
export default function (pi: ExtensionAPI) {
|
|
18
19
|
let ctx: ExtensionContext | null = null;
|
|
19
20
|
let companionTimer: ReturnType<typeof setInterval> | null = null;
|
|
@@ -22,31 +23,44 @@ export default function (pi: ExtensionAPI) {
|
|
|
22
23
|
let terminalInputUnsub: (() => void) | null = null;
|
|
23
24
|
let enabled = true;
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
if (companionActive) return;
|
|
27
|
-
if (!ctx?.hasUI) return;
|
|
28
|
-
companionActive = true;
|
|
29
|
-
lastRenderTime = Date.now();
|
|
26
|
+
// ─── Safe render wrapper — never lets an error crash the TUI ─────────
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
function safeRender(width: number): string[] {
|
|
29
|
+
try {
|
|
32
30
|
const now = Date.now();
|
|
33
31
|
const dt = Math.min(0.1, (now - lastRenderTime) / 1000);
|
|
34
32
|
lastRenderTime = now;
|
|
35
33
|
return renderPompom(Math.max(40, width), 0, dt);
|
|
36
|
-
}
|
|
34
|
+
} catch {
|
|
35
|
+
// If rendering fails, return a minimal placeholder so the TUI doesn't crash
|
|
36
|
+
return [" ".repeat(Math.max(1, width))];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Widget management ──────────────────────────────────────────────
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
function showCompanion() {
|
|
43
|
+
if (companionActive) return;
|
|
44
|
+
if (!ctx?.hasUI) return;
|
|
45
|
+
companionActive = true;
|
|
46
|
+
lastRenderTime = Date.now();
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
const setWidget = () => {
|
|
44
49
|
if (!ctx?.hasUI) return;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
try {
|
|
51
|
+
ctx.ui.setWidget(WIDGET_ID, (_tui, _theme) => ({
|
|
52
|
+
invalidate() {},
|
|
53
|
+
render: safeRender,
|
|
54
|
+
}), { placement: "aboveEditor" });
|
|
55
|
+
} catch {
|
|
56
|
+
// Widget slot may be unavailable — don't crash
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
setWidget();
|
|
61
|
+
// Re-set widget on interval for animation. Defensive: clear any stale timer first.
|
|
62
|
+
if (companionTimer) clearInterval(companionTimer);
|
|
63
|
+
companionTimer = setInterval(setWidget, 150);
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
function hideCompanion() {
|
|
@@ -56,74 +70,77 @@ export default function (pi: ExtensionAPI) {
|
|
|
56
70
|
companionTimer = null;
|
|
57
71
|
}
|
|
58
72
|
pompomSetTalking(false);
|
|
59
|
-
|
|
60
|
-
ctx.ui.setWidget(
|
|
73
|
+
try {
|
|
74
|
+
if (ctx?.hasUI) ctx.ui.setWidget(WIDGET_ID, undefined);
|
|
75
|
+
} catch {
|
|
76
|
+
// Ignore — widget may already be gone
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
//
|
|
65
|
-
|
|
80
|
+
// ─── Keyboard input ─────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
// macOS Option+key Unicode map (only fires when macos-option-as-alt is off)
|
|
66
83
|
const optionUnicodeMap: Record<string, string> = {
|
|
67
|
-
"π": "p",
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"µ": "m", // Option+m → Music
|
|
71
|
-
"ç": "c", // Option+c → Color
|
|
72
|
-
"∂": "d", // Option+d → Flip
|
|
73
|
-
"ß": "s", // Option+s → Sleep
|
|
74
|
-
"∑": "w", // Option+w → Wake
|
|
75
|
-
"ø": "o", // Option+o → Hide
|
|
76
|
-
"≈": "x", // Option+x → Dance
|
|
77
|
-
"†": "t", // Option+t → Treat
|
|
78
|
-
"˙": "h", // Option+h → Hug
|
|
84
|
+
"π": "p", "ƒ": "f", "∫": "b", "µ": "m", "ç": "c",
|
|
85
|
+
"∂": "d", "ß": "s", "∑": "w", "ø": "o",
|
|
86
|
+
"≈": "x", "†": "t", "˙": "h",
|
|
79
87
|
};
|
|
80
88
|
|
|
89
|
+
const POMPOM_KEYS = "pfbmcdswoxth";
|
|
90
|
+
|
|
81
91
|
function setupKeyHandler() {
|
|
82
92
|
if (!ctx?.hasUI) return;
|
|
93
|
+
// Always clean up previous handler first — prevents double-binding
|
|
83
94
|
if (terminalInputUnsub) { terminalInputUnsub(); terminalInputUnsub = null; }
|
|
84
95
|
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
try {
|
|
97
|
+
terminalInputUnsub = ctx.ui.onTerminalInput((data: string) => {
|
|
98
|
+
if (!enabled || !companionActive) return undefined;
|
|
87
99
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// 2. ESC prefix — Alt+key on Windows/Linux, Option-as-Meta on macOS
|
|
95
|
-
// This is the primary method on Windows Terminal, CMD, PowerShell, WSL
|
|
96
|
-
if (data.length === 2 && data[0] === "\x1b" && "pfbmcdswoxth".includes(data[1])) {
|
|
97
|
-
pompomKeypress(data[1]);
|
|
98
|
-
return { consume: true };
|
|
99
|
-
}
|
|
100
|
+
try {
|
|
101
|
+
// 1. Ghostty keybind prefix \x1d + letter
|
|
102
|
+
if (data.length === 2 && data[0] === "\x1d" && POMPOM_KEYS.includes(data[1])) {
|
|
103
|
+
pompomKeypress(data[1]);
|
|
104
|
+
return { consume: true };
|
|
105
|
+
}
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
+
// 2. ESC prefix — Alt+key on Windows/Linux, Option-as-Meta on macOS
|
|
108
|
+
if (data.length === 2 && data[0] === "\x1b" && POMPOM_KEYS.includes(data[1])) {
|
|
109
|
+
pompomKeypress(data[1]);
|
|
110
|
+
return { consume: true };
|
|
111
|
+
}
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const mod = parseInt(kittyMatch[2]);
|
|
113
|
-
if ((mod - 1) & 2) {
|
|
114
|
-
const char = String.fromCharCode(parseInt(kittyMatch[1]));
|
|
115
|
-
if ("pfbmcdswoxth".includes(char)) {
|
|
116
|
-
pompomKeypress(char);
|
|
113
|
+
// 3. macOS Unicode chars
|
|
114
|
+
const mapped = optionUnicodeMap[data];
|
|
115
|
+
if (mapped) {
|
|
116
|
+
pompomKeypress(mapped);
|
|
117
117
|
return { consume: true };
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
// 4. Kitty keyboard protocol
|
|
121
|
+
const kittyMatch = data.match(/^\x1b\[(\d+);(\d+)u$/);
|
|
122
|
+
if (kittyMatch) {
|
|
123
|
+
const mod = parseInt(kittyMatch[2]);
|
|
124
|
+
if ((mod - 1) & 2) {
|
|
125
|
+
const char = String.fromCharCode(parseInt(kittyMatch[1]));
|
|
126
|
+
if (POMPOM_KEYS.includes(char)) {
|
|
127
|
+
pompomKeypress(char);
|
|
128
|
+
return { consume: true };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// Never let a key handler error propagate to the TUI
|
|
119
134
|
}
|
|
120
|
-
}
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
return undefined;
|
|
137
|
+
});
|
|
138
|
+
} catch {
|
|
139
|
+
// onTerminalInput may not be available — gracefully degrade (commands still work)
|
|
140
|
+
}
|
|
124
141
|
}
|
|
125
142
|
|
|
126
|
-
// ─── Lifecycle
|
|
143
|
+
// ─── Lifecycle — defensive against load-order issues ────────────────
|
|
127
144
|
|
|
128
145
|
pi.on("session_start", async (_event, startCtx) => {
|
|
129
146
|
ctx = startCtx;
|
|
@@ -149,7 +166,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
149
166
|
}
|
|
150
167
|
});
|
|
151
168
|
|
|
152
|
-
// ─── /pompom command
|
|
169
|
+
// ─── /pompom command ────────────────────────────────────────────────
|
|
153
170
|
|
|
154
171
|
const pompomCommands: Record<string, string> = {
|
|
155
172
|
pet: "p", feed: "f", ball: "b", music: "m", color: "c", theme: "c",
|
|
@@ -229,7 +246,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
229
246
|
return;
|
|
230
247
|
}
|
|
231
248
|
|
|
232
|
-
// No args
|
|
249
|
+
// No args: toggle. Unknown: error.
|
|
233
250
|
if (sub === "") {
|
|
234
251
|
if (companionActive) {
|
|
235
252
|
enabled = false;
|
package/extensions/pompom.ts
CHANGED
|
@@ -650,9 +650,11 @@ function renderToBuffers() {
|
|
|
650
650
|
const objects = buildObjects();
|
|
651
651
|
const skyColors = getWeatherAndTime();
|
|
652
652
|
|
|
653
|
-
//
|
|
654
|
-
|
|
655
|
-
|
|
653
|
+
// Hybrid renderer: quadrant blocks at edges (2× horizontal detail),
|
|
654
|
+
// half-blocks in smooth areas (better gradient color).
|
|
655
|
+
// 16 Unicode quadrant characters: 4 sub-pixels (2×2) per cell, 2 colors each.
|
|
656
|
+
const QUAD = " \u2597\u2596\u2584\u259D\u2590\u259E\u259F\u2598\u259A\u258C\u2599\u2580\u259C\u259B\u2588";
|
|
657
|
+
const halfX = scale * 0.25;
|
|
656
658
|
|
|
657
659
|
for (let cy = 0; cy < H; cy++) {
|
|
658
660
|
for (let cx = 0; cx < W; cx++) {
|
|
@@ -660,22 +662,50 @@ function renderToBuffers() {
|
|
|
660
662
|
const py1 = (cy * 2.0 - H) * scale + VIEW_OFFSET_Y;
|
|
661
663
|
const py2 = (cy * 2.0 + 1.0 - H) * scale + VIEW_OFFSET_Y;
|
|
662
664
|
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
665
|
+
// Sample 4 quadrant centers (TL, TR, BL, BR)
|
|
666
|
+
const tl = getPixel(px - halfX, py1, objects, skyColors);
|
|
667
|
+
const tr = getPixel(px + halfX, py1, objects, skyColors);
|
|
668
|
+
const bl = getPixel(px - halfX, py2, objects, skyColors);
|
|
669
|
+
const br = getPixel(px + halfX, py2, objects, skyColors);
|
|
670
|
+
|
|
671
|
+
// Edge detection: max color difference across the 4 quadrants
|
|
672
|
+
let maxD = 0;
|
|
673
|
+
const cs = [tl, tr, bl, br];
|
|
674
|
+
for (let i = 0; i < 4; i++) for (let j = i + 1; j < 4; j++) {
|
|
675
|
+
const d = Math.abs(cs[i][0] - cs[j][0]) + Math.abs(cs[i][1] - cs[j][1]) + Math.abs(cs[i][2] - cs[j][2]);
|
|
676
|
+
if (d > maxD) maxD = d;
|
|
668
677
|
}
|
|
669
678
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const
|
|
674
|
-
|
|
679
|
+
if (maxD > 60) {
|
|
680
|
+
// EDGE CELL — use quadrant character for 2× horizontal detail
|
|
681
|
+
const lum0 = tl[0] * 77 + tl[1] * 150 + tl[2] * 29;
|
|
682
|
+
const lum1 = tr[0] * 77 + tr[1] * 150 + tr[2] * 29;
|
|
683
|
+
const lum2 = bl[0] * 77 + bl[1] * 150 + bl[2] * 29;
|
|
684
|
+
const lum3 = br[0] * 77 + br[1] * 150 + br[2] * 29;
|
|
685
|
+
const med = (Math.min(lum0, lum1, lum2, lum3) + Math.max(lum0, lum1, lum2, lum3)) / 2;
|
|
686
|
+
|
|
687
|
+
const b0 = lum0 >= med ? 1 : 0, b1 = lum1 >= med ? 1 : 0;
|
|
688
|
+
const b2 = lum2 >= med ? 1 : 0, b3 = lum3 >= med ? 1 : 0;
|
|
689
|
+
const pattern = (b0 << 3) | (b1 << 2) | (b2 << 1) | b3;
|
|
690
|
+
|
|
691
|
+
// Average fg (bright) and bg (dark) group colors
|
|
692
|
+
let fR = 0, fG = 0, fB = 0, fN = 0;
|
|
693
|
+
let bR = 0, bG = 0, bB = 0, bN = 0;
|
|
694
|
+
const bits = [b0, b1, b2, b3];
|
|
695
|
+
for (let i = 0; i < 4; i++) {
|
|
696
|
+
if (bits[i]) { fR += cs[i][0]; fG += cs[i][1]; fB += cs[i][2]; fN++; }
|
|
697
|
+
else { bR += cs[i][0]; bG += cs[i][1]; bB += cs[i][2]; bN++; }
|
|
698
|
+
}
|
|
699
|
+
if (!fN) { fR = bR; fG = bG; fB = bB; fN = bN; }
|
|
700
|
+
if (!bN) { bR = fR; bG = fG; bB = fB; bN = fN; }
|
|
701
|
+
|
|
702
|
+
screenChars[cy][cx] = QUAD[pattern];
|
|
703
|
+
screenColors[cy][cx] = `\x1b[38;2;${Math.round(fR / fN)};${Math.round(fG / fN)};${Math.round(fB / fN)}m\x1b[48;2;${Math.round(bR / bN)};${Math.round(bG / bN)};${Math.round(bB / bN)}m`;
|
|
704
|
+
} else {
|
|
705
|
+
// SMOOTH CELL — half-block with averaged top/bottom
|
|
706
|
+
screenChars[cy][cx] = "▀";
|
|
707
|
+
screenColors[cy][cx] = `\x1b[38;2;${(tl[0] + tr[0]) >> 1};${(tl[1] + tr[1]) >> 1};${(tl[2] + tr[2]) >> 1}m\x1b[48;2;${(bl[0] + br[0]) >> 1};${(bl[1] + br[1]) >> 1};${(bl[2] + br[2]) >> 1}m`;
|
|
675
708
|
}
|
|
676
|
-
|
|
677
|
-
screenChars[cy][cx] = "▀";
|
|
678
|
-
screenColors[cy][cx] = `\x1b[38;2;${tr >> 4};${tg >> 4};${tb >> 4}m\x1b[48;2;${br >> 4};${bg >> 4};${bb >> 4}m`;
|
|
679
709
|
}
|
|
680
710
|
}
|
|
681
711
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codexstar/pi-pompom",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "A 3D raymarched virtual pet companion for Pi CLI",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"author": "codexstar69 <engazedigital@gmail.com>",
|
|
7
|
+
"license": "MIT",
|
|
6
8
|
"keywords": [
|
|
7
9
|
"pi-package",
|
|
8
10
|
"pi",
|
|
@@ -10,9 +12,25 @@
|
|
|
10
12
|
"pompom",
|
|
11
13
|
"companion",
|
|
12
14
|
"virtual-pet",
|
|
13
|
-
"terminal-pet"
|
|
15
|
+
"terminal-pet",
|
|
16
|
+
"terminal-art",
|
|
17
|
+
"raymarching",
|
|
18
|
+
"ascii-art",
|
|
19
|
+
"ansi",
|
|
20
|
+
"tui",
|
|
21
|
+
"widget",
|
|
22
|
+
"interactive",
|
|
23
|
+
"animation",
|
|
24
|
+
"coding-agent"
|
|
14
25
|
],
|
|
15
|
-
"
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/codexstar69/pi-pompom.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/codexstar69/pi-pompom#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/codexstar69/pi-pompom/issues"
|
|
33
|
+
},
|
|
16
34
|
"scripts": {
|
|
17
35
|
"typecheck": "bunx tsc -p tsconfig.json",
|
|
18
36
|
"check": "bun run typecheck"
|
|
@@ -32,6 +50,8 @@
|
|
|
32
50
|
"files": [
|
|
33
51
|
"extensions",
|
|
34
52
|
"README.md",
|
|
53
|
+
"CHANGELOG.md",
|
|
54
|
+
"LICENSE",
|
|
35
55
|
"package.json"
|
|
36
56
|
]
|
|
37
57
|
}
|