plugin-gentleman 1.0.8 → 1.1.1

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
@@ -98,7 +98,7 @@ The ASCII representation features:
98
98
  - **Eyes** that blink and look in 8 directions (center, up, down, left, right, and 4 diagonals) *(sidebar only)*
99
99
  - **Mustache** rendered in grayscale gradient on home screen, semantic zone colors in sidebar
100
100
  - **Tongue** that appears during busy states or periodic expressive cycles *(sidebar only)*
101
- - **Motivational phrases** in Rioplatense Spanish style — 2-3 random phrases rotating every 3s *(sidebar only)*
101
+ - **Motivational phrases** in Rioplatense Spanish style — single random phrase rotating every 3s *(sidebar only)*
102
102
 
103
103
  **Example phrases during busy states:**
104
104
  - *"Ponete las pilas, hermano..."*
@@ -124,9 +124,9 @@ The ASCII representation features:
124
124
  - Returns to center frequently for natural feel
125
125
 
126
126
  **Busy/Expressive State** *(when `animations: true`)*
127
- - Tongue appears progressively when OpenCode is processing
127
+ - Tongue appears when OpenCode is processing
128
128
  - Eyes squint during expressive state
129
- - 2-3 random motivational phrases rotating every 3 seconds (36+ phrase library)
129
+ - Single motivational phrase rotating every 3 seconds (36+ phrase library)
130
130
  - Active during detected busy states OR periodic expressive cycles
131
131
 
132
132
  **Expressive Cycle Fallback** *(when `animations: true`)*
@@ -388,7 +388,7 @@ The plugin is organized into focused modules for easy customization:
388
388
  **Adding new content:**
389
389
 
390
390
  - **Motivational phrases:** Edit `phrases.ts` — add new phrases to the `busyPhrases` array (currently 36+ phrases)
391
- - **ASCII art frames:** Edit `ascii-frames.ts` — modify eye positions (9 variants), blink frames (3 stages), mustache designs, or tongue frames (3 growth stages)
391
+ - **ASCII art frames:** Edit `ascii-frames.ts` — modify eye positions (9 variants), blink frames (3 stages), mustache designs, or tongue states (binary on/off)
392
392
  - **UI logic:** Edit `components.tsx` — adjust animation timings, add new effects, or tweak component layout
393
393
  - **Configuration:** Edit `config.ts` — add new config options with type-safe defaults
394
394
  - **Detection logic:** Edit `detection.ts` — add new OS detection patterns or provider mappings
@@ -404,17 +404,17 @@ The plugin is organized into focused modules for easy customization:
404
404
  **Animation timing customization:**
405
405
 
406
406
  All animation intervals are in `components.tsx`:
407
- - **Look-around interval:** Line 79 — currently 3000ms (3s)
408
- - **Blink interval:** Line 107 — currently 2000ms with 35% chance (~5-6s average)
409
- - **Blink frame timing:** Lines 91-98 — currently 80-100ms per frame progression
410
- - **Phrase rotation:** Line 168 — currently 3000ms (3s) during expressive state
411
- - **Expressive cycle timing:** Lines 193-198 — first cycle at 30-45s, then every 45-60s
412
- - **Expressive cycle duration:** Line 189 — currently 8000ms (8s)
407
+ - **Look-around interval:** Currently 3000ms (3s)
408
+ - **Blink interval:** Currently 2000ms with 35% chance (~5-6s average)
409
+ - **Blink frame timing:** Currently 80-100ms per frame progression
410
+ - **Phrase rotation:** Currently 3000ms (3s) during expressive state
411
+ - **Expressive cycle timing:** First cycle at 30-45s, then every 45-60s
412
+ - **Expressive cycle duration:** Currently 8000ms (8s)
413
413
 
414
414
  **Color customization:**
415
415
 
416
416
  - **Zone colors (sidebar):** Edit `zoneColors` object in `ascii-frames.ts` (monocle, eyes, mustache, tongue)
417
- - **Home grayscale gradient:** Edit `HomeLogo` component in `components.tsx` (lines 22-24)
417
+ - **Home grayscale gradient:** Edit `HomeLogo` component in `components.tsx` (top/middle/bottom color values)
418
418
  - **Theme colors:** Edit `gentleman.json` for the full color palette
419
419
 
420
420
  ---
package/ascii-frames.ts CHANGED
@@ -9,15 +9,15 @@
9
9
  export const eyeNeutralCenter = [
10
10
  " █████ █████ ", // 27 chars
11
11
  " ██░░░░░██ ██░░░░░██ ", // 27 chars
12
- " ██░░███░░██ ██░░░░░░░██ ", // 27 chars - pupils center
13
- " ██░░███░░██ ██░░░░░░░██ ", // 27 chars - pupils center
12
+ " ██░░ ░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil center
13
+ " ██░░ ░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil center
14
14
  "██ ██░░░░░██ ██░░░░░██ ██", // 27 chars
15
15
  ]
16
16
 
17
17
  export const eyeNeutralUp = [
18
18
  " █████ █████ ", // 27 chars
19
- " ██████░██ ██░░░░░██ ", // 27 chars - pupils up
20
- " ██░█████░██ ██░░░░░░░██ ", // 27 chars - pupils up
19
+ " ██░░ ██ ██░░░░░██ ", // 27 chars - hollow pupil up
20
+ " ██░░░ ░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil up
21
21
  " ██░░░░░░░██ ██░░░░░░░██ ", // 27 chars
22
22
  "██ ██░░░░░██ ██░░░░░██ ██", // 27 chars
23
23
  ]
@@ -26,38 +26,38 @@ export const eyeNeutralDown = [
26
26
  " █████ █████ ", // 27 chars
27
27
  " ██░░░░░██ ██░░░░░██ ", // 27 chars
28
28
  " ██░░░░░░░██ ██░░░░░░░██ ", // 27 chars
29
- " ██░░███░░██ ██░░░░░░░██ ", // 27 chars - pupils down
30
- "██ ██████░░██ ██░░░░░██ ██", // 27 chars - pupils down
29
+ " ██░░ ░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil down
30
+ "██ ██░░ ░██ ██░░░░░██ ██", // 27 chars - hollow pupil down
31
31
  ]
32
32
 
33
33
  export const eyeNeutralLeft = [
34
34
  " █████ █████ ", // 27 chars
35
35
  " ██░░░░░██ ██░░░░░██ ", // 27 chars
36
- " ██████░░░██ ██░░░░░░░██ ", // 27 chars - pupils left
37
- " ██████░░░██ ██░░░░░░░██ ", // 27 chars - pupils left
36
+ " ██ ░░░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil left
37
+ " ██ ░░░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil left
38
38
  "██ ██░░░░░██ ██░░░░░██ ██", // 27 chars
39
39
  ]
40
40
 
41
41
  export const eyeNeutralRight = [
42
42
  " █████ █████ ", // 27 chars
43
43
  " ██░░░░░██ ██░░░░░██ ", // 27 chars
44
- " ██░░░██████ ██░░░░░░░██ ", // 27 chars - pupils right
45
- " ██░░░██████ ██░░░░░░░██ ", // 27 chars - pupils right
44
+ " ██░░░░░ ██ ██░░░░░░░██ ", // 27 chars - hollow pupil right
45
+ " ██░░░░░ ██ ██░░░░░░░██ ", // 27 chars - hollow pupil right
46
46
  "██ ██░░░░░██ ██░░░░░██ ██", // 27 chars
47
47
  ]
48
48
 
49
49
  export const eyeNeutralUpLeft = [
50
50
  " █████ █████ ", // 27 chars
51
- " ███████░██ ██░░░░░██ ", // 27 chars - up-left diagonal
52
- " ████████░██ ██░░░░░░░██ ", // 27 chars - up-left diagonal
51
+ " ██ ░░██ ██░░░░░██ ", // 27 chars - hollow pupil up-left
52
+ " ██ ░░░░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil up-left
53
53
  " ██░░░░░░░██ ██░░░░░░░██ ", // 27 chars
54
54
  "██ ██░░░░░██ ██░░░░░██ ██", // 27 chars
55
55
  ]
56
56
 
57
57
  export const eyeNeutralUpRight = [
58
58
  " █████ █████ ", // 27 chars
59
- " ██░███████ ██░░░░░██ ", // 27 chars - up-right diagonal
60
- " ██░████████ ██░░░░░░░██ ", // 27 chars - up-right diagonal
59
+ " ██░░░░ ██ ██░░░░░██ ", // 27 chars - hollow pupil up-right
60
+ " ██░░░░░ ██ ██░░░░░░░██ ", // 27 chars - hollow pupil up-right
61
61
  " ██░░░░░░░██ ██░░░░░░░██ ", // 27 chars
62
62
  "██ ██░░░░░██ ██░░░░░██ ██", // 27 chars
63
63
  ]
@@ -66,23 +66,23 @@ export const eyeNeutralDownLeft = [
66
66
  " █████ █████ ", // 27 chars
67
67
  " ██░░░░░██ ██░░░░░██ ", // 27 chars
68
68
  " ██░░░░░░░██ ██░░░░░░░██ ", // 27 chars
69
- " ███████░░██ ██░░░░░░░██ ", // 27 chars - down-left diagonal
70
- "██ ████████░█ ██░░░░░██ ██", // 27 chars - down-left diagonal
69
+ " ██ ░░░░░██ ██░░░░░░░██ ", // 27 chars - hollow pupil down-left
70
+ "██ ██ ░░░░██ ██░░░░░██ ██", // 27 chars - hollow pupil down-left
71
71
  ]
72
72
 
73
73
  export const eyeNeutralDownRight = [
74
74
  " █████ █████ ", // 27 chars
75
75
  " ██░░░░░██ ██░░░░░██ ", // 27 chars
76
76
  " ██░░░░░░░██ ██░░░░░░░██ ", // 27 chars
77
- " ██░░░███████ ██░░░░░░░██ ", // 27 chars - down-right diagonal
78
- "██ ██░████████ ██░░░░░██ ██", // 27 chars - down-right diagonal
77
+ " ██░░░░░ ██ ██░░░░░░░██ ", // 27 chars - hollow pupil down-right
78
+ "██ ██░░░░ ██ ██░░░░░██ ██", // 27 chars - hollow pupil down-right
79
79
  ]
80
80
 
81
81
  // Squinted eyes version for busy/expressive state
82
82
  export const eyeSquinted = [
83
83
  " █████ █████ ", // 27 chars
84
84
  " ██░░░░░██ ██░░░░░██ ", // 27 chars
85
- " ██░░███░░██ ██░░░░░░░██ ", // 27 chars
85
+ " ██░░ ░░██ ██░░░░░░░██ ", // 27 chars
86
86
  " █████████ █████████ ", // 27 chars
87
87
  "██ █████ █████ ██", // 27 chars
88
88
  ]
@@ -91,8 +91,8 @@ export const eyeSquinted = [
91
91
  export const eyeBlinkHalf = [
92
92
  " █████ █████ ", // 27 chars - monocle border top unchanged
93
93
  " █████████ ██████████ ", // 27 chars - upper eyelid descends halfway
94
- " ██░░███░░██ ██░░░░░░░██ ", // 27 chars - pupils still visible
95
- " ██░░███░░██ ██░░░░░░░██ ", // 27 chars - pupils still visible
94
+ " ██░░ ░░██ ██░░░░░░░██ ", // 27 chars - pupils still visible
95
+ " ██░░ ░░██ ██░░░░░░░██ ", // 27 chars - pupils still visible
96
96
  "██ ██░░░░░██ ██░░░░░██ ██", // 27 chars - bottom unchanged
97
97
  ]
98
98
 
@@ -107,11 +107,11 @@ export const eyeBlinkClosed = [
107
107
 
108
108
  // Mustache section (all lines padded to 27 chars for alignment)
109
109
  export const mustachiMustacheSection = [
110
+ "███████████ ███████████", // 27 chars - perfectly symmetric
110
111
  "████████████ ████████████", // 27 chars - perfectly symmetric
111
- "███████████████████████████", // 27 chars - perfectly symmetric
112
112
  " █████████████████████████ ", // 27 chars - perfectly symmetric
113
- " ▓█████████████████████▓ ", // 27 chars - perfectly symmetric
114
- " ▓█████████████████▓ ", // 27 chars - perfectly symmetric
113
+ " ▓██████████ ██████████▓ ", // 27 chars - perfectly symmetric
114
+ " ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ", // 27 chars - perfectly symmetric
115
115
  ]
116
116
 
117
117
  // Tongue animation frames (progressive) - compact design
package/components.tsx CHANGED
@@ -161,11 +161,13 @@ export const SidebarMustachi = (props: { theme: TuiThemeCurrent; config: Cfg; is
161
161
  createEffect(() => {
162
162
  if (!props.config.animations || props.isBusy) return
163
163
 
164
+ let cycleEndTimeout: NodeJS.Timeout | undefined
165
+
164
166
  const triggerExpressiveCycle = () => {
165
167
  setExpressiveCycle(true)
166
168
 
167
169
  // End expressive cycle after 8 seconds
168
- setTimeout(() => {
170
+ cycleEndTimeout = setTimeout(() => {
169
171
  setExpressiveCycle(false)
170
172
  }, 8000)
171
173
  }
@@ -181,6 +183,11 @@ export const SidebarMustachi = (props: { theme: TuiThemeCurrent; config: Cfg; is
181
183
  onCleanup(() => {
182
184
  clearTimeout(firstTimeout)
183
185
  clearInterval(interval)
186
+ if (cycleEndTimeout !== undefined) {
187
+ clearTimeout(cycleEndTimeout)
188
+ }
189
+ // Explicitly reset expressive cycle state to prevent sticking
190
+ setExpressiveCycle(false)
184
191
  })
185
192
  })
186
193
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "plugin-gentleman",
4
- "version": "1.0.8",
4
+ "version": "1.1.1",
5
5
  "description": "OpenCode TUI plugin featuring Mustachi - an animated ASCII mascot with eyes, mustache, and optional motivational phrases during busy states",
6
6
  "type": "module",
7
7
  "exports": {
@@ -36,7 +36,15 @@
36
36
  "@opentui/solid": "*",
37
37
  "solid-js": "*"
38
38
  },
39
- "keywords": ["opencode", "plugin", "theme", "gentleman", "tui", "ascii", "mustachi"],
39
+ "keywords": [
40
+ "opencode",
41
+ "plugin",
42
+ "theme",
43
+ "gentleman",
44
+ "tui",
45
+ "ascii",
46
+ "mustachi"
47
+ ],
40
48
  "author": "",
41
49
  "license": "ISC",
42
50
  "repository": {
@@ -47,4 +55,4 @@
47
55
  "bugs": {
48
56
  "url": "https://github.com/IrrealV/plugin-gentleman/issues"
49
57
  }
50
- }
58
+ }
package/tui.tsx CHANGED
@@ -19,20 +19,35 @@ const tui: TuiPlugin = async (api, options) => {
19
19
  const [value] = createSignal(boot)
20
20
  const [isBusy, setIsBusy] = createSignal(false)
21
21
 
22
- await api.theme.install("./gentleman.json")
23
- if (value().set_theme) {
24
- api.theme.set(value().theme)
22
+ // Theme setup - wrapped in try-catch to avoid breaking plugin bootstrap
23
+ try {
24
+ await api.theme.install("./gentleman.json")
25
+ if (value().set_theme) {
26
+ api.theme.set(value().theme)
27
+ }
28
+ } catch (error) {
29
+ // Theme installation failed - log but continue with slot registration
30
+ // Plugin will still render with default theme
31
+ console.error("[plugin-gentleman] Theme setup failed:", error)
25
32
  }
26
33
 
27
34
  // Detect busy state if API exposes it
28
- // This is a best-effort detection - OpenCode TUI may or may not expose this
35
+ // Note: Best-effort detection - OpenCode TUI may not expose session.running
36
+ // If unavailable, isBusy remains false and expressive cycle fallback handles animations
29
37
  createEffect(() => {
30
38
  try {
31
- // Check if there's a running agent or session state
32
- const hasRunningSession = api.state?.session?.running
33
- setIsBusy(!!hasRunningSession)
39
+ // Attempt to reactively track running state if API supports it
40
+ // This may or may not work depending on OpenCode version
41
+ if (api.state?.session?.running !== undefined) {
42
+ setIsBusy(!!api.state.session.running)
43
+ } else {
44
+ // API doesn't expose running state - degrade gracefully
45
+ // Expressive cycle fallback in components.tsx will demonstrate animations
46
+ setIsBusy(false)
47
+ }
34
48
  } catch {
35
- // If API doesn't expose this, animations will just use idle state
49
+ // If API doesn't expose this at all, stay in idle state
50
+ // Periodic expressive cycles ensure animations are still visible
36
51
  setIsBusy(false)
37
52
  }
38
53
  })