@mingxy/opencode-mascot 0.5.8 → 0.5.9
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 +115 -114
- package/package.json +1 -1
- package/src/components/sidebar-mascot.tsx +5 -1
- package/src/core/ascii-renderer.tsx +2 -1
package/README.md
CHANGED
|
@@ -1,123 +1,127 @@
|
|
|
1
1
|
# 🐱 opencode-mascot
|
|
2
2
|
|
|
3
|
-
> OpenCode TUI
|
|
3
|
+
> OpenCode TUI mascot plugin framework — bring your terminal to life
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Customizable ASCII mascots that breathe, walk, sleep, get launched across the screen, blown up by falling bombs, then quietly reassemble themselves.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[English](./README.md) | [简体中文](./README_zh-CN.md)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
### 🎭 Built-in Characters (2)
|
|
10
14
|
|
|
11
|
-
|
|
|
12
|
-
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
+
| Character | Description | Color |
|
|
16
|
+
|-----------|-------------|-------|
|
|
17
|
+
| **yueer** | Purple-haired girl with an ahoge, tsundere style, default mascot | `#8B7EB8` lavender |
|
|
18
|
+
| **baozi** | A steaming hot bun, warm and cozy | `#D4885A` warm orange |
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
Each character includes **5 expression frames**: default / blink / happy / thinking / sleeping
|
|
17
21
|
|
|
18
22
|
---
|
|
19
23
|
|
|
20
|
-
### 🎬
|
|
24
|
+
### 🎬 Automatic Animations (16)
|
|
21
25
|
|
|
22
|
-
**
|
|
26
|
+
**Built-in (shared by all characters):**
|
|
23
27
|
|
|
24
|
-
| # |
|
|
25
|
-
|
|
26
|
-
| 1 |
|
|
27
|
-
| 2 |
|
|
28
|
-
| 3 |
|
|
29
|
-
| 4 |
|
|
30
|
-
| 5 |
|
|
31
|
-
| 6 |
|
|
28
|
+
| # | Animation | Trigger | Effect |
|
|
29
|
+
|---|-----------|---------|--------|
|
|
30
|
+
| 1 | Blink | Random (30% / 2.5s) | Switch to blink frame for 150ms |
|
|
31
|
+
| 2 | Random expression | Every 8s | Cycles expressions while idle |
|
|
32
|
+
| 3 | Breathing | Every 3s | Lines shift up one row, simulating breathing |
|
|
33
|
+
| 4 | Walking | Every 20-40s | Sways left and right (14-step path) |
|
|
34
|
+
| 5 | Jumping | Every 20-40s | Bounces -2 → -1 → 0 |
|
|
35
|
+
| 6 | Sleep | Idle 90-120s | Auto-closed eyes + alien-text Zzz |
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
**yueer exclusive:**
|
|
34
38
|
|
|
35
|
-
| # |
|
|
36
|
-
|
|
37
|
-
| 7 |
|
|
38
|
-
| 8 |
|
|
39
|
-
| 9 |
|
|
40
|
-
| 10 |
|
|
41
|
-
| 11 |
|
|
42
|
-
| 12 |
|
|
43
|
-
| 13 |
|
|
39
|
+
| # | Animation | Trigger | Effect |
|
|
40
|
+
|---|-----------|---------|--------|
|
|
41
|
+
| 7 | Ahoge sparkle | Random (25% / 1.5s) | ☆ ↔ ★ |
|
|
42
|
+
| 8 | Happy head sway | happy state | Face sways left/right + ahoge synced |
|
|
43
|
+
| 9 | Thinking foot stomp | thinking state | Left foot fixed, right foot stomps ║↔_ |
|
|
44
|
+
| 10 | Thinking face shift | thinking state | 6 expressions rotate (o_o / O_O / >_o / o_< / ⊙_⊙ / ◔_◔) |
|
|
45
|
+
| 11 | Alien text bubble | busy/thinking | 12 alien-text phrases rotate |
|
|
46
|
+
| 12 | Drag arm flail | While dragging | Arms ┃███┃ ↔ ╱███╲ |
|
|
47
|
+
| 13 | Jump arm flail | While jumping | Same as above |
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
**baozi exclusive:**
|
|
46
50
|
|
|
47
|
-
| # |
|
|
48
|
-
|
|
49
|
-
| 14 |
|
|
50
|
-
| 15 |
|
|
51
|
-
| 16 |
|
|
51
|
+
| # | Animation | Trigger | Effect |
|
|
52
|
+
|---|-----------|---------|--------|
|
|
53
|
+
| 14 | Steam | Continuous | 4 steam patterns rotate |
|
|
54
|
+
| 15 | Alien text bubble | busy/thinking | 12 alien-text phrases rotate |
|
|
55
|
+
| 16 | Drag panic | While dragging | `( °□° )` |
|
|
52
56
|
|
|
53
57
|
---
|
|
54
58
|
|
|
55
|
-
### 🖱️
|
|
59
|
+
### 🖱️ Interactions (5)
|
|
56
60
|
|
|
57
|
-
| # |
|
|
58
|
-
|
|
59
|
-
| 1 | **Alt +
|
|
60
|
-
| 2 |
|
|
61
|
-
| 3 |
|
|
62
|
-
| 4 |
|
|
63
|
-
| 5 |
|
|
61
|
+
| # | Action | Effect |
|
|
62
|
+
|---|--------|--------|
|
|
63
|
+
| 1 | **Alt + drag** | Freely move the mascot anywhere |
|
|
64
|
+
| 2 | **Double-click** | Cycle through characters (within 300ms) |
|
|
65
|
+
| 3 | **Drag color flash** | Body flashes through 8 highlight colors at 100ms, locks on release |
|
|
66
|
+
| 4 | **Drag alien text** | Pink "let go of me" alien text appears above head while dragging |
|
|
67
|
+
| 5 | **Drag panic** | `( °□° )` face + arms flailing while dragging |
|
|
64
68
|
|
|
65
69
|
---
|
|
66
70
|
|
|
67
|
-
### 🫣
|
|
71
|
+
### 🫣 Peek-a-Boo System (Work Page)
|
|
68
72
|
|
|
69
|
-
| # |
|
|
70
|
-
|
|
71
|
-
| 1 |
|
|
72
|
-
| 2 |
|
|
73
|
-
| 3 |
|
|
73
|
+
| # | Action | Effect |
|
|
74
|
+
|---|--------|--------|
|
|
75
|
+
| 1 | Drag to left edge | Mascot hides, only 2 rows visible |
|
|
76
|
+
| 2 | Release at edge | Auto peek cycle (peeks 2 more rows every 1.2s, then retreats) |
|
|
77
|
+
| 3 | Click / start working | Slides back from edge + bounce |
|
|
74
78
|
|
|
75
79
|
---
|
|
76
80
|
|
|
77
|
-
### 💥
|
|
81
|
+
### 💥 Random Events (3)
|
|
78
82
|
|
|
79
|
-
| # |
|
|
80
|
-
|
|
81
|
-
| 1 |
|
|
82
|
-
| 2 |
|
|
83
|
-
| 3 |
|
|
83
|
+
| # | Event | Trigger | Effect |
|
|
84
|
+
|---|-------|---------|--------|
|
|
85
|
+
| 1 | **Fall apart** | Jump landing 40% / bounce 50% | Lines scatter → reassemble after 1.5s |
|
|
86
|
+
| 2 | **Bomb drop** | 10% chance replacing walk (idle) | Fuse burns ✦/◌ + countdown ³·→²·→¹· → white flash explosion + `ᵇᵒᵒᵐ~` → reassemble |
|
|
87
|
+
| 3 | **Scatter & assemble** | Startup / switch to work page | Lines start from random positions, 15-frame linear interpolation to home |
|
|
84
88
|
|
|
85
89
|
---
|
|
86
90
|
|
|
87
|
-
### 🔄
|
|
91
|
+
### 🔄 Auto-Update
|
|
88
92
|
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
+
- Checks npm latest version on startup → semver compare → `npm pack` download → `tar` extract overwrite
|
|
94
|
+
- File lock prevents concurrency (30s expiry auto-cleanup)
|
|
95
|
+
- Syncs opencode plugin manifest version to prevent rollback on restart
|
|
96
|
+
- On successful update: mascot jumps to celebrate + alien-text version number `ᵘᵖ→⁰·⁵·¹`
|
|
93
97
|
|
|
94
98
|
---
|
|
95
99
|
|
|
96
|
-
### 🎵
|
|
100
|
+
### 🎵 State Sync
|
|
97
101
|
|
|
98
|
-
|
|
|
99
|
-
|
|
100
|
-
| session busy |
|
|
101
|
-
| session thinking |
|
|
102
|
-
| session happy |
|
|
103
|
-
| session idle
|
|
104
|
-
|
|
|
102
|
+
| Trigger | Effect |
|
|
103
|
+
|---------|--------|
|
|
104
|
+
| session busy | Alien text bubble + 8-color highlight flash |
|
|
105
|
+
| session thinking | Foot stomp + face shift + alien text |
|
|
106
|
+
| session happy | Head sway celebration 3s |
|
|
107
|
+
| session idle timeout | Auto-sleep + alien-text Zzz (`zᶻ...` → `zᶻᶻ...` → `zᶻᶻᶻ...`) |
|
|
108
|
+
| Drag while sleeping | Startles awake to idle + arm flail panic |
|
|
105
109
|
|
|
106
|
-
>
|
|
110
|
+
> All states default to yueer. Double-click to manually switch to baozi.
|
|
107
111
|
|
|
108
112
|
---
|
|
109
113
|
|
|
110
|
-
### 🚀
|
|
114
|
+
### 🚀 Startup Effects
|
|
111
115
|
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
116
|
+
- Version number shown 2s after startup as alien-text `ᵛ⁰·⁵·¹` (3s duration)
|
|
117
|
+
- Home page mascot appears at random horizontal position
|
|
118
|
+
- Work page scatter-assemble animation on first message / `opencode -c`
|
|
115
119
|
|
|
116
120
|
---
|
|
117
121
|
|
|
118
|
-
## 📦
|
|
122
|
+
## 📦 Installation
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
Add to `~/.config/opencode/tui.json`:
|
|
121
125
|
|
|
122
126
|
```json
|
|
123
127
|
{
|
|
@@ -125,51 +129,47 @@
|
|
|
125
129
|
}
|
|
126
130
|
```
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
Restart opencode. The plugin auto-updates to the latest version.
|
|
129
133
|
|
|
130
|
-
## 🛠️
|
|
134
|
+
## 🛠️ Tech Stack
|
|
131
135
|
|
|
132
136
|
- **TypeScript** ESM
|
|
133
|
-
- **@opentui/solid** — SolidJS
|
|
134
|
-
- **@opencode-ai/plugin** — OpenCode
|
|
135
|
-
-
|
|
136
|
-
- TypeScript
|
|
137
|
+
- **@opentui/solid** — SolidJS reactive TUI rendering
|
|
138
|
+
- **@opencode-ai/plugin** — OpenCode plugin API
|
|
139
|
+
- Zero runtime dependencies (peer dep only)
|
|
140
|
+
- TypeScript type-checked
|
|
137
141
|
|
|
138
|
-
## 📂
|
|
142
|
+
## 📂 Project Structure
|
|
139
143
|
|
|
140
144
|
```
|
|
141
145
|
opencode-mascot/
|
|
142
|
-
├── tui.tsx #
|
|
146
|
+
├── tui.tsx # Plugin entry, registers slots + startup
|
|
143
147
|
├── src/
|
|
144
148
|
│ ├── core/
|
|
145
|
-
│ │ ├── types.ts # MascotPack / MascotState / Effect
|
|
146
|
-
│ │ ├── ascii-renderer.tsx #
|
|
147
|
-
│ │ ├── mascot-loader.ts #
|
|
148
|
-
│ │ ├── celebration-bus.ts #
|
|
149
|
-
│ │ ├── updater.ts # npm
|
|
150
|
-
│ │ └── logger.ts #
|
|
149
|
+
│ │ ├── types.ts # MascotPack / MascotState / Effect types
|
|
150
|
+
│ │ ├── ascii-renderer.tsx # Core rendering engine (574 lines, 16+ animations)
|
|
151
|
+
│ │ ├── mascot-loader.ts # Built-in character loader
|
|
152
|
+
│ │ ├── celebration-bus.ts # Module-level event bus
|
|
153
|
+
│ │ ├── updater.ts # npm auto-update
|
|
154
|
+
│ │ └── logger.ts # File logger
|
|
151
155
|
│ ├── components/
|
|
152
|
-
│ │ ├── home-mascot.tsx #
|
|
153
|
-
│ │ └── sidebar-mascot.tsx #
|
|
156
|
+
│ │ ├── home-mascot.tsx # Home page mascot
|
|
157
|
+
│ │ └── sidebar-mascot.tsx # Work page mascot (peek-a-boo)
|
|
154
158
|
│ └── builtins/
|
|
155
|
-
│ ├── yueer/ #
|
|
156
|
-
│
|
|
157
|
-
│ │ └── index.ts # 专属动画(呆毛/晃头/跺脚/变脸/气泡/扇手)
|
|
158
|
-
│ └── baozi/ # 包子形象(frames + effects)
|
|
159
|
-
│ ├── frames.ts # 5 帧 ASCII art
|
|
160
|
-
│ └── index.ts # 专属动画(蒸汽/气泡/惊恐)
|
|
159
|
+
│ ├── yueer/ # yueer (frames + effects)
|
|
160
|
+
│ └── baozi/ # baozi (frames + effects)
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
## 🎨
|
|
163
|
+
## 🎨 Custom Characters
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
Define a `MascotPack`:
|
|
166
166
|
|
|
167
167
|
```typescript
|
|
168
168
|
import type { MascotPack } from "@mingxy/opencode-mascot/types";
|
|
169
169
|
|
|
170
170
|
const myMascot: MascotPack = {
|
|
171
171
|
name: "@mingxy/mascot-custom",
|
|
172
|
-
displayName: "
|
|
172
|
+
displayName: "Kitty",
|
|
173
173
|
version: "0.1.0",
|
|
174
174
|
author: "you",
|
|
175
175
|
description: "My custom mascot",
|
|
@@ -191,22 +191,22 @@ const myMascot: MascotPack = {
|
|
|
191
191
|
};
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
All built-in animations (blink/breath/walk/jump/sleep/drag/color-flash/bomb/fall-apart/reassemble) **work automatically**.
|
|
195
195
|
|
|
196
|
-
## 📊
|
|
196
|
+
## 📊 Capabilities
|
|
197
197
|
|
|
198
|
-
|
|
|
199
|
-
|
|
200
|
-
|
|
|
201
|
-
|
|
|
202
|
-
|
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
208
|
-
|
|
|
209
|
-
|
|
|
198
|
+
| Category | Count |
|
|
199
|
+
|----------|-------|
|
|
200
|
+
| Built-in characters | 2 |
|
|
201
|
+
| Expression frames | 5 / character |
|
|
202
|
+
| Auto animations | 16 |
|
|
203
|
+
| Interactions | 5 |
|
|
204
|
+
| Peek-a-boo behaviors | 3 |
|
|
205
|
+
| Random events | 3 |
|
|
206
|
+
| Alien text phrases | 24 (12/character) |
|
|
207
|
+
| Flash colors | 8 |
|
|
208
|
+
| Drag alien text | 6 |
|
|
209
|
+
| **Total** | **33+** |
|
|
210
210
|
|
|
211
211
|
## 📄 License
|
|
212
212
|
|
|
@@ -216,3 +216,4 @@ MIT © [mingxy](https://github.com/mengfanbo123)
|
|
|
216
216
|
|
|
217
217
|
- [GitHub](https://github.com/mengfanbo123/opencode-mascot)
|
|
218
218
|
- [npm](https://www.npmjs.com/package/@mingxy/opencode-mascot)
|
|
219
|
+
- [中文文档](./README_zh-CN.md)
|
package/package.json
CHANGED
|
@@ -117,6 +117,7 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
117
117
|
const returnToView = () => {
|
|
118
118
|
if (!hideSide) return;
|
|
119
119
|
stopPeek();
|
|
120
|
+
stopReturn();
|
|
120
121
|
const cw = getCw();
|
|
121
122
|
const cur = posX();
|
|
122
123
|
const targetX = hideSide === "left" ? 0 : Math.max(0, cw - MASCOT_WIDTH);
|
|
@@ -128,7 +129,10 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
128
129
|
setPosX(targetX);
|
|
129
130
|
stopReturn();
|
|
130
131
|
hideSide = null;
|
|
131
|
-
renderers[currentName()]
|
|
132
|
+
const r = renderers[currentName()];
|
|
133
|
+
if (r.getState() === "idle") {
|
|
134
|
+
r.bounce();
|
|
135
|
+
}
|
|
132
136
|
return;
|
|
133
137
|
}
|
|
134
138
|
setPosX(now + step);
|
|
@@ -46,6 +46,7 @@ function getFrameLines(pack: MascotPack, frameName: string): string[] {
|
|
|
46
46
|
|
|
47
47
|
export function createAnimatedRenderer(pack: MascotPack): {
|
|
48
48
|
element: () => JSX.Element;
|
|
49
|
+
getState: () => MascotState;
|
|
49
50
|
setState: (s: MascotState) => void;
|
|
50
51
|
toggleWalk: () => void;
|
|
51
52
|
setDragging: (v: boolean) => void;
|
|
@@ -573,5 +574,5 @@ export function createAnimatedRenderer(pack: MascotPack): {
|
|
|
573
574
|
}, 700);
|
|
574
575
|
};
|
|
575
576
|
|
|
576
|
-
return { element, setState, toggleWalk, setDragging, celebrateUpdate, bounce, showVersion, scatterIn, explode };
|
|
577
|
+
return { element, getState: currentState, setState, toggleWalk, setDragging, celebrateUpdate, bounce, showVersion, scatterIn, explode };
|
|
577
578
|
}
|