plugin-gentleman 1.0.2 → 1.0.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tui.tsx +86 -92
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.2",
4
+ "version": "1.0.3",
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": {
package/tui.tsx CHANGED
@@ -6,60 +6,41 @@ import { createSignal, onCleanup, createEffect } from "solid-js"
6
6
 
7
7
  const id = "gentleman"
8
8
 
9
- // Premium Mustachi ASCII art - full version for sidebar
9
+ // Premium Mustachi ASCII art - compact version for sidebar (25 chars wide)
10
10
  // Base structure with eyes that will be replaced dynamically
11
11
  const mustachiNeutralBase = [
12
- " ████████████ ████████████",
13
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
14
- " ██░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░██",
15
- " ██░░░░████████░░░░██ ██░░░░░░░░░░░░░░░░██",
16
- " ██░░░░████████░░░░██ ██░░░░░░░░░░░░░░░░██",
17
- " ██░░░░████████░░░░██ ██░░░░░░░░░░░░░░░░██",
18
- " ██░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░██",
19
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
20
- " ████████████ ████████████",
12
+ " █████ █████",
13
+ " ██░░░░░██ ██░░░░░██",
14
+ " ██░░███░░██ ██░░░░░░░██",
15
+ " ██░░███░░██ ██░░░░░░░██",
16
+ "██ ██░░░░░██ ██░░░░░██ ██",
21
17
  ]
22
18
 
23
19
  // Squinted eyes version for busy state
24
20
  const mustachiSquintedBase = [
25
- " ████████████ ████████████",
26
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
27
- " ██░░░████████░░░██ ██░░░░░░░░░░░░░░██",
28
- " ██░░████░░░░████░░██ ██░░░░░░░░░░░░░░░░██",
29
- " ██░░██░░░░░░░░██░░██ ██░░░░░░░░░░░░░░░░██",
30
- " ██░░░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░░░██",
31
- " ██░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░██",
32
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
33
- " ████████████ ████████████",
21
+ " █████ █████",
22
+ " ██░░░░░██ ██░░░░░██",
23
+ " ██░░███░░██ ██░░░░░░░██",
24
+ " █████████ █████████",
25
+ "██ █████ █████ ██",
34
26
  ]
35
27
 
36
- // Mustache section (shared by all states)
28
+ // Mustache section (compact 25-char wide design)
37
29
  const mustachiMustacheSection = [
38
- " ████████ ████████",
39
- " ████████████ ████████████",
40
- " ██ ████████████████ ████████████████ ██",
41
- " ████ ████████████████████ ████████████████████ ████",
42
- " ██████ ███████████████████████████████████████████ ██████",
43
- " ███████████████████████████████████████████████████████████",
44
- " ███████████████████████████████████████████████████████████",
45
- " ███████████████████████████████████████████████████████████",
46
- " █████████████████████████████████████████████████████████",
47
- " ███████████████████████████████████████████████████████",
48
- " ▓▓█████████████████████ █████████████████████▓▓",
49
- " ▓▓▓███████████████ ███████████████▓▓▓",
50
- " ▓▓▓█████████ █████████▓▓▓",
51
- " ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓",
30
+ "██████████ ████████",
31
+ "████████████ ██████████",
32
+ " █████████████████████████",
33
+ " ▓██████████ ██████████▓",
34
+ " ▓██████ ██████▓",
52
35
  ]
53
36
 
54
- // Tongue animation frames (progressive)
37
+ // Tongue animation frames (progressive) - compact design
55
38
  const tongueFrames = [
56
39
  [], // no tongue
57
- [" ███████"], // small tongue
58
- [" ███████", " █████"], // medium tongue
59
- [" ███████", " █████", " ███"], // full tongue
40
+ [" ███", " █"], // tongue out
60
41
  ]
61
42
 
62
- // Mustache-only ASCII art for home logo (prominent and simple)
43
+ // Mustache-only ASCII art for home logo (original massive solid block design)
63
44
  const mustachiMustacheOnly = [
64
45
  "",
65
46
  " ████████ ████████",
@@ -82,11 +63,12 @@ const mustachiMustacheOnly = [
82
63
  // Left pupil positions for look-around animation (progressive)
83
64
  // Modifies only the left eye (white sclera with dark pupil)
84
65
  // Right eye is monocle/glass and remains static
66
+ // Pupil is on lines 2 and 3 (indices 2-3) of the 5-line eye array
85
67
  const leftPupilPositions = [
86
- "████████", // center (line 3 of eyes)
87
- "██████ ", // looking left
88
- " ██████", // looking right
89
- "████████", // center again
68
+ "██░░███░░██", // center (line 2 of eyes)
69
+ "██████░░░██", // looking left
70
+ "██░░░██████", // looking right
71
+ "██░░███░░██", // center again
90
72
  ]
91
73
 
92
74
  // Blink animation frames (progressive) - affects both eyes
@@ -96,30 +78,22 @@ const blinkFrames = [
96
78
  // Half closed
97
79
  {
98
80
  left: [
99
- " ████████████ ████████████",
100
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
101
- " ██░░░░████░░░░░░██ ██░░░░░░░░░░░░░░██",
102
- " ██░░░░████████░░░░██ ██░░░░░░░░░░░░░░░░██",
103
- " ██░░░░████████░░░░██ ██░░░░░░░░░░░░░░░░██",
104
- " ██░░░░████████░░░░██ ██░░░░░░░░░░░░░░░░██",
105
- " ██░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░██",
106
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
107
- " ████████████ ████████████",
81
+ " █████ █████",
82
+ " ██░░░░░██ ██░░░░░██",
83
+ " ██░░███░░██ ██░░░░░░░██",
84
+ " █████████ █████████",
85
+ "██ █████ █████ ██",
108
86
  ],
109
87
  squinted: mustachiSquintedBase // squinted stays squinted during blink
110
88
  },
111
89
  // Fully closed
112
90
  {
113
91
  left: [
114
- " ████████████ ████████████",
115
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
116
- " ██░░████████░░░░██ ██░░░░░░░░░░░░░░██",
117
- " ██░░████████████░░██ ██░░░░░░░░░░░░░░░░██",
118
- " ██░░████████████░░██ ██░░░░░░░░░░░░░░░░██",
119
- " ██░░████████████░░██ ██░░░░░░░░░░░░░░░░██",
120
- " ██░░░░░░░░░░░░░░██ ██░░░░░░░░░░░░░░██",
121
- " ██░░░░░░░░░░░░██ ██░░░░░░░░░░░░██",
122
- " ████████████ ████████████",
92
+ " █████ █████",
93
+ " ██░░░░░██ ██░░░░░██",
94
+ " █████████ █████████",
95
+ " █████████ █████████",
96
+ "██ █████ █████ ██",
123
97
  ],
124
98
  squinted: mustachiSquintedBase
125
99
  },
@@ -235,31 +209,40 @@ const getProviders = (providers: ReadonlyArray<{ id: string; name: string }> | u
235
209
  return Array.from(names).sort().join(", ")
236
210
  }
237
211
 
238
- // Home logo: Mustache-only (simple and prominent)
212
+ // Home logo: Mustache-only (simple and prominent) with grayscale gradient
239
213
  const HomeLogo = (props: { theme: TuiThemeCurrent }) => {
240
- const topColor = props.theme.accent || "#E0C15A"
241
- const midColor = props.theme.primary || "#7FB4CA"
242
- const bottomColor = props.theme.error || "#CB7C94"
214
+ // Grayscale palette for better TUI readability
215
+ const lightGray = "#C0C0C0" // Light gray for highlights
216
+ const midGray = "#808080" // Mid gray for main body
217
+ const darkGray = "#505050" // Dark gray for shadows
243
218
 
244
219
  return (
245
220
  <box flexDirection="column" alignItems="center">
246
- {/* Mustache-only with 3-tone gradient */}
221
+ {/* Mustache with grayscale gradient for depth */}
247
222
  {mustachiMustacheOnly.map((line, idx) => {
248
223
  const totalLines = mustachiMustacheOnly.length
249
- let color = midColor
224
+ let color = midGray
250
225
  if (idx < totalLines / 3) {
251
- color = topColor
226
+ color = lightGray // Top highlight
252
227
  } else if (idx >= (2 * totalLines) / 3) {
253
- color = bottomColor
228
+ color = darkGray // Bottom shadow
254
229
  }
255
230
  return <text fg={color}>{line}</text>
256
231
  })}
257
232
 
258
- {/* OpenCode branding */}
259
- <box flexDirection="row" gap={0}>
260
- <text fg={props.theme.textMuted} dimColor={true}>╭</text>
261
- <text fg={props.theme.primary} dimColor={false}> OpenCode </text>
262
- <text fg={props.theme.textMuted} dimColor={true}>╮</text>
233
+ {/* OpenCode branding — enlarged and prominent */}
234
+ <box flexDirection="column" alignItems="center" marginTop={1}>
235
+ <box flexDirection="row" gap={0}>
236
+ <text fg={props.theme.textMuted}>╔═══════════╗</text>
237
+ </box>
238
+ <box flexDirection="row" gap={0}>
239
+ <text fg={props.theme.textMuted}>║ </text>
240
+ <text fg={props.theme.primary} bold={true}>OpenCode</text>
241
+ <text fg={props.theme.textMuted}> ║</text>
242
+ </box>
243
+ <box flexDirection="row" gap={0}>
244
+ <text fg={props.theme.textMuted}>╚═══════════╝</text>
245
+ </box>
263
246
  </box>
264
247
 
265
248
  <text> </text>
@@ -267,7 +250,7 @@ const HomeLogo = (props: { theme: TuiThemeCurrent }) => {
267
250
  )
268
251
  }
269
252
 
270
- // Sidebar: Full Mustachi face with progressive animations
253
+ // Sidebar: Full Mustachi face with progressive animations (grayscale for clarity)
271
254
  const SidebarMustachi = (props: { theme: TuiThemeCurrent; config: Cfg; isBusy?: boolean }) => {
272
255
  const [pupilIndex, setPupilIndex] = createSignal(0)
273
256
  const [blinkFrame, setBlinkFrame] = createSignal(0)
@@ -328,17 +311,17 @@ const SidebarMustachi = (props: { theme: TuiThemeCurrent; config: Cfg; isBusy?:
328
311
  return
329
312
  }
330
313
 
331
- // Grow tongue progressively when entering busy state
314
+ // Grow tongue progressively when entering busy state (2 frames: hidden -> visible)
332
315
  let currentFrame = 0
333
316
  let tongueTimeoutId: NodeJS.Timeout | undefined
334
317
  const growTongue = () => {
335
318
  if (currentFrame < tongueFrames.length - 1) {
336
319
  currentFrame++
337
320
  setTongueFrame(currentFrame)
338
- tongueTimeoutId = setTimeout(growTongue, 200)
339
321
  }
340
322
  }
341
- growTongue()
323
+ // Show tongue immediately when busy
324
+ tongueTimeoutId = setTimeout(growTongue, 200)
342
325
 
343
326
  // Rotate busy phrases
344
327
  let phraseIdx = 0
@@ -371,12 +354,12 @@ const SidebarMustachi = (props: { theme: TuiThemeCurrent; config: Cfg; isBusy?:
371
354
  : blinkFrames[blinkFrame()].left
372
355
  }
373
356
 
374
- // Add eyes with pupil position (modify line 3 for left eye pupil)
357
+ // Add eyes with pupil position (modify line 2 for left eye pupil - index 2 in 5-line array)
375
358
  eyeBase.forEach((line, idx) => {
376
- if (idx === 3 && !props.isBusy && pupilIndex() >= 0) {
377
- // Replace pupil in left eye (center of line 3)
359
+ if (idx === 2 && !props.isBusy && pupilIndex() >= 0) {
360
+ // Replace pupil in left eye (positions 2-12 of the line for the 25-char compact design)
378
361
  const pupil = leftPupilPositions[pupilIndex()]
379
- const modifiedLine = line.substring(0, 14) + pupil + line.substring(22)
362
+ const modifiedLine = line.substring(0, 2) + pupil + line.substring(13)
380
363
  lines.push(modifiedLine)
381
364
  } else {
382
365
  lines.push(line)
@@ -386,10 +369,10 @@ const SidebarMustachi = (props: { theme: TuiThemeCurrent; config: Cfg; isBusy?:
386
369
  // Add mustache section
387
370
  mustachiMustacheSection.forEach(line => lines.push(line))
388
371
 
389
- // Add tongue if busy (progressive frames)
372
+ // Add tongue if busy (progressive frames) - mark as tongue for coloring
390
373
  if (props.isBusy && tongueFrame() > 0) {
391
374
  const tongueLines = tongueFrames[tongueFrame()]
392
- tongueLines.forEach(line => lines.push(line))
375
+ tongueLines.forEach(line => lines.push(`TONGUE:${line}`))
393
376
  }
394
377
 
395
378
  return lines
@@ -397,22 +380,33 @@ const SidebarMustachi = (props: { theme: TuiThemeCurrent; config: Cfg; isBusy?:
397
380
 
398
381
  const faceLines = buildFace()
399
382
 
400
- const topColor = props.theme.accent || "#E0C15A"
401
- const midColor = props.theme.primary || "#7FB4CA"
402
- const bottomColor = props.theme.error || "#CB7C94"
383
+ // Grayscale palette for TUI clarity
384
+ const lightGray = "#C0C0C0" // Light gray for highlights
385
+ const midGray = "#808080" // Mid gray for main body
386
+ const darkGray = "#505050" // Dark gray for shadows
387
+ const tongueColor = "#FF4466" // Pink/Red for tongue
403
388
 
404
389
  return (
405
390
  <box flexDirection="column" alignItems="center">
406
- {/* Full Mustachi face with 3-tone gradient */}
391
+ {/* Full Mustachi face with grayscale gradient + pink tongue */}
407
392
  {faceLines.map((line, idx) => {
393
+ // Check if this is a tongue line
394
+ const isTongue = line.startsWith("TONGUE:")
395
+ const displayLine = isTongue ? line.substring(7) : line
396
+
397
+ if (isTongue) {
398
+ return <text fg={tongueColor}>{displayLine}</text>
399
+ }
400
+
401
+ // Apply grayscale gradient to eyes and mustache
408
402
  const totalLines = faceLines.length
409
- let color = midColor
403
+ let color = midGray
410
404
  if (idx < totalLines / 3) {
411
- color = topColor
405
+ color = lightGray // Top highlight
412
406
  } else if (idx >= (2 * totalLines) / 3) {
413
- color = bottomColor
407
+ color = darkGray // Bottom shadow
414
408
  }
415
- return <text fg={color}>{line}</text>
409
+ return <text fg={color}>{displayLine}</text>
416
410
  })}
417
411
 
418
412
  {/* Busy phrase if loading */}