@mingxy/opencode-mascot 0.5.7 → 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 -113
- 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,50 +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
|
-
-
|
|
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
|
|
136
141
|
|
|
137
|
-
## 📂
|
|
142
|
+
## 📂 Project Structure
|
|
138
143
|
|
|
139
144
|
```
|
|
140
145
|
opencode-mascot/
|
|
141
|
-
├── tui.tsx #
|
|
146
|
+
├── tui.tsx # Plugin entry, registers slots + startup
|
|
142
147
|
├── src/
|
|
143
148
|
│ ├── core/
|
|
144
|
-
│ │ ├── types.ts # MascotPack / MascotState / Effect
|
|
145
|
-
│ │ ├── ascii-renderer.tsx #
|
|
146
|
-
│ │ ├── mascot-loader.ts #
|
|
147
|
-
│ │ ├── celebration-bus.ts #
|
|
148
|
-
│ │ ├── updater.ts # npm
|
|
149
|
-
│ │ └── 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
|
|
150
155
|
│ ├── components/
|
|
151
|
-
│ │ ├── home-mascot.tsx #
|
|
152
|
-
│ │ └── sidebar-mascot.tsx #
|
|
156
|
+
│ │ ├── home-mascot.tsx # Home page mascot
|
|
157
|
+
│ │ └── sidebar-mascot.tsx # Work page mascot (peek-a-boo)
|
|
153
158
|
│ └── builtins/
|
|
154
|
-
│ ├── yueer/ #
|
|
155
|
-
│
|
|
156
|
-
│ │ └── index.ts # 专属动画(呆毛/晃头/跺脚/变脸/气泡/扇手)
|
|
157
|
-
│ └── baozi/ # 包子形象(frames + effects)
|
|
158
|
-
│ ├── frames.ts # 5 帧 ASCII art
|
|
159
|
-
│ └── index.ts # 专属动画(蒸汽/气泡/惊恐)
|
|
159
|
+
│ ├── yueer/ # yueer (frames + effects)
|
|
160
|
+
│ └── baozi/ # baozi (frames + effects)
|
|
160
161
|
```
|
|
161
162
|
|
|
162
|
-
## 🎨
|
|
163
|
+
## 🎨 Custom Characters
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
Define a `MascotPack`:
|
|
165
166
|
|
|
166
167
|
```typescript
|
|
167
168
|
import type { MascotPack } from "@mingxy/opencode-mascot/types";
|
|
168
169
|
|
|
169
170
|
const myMascot: MascotPack = {
|
|
170
171
|
name: "@mingxy/mascot-custom",
|
|
171
|
-
displayName: "
|
|
172
|
+
displayName: "Kitty",
|
|
172
173
|
version: "0.1.0",
|
|
173
174
|
author: "you",
|
|
174
175
|
description: "My custom mascot",
|
|
@@ -190,22 +191,22 @@ const myMascot: MascotPack = {
|
|
|
190
191
|
};
|
|
191
192
|
```
|
|
192
193
|
|
|
193
|
-
|
|
194
|
+
All built-in animations (blink/breath/walk/jump/sleep/drag/color-flash/bomb/fall-apart/reassemble) **work automatically**.
|
|
194
195
|
|
|
195
|
-
## 📊
|
|
196
|
+
## 📊 Capabilities
|
|
196
197
|
|
|
197
|
-
|
|
|
198
|
-
|
|
199
|
-
|
|
|
200
|
-
|
|
|
201
|
-
|
|
|
202
|
-
|
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
208
|
-
|
|
|
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+** |
|
|
209
210
|
|
|
210
211
|
## 📄 License
|
|
211
212
|
|
|
@@ -215,3 +216,4 @@ MIT © [mingxy](https://github.com/mengfanbo123)
|
|
|
215
216
|
|
|
216
217
|
- [GitHub](https://github.com/mengfanbo123/opencode-mascot)
|
|
217
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
|
}
|